package handlers

// settings.go — User profile, preferences, and password management.
//
// v0.29.0: Raw SQL replaced with UserStore methods.

import (
	"context"
	"encoding/json"
	"log"
	"net/http"
	"strings"
	"time"

	"github.com/gin-gonic/gin"
	"golang.org/x/crypto/bcrypt"

	"switchboard-core/crypto"
	"switchboard-core/store"
)

// ── Request Types ───────────────────────────

type updateProfileRequest struct {
	DisplayName *string `json:"display_name,omitempty"`
	Email       *string `json:"email,omitempty"`
}

type changePasswordRequest struct {
	CurrentPassword string `json:"current_password" binding:"required"`
	NewPassword     string `json:"new_password" binding:"required,min=8,max=128"`
}

type profileResponse struct {
	ID          string                 `json:"id"`
	Username    string                 `json:"username"`
	Email       string                 `json:"email"`
	DisplayName *string                `json:"display_name"`
	Avatar      *string                `json:"avatar,omitempty"`
	Settings    map[string]interface{} `json:"settings"`
	CreatedAt   time.Time              `json:"created_at"`
	LastLoginAt *time.Time             `json:"last_login_at,omitempty"`
}

// SettingsHandler manages user profile and preferences.
type SettingsHandler struct {
	stores   store.Stores
	uekCache *crypto.UEKCache
}

// NewSettingsHandler creates a new handler.
func NewSettingsHandler(stores store.Stores, uekCache *crypto.UEKCache) *SettingsHandler {
	return &SettingsHandler{stores: stores, uekCache: uekCache}
}

// ── Get Profile ─────────────────────────────

func (h *SettingsHandler) GetProfile(c *gin.Context) {
	userID := getUserID(c)

	user, err := h.stores.Users.GetByID(c.Request.Context(), userID)
	if err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "user not found"})
		return
	}

	var dn *string
	if user.DisplayName != "" {
		dn = &user.DisplayName
	}
	var avatar *string
	if user.AvatarURL != "" {
		avatar = &user.AvatarURL
	}

	settings := make(map[string]interface{})
	if user.Settings != nil {
		settings = user.Settings
	}

	c.JSON(http.StatusOK, profileResponse{
		ID:          user.ID,
		Username:    user.Username,
		Email:       user.Email,
		DisplayName: dn,
		Avatar:      avatar,
		Settings:    settings,
		CreatedAt:   user.CreatedAt,
		LastLoginAt: user.LastLoginAt,
	})
}

// ── Update Profile ──────────────────────────

func (h *SettingsHandler) UpdateProfile(c *gin.Context) {
	userID := getUserID(c)

	var req updateProfileRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	if req.DisplayName != nil {
		if err := h.stores.Users.Update(c.Request.Context(), userID, map[string]interface{}{
			"display_name": *req.DisplayName,
		}); err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to update display name"})
			return
		}
	}

	if req.Email != nil {
		email := strings.ToLower(strings.TrimSpace(*req.Email))
		err := h.stores.Users.Update(c.Request.Context(), userID, map[string]interface{}{
			"email": email,
		})
		if err != nil {
			if isDuplicateErr(err) {
				c.JSON(http.StatusConflict, gin.H{"error": "email already taken"})
				return
			}
			c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to update email"})
			return
		}
	}

	h.GetProfile(c)
}

// ── Change Password ─────────────────────────

func (h *SettingsHandler) ChangePassword(c *gin.Context) {
	userID := getUserID(c)

	var req changePasswordRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	user, err := h.stores.Users.GetByID(c.Request.Context(), userID)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to verify password"})
		return
	}

	if err := bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(req.CurrentPassword)); err != nil {
		c.JSON(http.StatusUnauthorized, gin.H{"error": "current password is incorrect"})
		return
	}

	newHash, err := bcrypt.GenerateFromPassword([]byte(req.NewPassword), bcryptCost)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to hash password"})
		return
	}

	if err := h.stores.Users.Update(c.Request.Context(), userID, map[string]interface{}{
		"password_hash": string(newHash),
	}); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to update password"})
		return
	}

	h.rewrapVault(userID, req.CurrentPassword, req.NewPassword)
	c.JSON(http.StatusOK, gin.H{"message": "password updated"})
}

// ── Get User Settings (JSONB) ───────────────

func (h *SettingsHandler) GetSettings(c *gin.Context) {
	userID := getUserID(c)

	user, err := h.stores.Users.GetByID(c.Request.Context(), userID)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to load settings"})
		return
	}

	settings := make(map[string]interface{})
	if user.Settings != nil {
		settings = user.Settings
	}

	c.JSON(http.StatusOK, gin.H{"settings": settings})
}

// ── Update User Settings (JSONB merge) ──────

func (h *SettingsHandler) UpdateSettings(c *gin.Context) {
	userID := getUserID(c)

	var incoming map[string]interface{}
	if err := c.ShouldBindJSON(&incoming); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	patch, err := json.Marshal(incoming)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid settings"})
		return
	}

	if err := h.stores.Users.MergeSettings(c.Request.Context(), userID, patch); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to update settings"})
		return
	}

	h.GetSettings(c)
}

// ── Vault Re-wrap ──────────────────────────

func (h *SettingsHandler) rewrapVault(userID, oldPassword, newPassword string) {
	if h.uekCache == nil {
		return
	}

	vaultSet, encUEK, salt, nonce, err := h.stores.Users.GetVaultKeys(c_bg(), userID)
	if err != nil || !vaultSet || len(encUEK) == 0 {
		return
	}

	oldPDK := crypto.DeriveKeyFromPassword(oldPassword, salt)
	uek, err := crypto.UnwrapUEK(encUEK, nonce, oldPDK)
	if err != nil {
		log.Printf("⚠ Vault re-wrap failed for user %s (unwrap): %v", userID, err)
		return
	}

	newSalt, err := crypto.GenerateSalt()
	if err != nil {
		log.Printf("⚠ Vault re-wrap failed for user %s (salt): %v", userID, err)
		return
	}

	newPDK := crypto.DeriveKeyFromPassword(newPassword, newSalt)
	newEncUEK, newNonce, err := crypto.WrapUEK(uek, newPDK)
	if err != nil {
		log.Printf("⚠ Vault re-wrap failed for user %s (wrap): %v", userID, err)
		return
	}

	if err := h.stores.Users.UpdateVaultKeys(c_bg(), userID, newEncUEK, newSalt, newNonce); err != nil {
		log.Printf("⚠ Vault re-wrap failed for user %s (persist): %v", userID, err)
		return
	}

	h.uekCache.Store(userID, uek)
	log.Printf("  🔐 Vault re-wrapped for user %s", userID)
}

// c_bg returns context.Background() — short alias for vault operations
// that run outside the request lifecycle.
func c_bg() context.Context { return context.Background() }
