// Handlers d'authentification : login PAM, logout, refresh token, profil utilisateur.
package api

import (
	"crypto/sha256"
	"database/sql"
	"encoding/hex"
	"log"
	"net/http"
	"time"

	"git.geronzi.fr/proxmoxPanel/core/backend/internal/audit"
	"git.geronzi.fr/proxmoxPanel/core/backend/internal/auth"
	"git.geronzi.fr/proxmoxPanel/core/backend/internal/db"
)

// AuthHandler contient les handlers d'authentification.
type AuthHandler struct {
	db          *db.DB
	jwtManager  *auth.JWTManager
	sshAuth     *auth.SSHAuthenticator
	auditLogger *audit.Logger
	authLimiter *RateLimiter
}

// NewAuthHandler crée un AuthHandler.
func NewAuthHandler(database *db.DB, jwtMgr *auth.JWTManager, sshAuth *auth.SSHAuthenticator, auditLog *audit.Logger) *AuthHandler {
	return &AuthHandler{
		db:          database,
		jwtManager:  jwtMgr,
		sshAuth:     sshAuth,
		auditLogger: auditLog,
		authLimiter: NewRateLimiter(5, time.Minute), // 5 tentatives par minute par IP
	}
}

// Login authentifie un utilisateur via ses credentials Linux (PAM via SSH).
// POST /api/auth/login
// Body: { "username": "enzo", "password": "..." }
func (h *AuthHandler) Login(w http.ResponseWriter, r *http.Request) {
	ip := clientIP(r)

	// Rate limiting sur le login
	if !h.authLimiter.Allow(ip) {
		h.auditLogger.Log(nil, "?", "login_rate_limited", "", nil, ip)
		JSONError(w, "Trop de tentatives de connexion, veuillez patienter", http.StatusTooManyRequests)
		return
	}

	var body struct {
		Username string `json:"username"`
		Password string `json:"password"`
	}
	if err := decodeJSON(r, &body); err != nil {
		JSONError(w, "Corps de requête invalide", http.StatusBadRequest)
		return
	}

	if body.Username == "" || body.Password == "" {
		JSONError(w, "Nom d'utilisateur et mot de passe requis", http.StatusBadRequest)
		return
	}

	// Lire le host SSH depuis la DB à chaque login.
	// L'authenticator global est créé au démarrage avec host="" (avant installation)
	// et ne se met pas à jour automatiquement après configuration.
	sshHost, _, _ := h.db.GetSetting("ssh_host")
	if sshHost == "" {
		log.Printf("[auth/login] SSH non configuré (ssh_host vide en base) — user=%s ip=%s", body.Username, ip)
		JSONError(w, "SSH non configuré, veuillez vérifier l'installation", http.StatusServiceUnavailable)
		return
	}

	log.Printf("[auth/login] Tentative — user=%s ip=%s ssh_host=%s", body.Username, ip, sshHost)
	authenticator := auth.NewSSHAuthenticator(sshHost)

	userInfo, err := authenticator.Authenticate(body.Username, body.Password)
	if err != nil {
		log.Printf("[auth/login] Échec auth SSH — user=%s ssh_host=%s erreur=%v", body.Username, sshHost, err)
		h.auditLogger.Log(nil, body.Username, "login_failed", "", map[string]string{"error": err.Error()}, ip)
		JSONError(w, "Identifiants invalides", http.StatusUnauthorized)
		return
	}
	log.Printf("[auth/login] Succès — user=%s admin=%v ssh_host=%s", body.Username, userInfo.IsAdmin, sshHost)

	// Créer ou mettre à jour le profil utilisateur en SQLite
	userID, err := h.upsertUser(userInfo)
	if err != nil {
		JSONError(w, "Erreur création profil utilisateur", http.StatusInternalServerError)
		return
	}

	// Générer les tokens JWT
	accessToken, err := h.jwtManager.GenerateAccessToken(userID, userInfo.Username, userInfo.IsAdmin)
	if err != nil {
		JSONError(w, "Erreur génération token", http.StatusInternalServerError)
		return
	}

	refreshToken, err := h.jwtManager.GenerateRefreshToken(userID)
	if err != nil {
		JSONError(w, "Erreur génération refresh token", http.StatusInternalServerError)
		return
	}

	// Stocker le hash du refresh token en base pour permettre la révocation
	tokenHash := hashToken(refreshToken)
	expiry := time.Now().Add(auth.RefreshTokenDuration())
	h.db.Exec(`
		INSERT INTO refresh_tokens (user_id, token_hash, expires_at) VALUES (?, ?, ?)
	`, userID, tokenHash, expiry)

	// Mettre à jour la date de dernier login
	h.db.Exec(`UPDATE users SET last_login_at = CURRENT_TIMESTAMP WHERE id = ?`, userID)

	// Cookie httpOnly pour le refresh token
	http.SetCookie(w, &http.Cookie{
		Name:     "pxp_refresh",
		Value:    refreshToken,
		Path:     "/api/auth/refresh",
		HttpOnly: true,
		Secure:   r.TLS != nil,
		SameSite: http.SameSiteStrictMode,
		Expires:  expiry,
	})

	h.auditLogger.Log(&userID, userInfo.Username, "login_success", "", nil, ip)

	JSONResponse(w, http.StatusOK, map[string]any{
		"access_token": accessToken,
		"expires_in":   900, // 15 minutes en secondes
		"user": map[string]any{
			"id":       userID,
			"username": userInfo.Username,
			"is_admin": userInfo.IsAdmin,
		},
	})
}

// Logout invalide la session de l'utilisateur.
// POST /api/auth/logout
func (h *AuthHandler) Logout(w http.ResponseWriter, r *http.Request) {
	claims := GetClaims(r)

	// Supprimer tous les refresh tokens de cet utilisateur
	if claims != nil {
		h.db.Exec(`DELETE FROM refresh_tokens WHERE user_id = ?`, claims.UserID)
		h.auditLogger.Log(&claims.UserID, claims.Username, "logout", "", nil, clientIP(r))
	}

	// Effacer le cookie de refresh
	http.SetCookie(w, &http.Cookie{
		Name:     "pxp_refresh",
		Value:    "",
		Path:     "/api/auth/refresh",
		HttpOnly: true,
		Expires:  time.Unix(0, 0),
		MaxAge:   -1,
	})

	JSONResponse(w, http.StatusOK, map[string]string{"message": "Déconnexion réussie"})
}

// Refresh renouvelle l'access token via le refresh token (cookie httpOnly).
// POST /api/auth/refresh
func (h *AuthHandler) Refresh(w http.ResponseWriter, r *http.Request) {
	cookie, err := r.Cookie("pxp_refresh")
	if err != nil {
		JSONError(w, "Refresh token manquant", http.StatusUnauthorized)
		return
	}

	userID, err := h.jwtManager.ValidateRefreshToken(cookie.Value)
	if err != nil {
		JSONError(w, "Refresh token invalide ou expiré", http.StatusUnauthorized)
		return
	}

	// Vérifier que le token est en base (non révoqué)
	tokenHash := hashToken(cookie.Value)
	var count int
	h.db.QueryRow(`SELECT COUNT(*) FROM refresh_tokens WHERE user_id = ? AND token_hash = ? AND expires_at > CURRENT_TIMESTAMP`, userID, tokenHash).Scan(&count)
	if count == 0 {
		JSONError(w, "Session expirée ou révoquée", http.StatusUnauthorized)
		return
	}

	// Récupérer les infos utilisateur
	var username string
	var isAdmin int
	err = h.db.QueryRow(`SELECT username, is_admin FROM users WHERE id = ?`, userID).Scan(&username, &isAdmin)
	if err != nil {
		JSONError(w, "Utilisateur introuvable", http.StatusUnauthorized)
		return
	}

	accessToken, err := h.jwtManager.GenerateAccessToken(userID, username, isAdmin == 1)
	if err != nil {
		JSONError(w, "Erreur génération token", http.StatusInternalServerError)
		return
	}

	JSONResponse(w, http.StatusOK, map[string]any{
		"access_token": accessToken,
		"expires_in":   900,
	})
}

// Me retourne le profil de l'utilisateur connecté.
// GET /api/auth/me
func (h *AuthHandler) Me(w http.ResponseWriter, r *http.Request) {
	claims := GetClaims(r)
	if claims == nil {
		JSONError(w, "Non authentifié", http.StatusUnauthorized)
		return
	}

	var lang, theme, sidebarPos string
	var lastLogin sql.NullTime
	err := h.db.QueryRow(`SELECT lang, theme, sidebar_position, last_login_at FROM users WHERE id = ?`, claims.UserID).
		Scan(&lang, &theme, &sidebarPos, &lastLogin)
	if err != nil {
		JSONError(w, "Profil introuvable", http.StatusNotFound)
		return
	}

	resp := map[string]any{
		"id":               claims.UserID,
		"username":         claims.Username,
		"is_admin":         claims.IsAdmin,
		"lang":             lang,
		"theme":            theme,
		"sidebar_position": sidebarPos,
	}
	if lastLogin.Valid {
		resp["last_login_at"] = lastLogin.Time
	}

	JSONResponse(w, http.StatusOK, resp)
}

// UpdatePreferences met à jour les préférences de l'utilisateur connecté.
// PATCH /api/auth/preferences
func (h *AuthHandler) UpdatePreferences(w http.ResponseWriter, r *http.Request) {
	claims := GetClaims(r)

	var body struct {
		Lang            *string `json:"lang"`
		Theme           *string `json:"theme"`
		SidebarPosition *string `json:"sidebar_position"`
	}
	if err := decodeJSON(r, &body); err != nil {
		JSONError(w, "Corps de requête invalide", http.StatusBadRequest)
		return
	}

	if body.Lang != nil {
		if !isValidLang(*body.Lang) {
			JSONError(w, "Langue non supportée", http.StatusBadRequest)
			return
		}
		h.db.Exec(`UPDATE users SET lang = ? WHERE id = ?`, *body.Lang, claims.UserID)
	}
	if body.Theme != nil {
		if *body.Theme != "dark" && *body.Theme != "light" {
			JSONError(w, "Thème invalide (dark ou light)", http.StatusBadRequest)
			return
		}
		h.db.Exec(`UPDATE users SET theme = ? WHERE id = ?`, *body.Theme, claims.UserID)
	}
	if body.SidebarPosition != nil {
		if *body.SidebarPosition != "left" && *body.SidebarPosition != "right" {
			JSONError(w, "Position sidebar invalide (left ou right)", http.StatusBadRequest)
			return
		}
		h.db.Exec(`UPDATE users SET sidebar_position = ? WHERE id = ?`, *body.SidebarPosition, claims.UserID)
	}

	JSONResponse(w, http.StatusOK, map[string]string{"message": "Préférences mises à jour"})
}

// upsertUser crée ou met à jour le profil utilisateur en SQLite.
// Retourne l'ID de l'utilisateur.
func (h *AuthHandler) upsertUser(info *auth.UserInfo) (int64, error) {
	isAdmin := 0
	if info.IsAdmin {
		isAdmin = 1
	}

	// Mise à jour du statut admin à chaque connexion (peut changer côté Linux)
	result, err := h.db.Exec(`
		INSERT INTO users (username, is_admin) VALUES (?, ?)
		ON CONFLICT(username) DO UPDATE SET is_admin = excluded.is_admin
	`, info.Username, isAdmin)
	if err != nil {
		return 0, err
	}

	// Tenter de récupérer l'ID (insertions ou update)
	id, err := result.LastInsertId()
	if err != nil || id == 0 {
		// En cas de ON CONFLICT DO UPDATE, LastInsertId peut retourner 0
		err = h.db.QueryRow(`SELECT id FROM users WHERE username = ?`, info.Username).Scan(&id)
	}
	return id, err
}

// hashToken crée un hash SHA-256 d'un token pour le stockage en base.
func hashToken(token string) string {
	h := sha256.Sum256([]byte(token))
	return hex.EncodeToString(h[:])
}
