package auth

import (
	"database/sql"
	"encoding/json"
	"net/http"

	"go.uber.org/zap"
	"golang.org/x/crypto/bcrypt"

	"github.com/veylant/ia-gateway/internal/apierror"
	"github.com/veylant/ia-gateway/internal/middleware"
)

// LoginHandler handles POST /v1/auth/login (public — no JWT required).
type LoginHandler struct {
	db         *sql.DB
	jwtSecret  string
	jwtTTLHours int
	logger     *zap.Logger
}

// NewLoginHandler creates a LoginHandler.
func NewLoginHandler(db *sql.DB, jwtSecret string, jwtTTLHours int, logger *zap.Logger) *LoginHandler {
	return &LoginHandler{db: db, jwtSecret: jwtSecret, jwtTTLHours: jwtTTLHours, logger: logger}
}

type loginRequest struct {
	Email    string `json:"email"`
	Password string `json:"password"`
}

type loginResponse struct {
	Token string   `json:"token"`
	User  userInfo `json:"user"`
}

type userInfo struct {
	ID         string `json:"id"`
	Email      string `json:"email"`
	Name       string `json:"name"`
	Role       string `json:"role"`
	TenantID   string `json:"tenant_id"`
	Department string `json:"department,omitempty"`
}

// ServeHTTP handles the login request.
func (h *LoginHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if h.db == nil {
		apierror.WriteError(w, &apierror.APIError{
			Type:       "not_implemented",
			Message:    "database not configured",
			HTTPStatus: http.StatusNotImplemented,
		})
		return
	}

	var req loginRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		apierror.WriteError(w, apierror.NewBadRequestError("invalid JSON"))
		return
	}
	if req.Email == "" || req.Password == "" {
		apierror.WriteError(w, apierror.NewBadRequestError("email and password are required"))
		return
	}

	// Look up user by email (cross-tenant — each email is unique per tenant, but
	// for V1 single-tenant we look globally).
	var u userInfo
	var passwordHash string
	err := h.db.QueryRowContext(r.Context(),
		`SELECT id, tenant_id, email, COALESCE(name,''), role, COALESCE(department,''), password_hash
		 FROM users
		 WHERE email = $1 AND is_active = TRUE
		 LIMIT 1`,
		req.Email,
	).Scan(&u.ID, &u.TenantID, &u.Email, &u.Name, &u.Role, &u.Department, &passwordHash)

	if err == sql.ErrNoRows {
		// Use same error to avoid user enumeration
		apierror.WriteError(w, apierror.NewAuthError("invalid credentials"))
		return
	}
	if err != nil {
		h.logger.Error("login db query failed", zap.Error(err))
		apierror.WriteError(w, apierror.NewUpstreamError("login failed"))
		return
	}

	if err := bcrypt.CompareHashAndPassword([]byte(passwordHash), []byte(req.Password)); err != nil {
		apierror.WriteError(w, apierror.NewAuthError("invalid credentials"))
		return
	}

	claims := &middleware.UserClaims{
		UserID:     u.ID,
		TenantID:   u.TenantID,
		Email:      u.Email,
		Roles:      []string{u.Role},
		Department: u.Department,
	}

	token, err := GenerateToken(claims, u.Name, h.jwtSecret, h.jwtTTLHours)
	if err != nil {
		h.logger.Error("token generation failed", zap.Error(err))
		apierror.WriteError(w, apierror.NewUpstreamError("failed to generate token"))
		return
	}

	h.logger.Info("user logged in", zap.String("email", u.Email), zap.String("tenant_id", u.TenantID))

	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusOK)
	_ = json.NewEncoder(w).Encode(loginResponse{Token: token, User: u})
}
