package api

import (
	"encoding/json"
	"errors"
	"net/http"
	"strings"

	"archivmail/internal/audit"
	"github.com/jackc/pgx/v5/pgconn"
	"golang.org/x/crypto/bcrypt"
)

// ── PROJ-25: Profile Handlers (Password & Email Change) ─────────────────

// handleChangePassword allows a local user to change their own password.
// PATCH /api/auth/password
func (s *Server) handleChangePassword(w http.ResponseWriter, r *http.Request) {
	sess := sessionFromCtx(r.Context())
	if sess.UserID == 0 {
		writeError(w, http.StatusUnauthorized, "not authenticated")
		return
	}

	var req struct {
		CurrentPassword string `json:"current_password"`
		NewPassword     string `json:"new_password"`
	}
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		writeError(w, http.StatusBadRequest, "invalid request body")
		return
	}

	// Load user
	user, err := s.users.GetByUsername(sess.Username)
	if err != nil {
		s.logger.Error("change_password: user not found", "err", err, "username", sess.Username)
		writeError(w, http.StatusInternalServerError, "user not found")
		return
	}

	// LDAP users cannot change password here
	if user.Source == "ldap" {
		writeError(w, http.StatusBadRequest, "password is managed by LDAP")
		return
	}

	// Verify current password
	currentHash, err := s.users.GetPasswordHash(r.Context(), user.ID)
	if err != nil {
		s.logger.Error("change_password: get hash failed", "err", err)
		writeError(w, http.StatusInternalServerError, "internal error")
		return
	}

	if err := bcrypt.CompareHashAndPassword([]byte(currentHash), []byte(req.CurrentPassword)); err != nil {
		writeError(w, http.StatusForbidden, "current password is incorrect")
		return
	}

	// Validate new password length
	if len(req.NewPassword) < 8 {
		writeError(w, http.StatusBadRequest, "new password must be at least 8 characters")
		return
	}

	// Hash new password
	newHash, err := bcrypt.GenerateFromPassword([]byte(req.NewPassword), 12)
	if err != nil {
		s.logger.Error("change_password: bcrypt failed", "err", err)
		writeError(w, http.StatusInternalServerError, "internal error")
		return
	}

	// Persist
	if err := s.users.UpdatePassword(r.Context(), user.ID, string(newHash)); err != nil {
		s.logger.Error("change_password: update failed", "err", err)
		writeError(w, http.StatusInternalServerError, "failed to update password")
		return
	}

	s.audlog.Log(audit.Entry{
		EventType: audit.EventUserMgmt,
		Username:  sess.Username,
		TenantID:  sess.TenantID,
		IPAddress: s.remoteIP(r),
		Success:   true,
		Detail:    "change_password",
	})

	writeJSON(w, http.StatusOK, map[string]bool{"ok": true})
}

// handleChangeEmail allows a local user to change their own email address.
// PATCH /api/auth/email
func (s *Server) handleChangeEmail(w http.ResponseWriter, r *http.Request) {
	sess := sessionFromCtx(r.Context())
	if sess.UserID == 0 {
		writeError(w, http.StatusUnauthorized, "not authenticated")
		return
	}

	var req struct {
		Email string `json:"email"`
	}
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		writeError(w, http.StatusBadRequest, "invalid request body")
		return
	}

	// Load user
	user, err := s.users.GetByUsername(sess.Username)
	if err != nil {
		s.logger.Error("change_email: user not found", "err", err, "username", sess.Username)
		writeError(w, http.StatusInternalServerError, "user not found")
		return
	}

	// LDAP users cannot change email here
	if user.Source == "ldap" {
		writeError(w, http.StatusBadRequest, "email is managed by LDAP")
		return
	}

	// Basic email validation
	if !strings.Contains(req.Email, "@") || !strings.Contains(req.Email, ".") {
		writeError(w, http.StatusBadRequest, "invalid email address")
		return
	}

	// Persist
	if err := s.users.UpdateEmail(r.Context(), user.ID, req.Email); err != nil {
		// Check for unique constraint violation (duplicate email)
		var pgErr *pgconn.PgError
		if errors.As(err, &pgErr) && pgErr.Code == "23505" {
			writeError(w, http.StatusConflict, "email already in use")
			return
		}
		s.logger.Error("change_email: update failed", "err", err)
		writeError(w, http.StatusInternalServerError, "failed to update email")
		return
	}

	s.audlog.Log(audit.Entry{
		EventType: audit.EventUserMgmt,
		Username:  sess.Username,
		TenantID:  sess.TenantID,
		IPAddress: s.remoteIP(r),
		Success:   true,
		Detail:    "change_email",
	})

	writeJSON(w, http.StatusOK, map[string]interface{}{"ok": true, "email": req.Email})
}

// allowedListPageSizes are the valid values for users.list_page_size (PROJ-53).
var allowedListPageSizes = map[int]bool{25: true, 50: true, 100: true, 200: true}

// handleChangePreferences allows a user to change their own UI preferences,
// currently just the number of list entries shown per page.
// PATCH /api/auth/preferences
func (s *Server) handleChangePreferences(w http.ResponseWriter, r *http.Request) {
	sess := sessionFromCtx(r.Context())
	if sess.UserID == 0 {
		writeError(w, http.StatusUnauthorized, "not authenticated")
		return
	}

	var req struct {
		ListPageSize int `json:"list_page_size"`
	}
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		writeError(w, http.StatusBadRequest, "invalid request body")
		return
	}

	if !allowedListPageSizes[req.ListPageSize] {
		writeError(w, http.StatusBadRequest, "invalid list_page_size: must be 25, 50, 100 or 200")
		return
	}

	user, err := s.users.GetByUsername(sess.Username)
	if err != nil {
		s.logger.Error("change_preferences: user not found", "err", err, "username", sess.Username)
		writeError(w, http.StatusInternalServerError, "user not found")
		return
	}

	if err := s.users.UpdateListPageSize(r.Context(), user.ID, req.ListPageSize); err != nil {
		s.logger.Error("change_preferences: update failed", "err", err)
		writeError(w, http.StatusInternalServerError, "failed to update preferences")
		return
	}

	writeJSON(w, http.StatusOK, map[string]interface{}{"ok": true, "list_page_size": req.ListPageSize})
}
