// Package auth gère les tokens JWT RS256 pour les sessions utilisateurs.
// Les clés RSA sont générées automatiquement au premier démarrage et stockées sur disque.
package auth

import (
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"encoding/pem"
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"time"

	"github.com/golang-jwt/jwt/v5"
)

const (
	accessTokenDuration  = 15 * time.Minute
	refreshTokenDuration = 7 * 24 * time.Hour
	rsaKeySize           = 2048
)

// Claims représente le contenu d'un JWT d'accès ProxmoxPanel.
type Claims struct {
	UserID   int64  `json:"uid"`
	Username string `json:"sub"`
	IsAdmin  bool   `json:"admin"`
	jwt.RegisteredClaims
}

// JWTManager gère la signature et la vérification des tokens JWT.
type JWTManager struct {
	privateKey *rsa.PrivateKey
	publicKey  *rsa.PublicKey
}

// NewJWTManager charge ou génère les clés RSA, et retourne un JWTManager prêt à l'emploi.
func NewJWTManager(dataDir string) (*JWTManager, error) {
	keysDir := filepath.Join(dataDir, "keys")
	if err := os.MkdirAll(keysDir, 0700); err != nil {
		return nil, fmt.Errorf("création répertoire clés : %w", err)
	}

	privPath := filepath.Join(keysDir, "jwt.key")
	pubPath := filepath.Join(keysDir, "jwt.pub")

	var privKey *rsa.PrivateKey

	if _, err := os.Stat(privPath); os.IsNotExist(err) {
		// Générer une paire de clés RSA-2048
		privKey, err = rsa.GenerateKey(rand.Reader, rsaKeySize)
		if err != nil {
			return nil, fmt.Errorf("génération clés RSA : %w", err)
		}

		// Sauvegarder la clé privée en PEM
		privPEM := pem.EncodeToMemory(&pem.Block{
			Type:  "RSA PRIVATE KEY",
			Bytes: x509.MarshalPKCS1PrivateKey(privKey),
		})
		if err := os.WriteFile(privPath, privPEM, 0600); err != nil {
			return nil, fmt.Errorf("sauvegarde clé privée : %w", err)
		}

		// Sauvegarder la clé publique en PEM
		pubBytes, err := x509.MarshalPKIXPublicKey(&privKey.PublicKey)
		if err != nil {
			return nil, fmt.Errorf("export clé publique : %w", err)
		}
		pubPEM := pem.EncodeToMemory(&pem.Block{
			Type:  "PUBLIC KEY",
			Bytes: pubBytes,
		})
		if err := os.WriteFile(pubPath, pubPEM, 0644); err != nil {
			return nil, fmt.Errorf("sauvegarde clé publique : %w", err)
		}
	} else {
		// Charger la clé privée existante
		privPEM, err := os.ReadFile(privPath)
		if err != nil {
			return nil, fmt.Errorf("lecture clé privée : %w", err)
		}
		block, _ := pem.Decode(privPEM)
		if block == nil {
			return nil, errors.New("clé privée invalide (PEM)")
		}
		privKey, err = x509.ParsePKCS1PrivateKey(block.Bytes)
		if err != nil {
			return nil, fmt.Errorf("parsing clé privée : %w", err)
		}
	}

	return &JWTManager{
		privateKey: privKey,
		publicKey:  &privKey.PublicKey,
	}, nil
}

// GenerateAccessToken crée un JWT d'accès signé RS256 (durée : 15 min).
func (m *JWTManager) GenerateAccessToken(userID int64, username string, isAdmin bool) (string, error) {
	claims := Claims{
		UserID:   userID,
		Username: username,
		IsAdmin:  isAdmin,
		RegisteredClaims: jwt.RegisteredClaims{
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(accessTokenDuration)),
			IssuedAt:  jwt.NewNumericDate(time.Now()),
			Issuer:    "proxmoxpanel",
		},
	}
	token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
	return token.SignedString(m.privateKey)
}

// GenerateRefreshToken crée un token de renouvellement (durée : 7 jours).
// Ce token est plus simple — il ne contient que le userID et l'expiration.
func (m *JWTManager) GenerateRefreshToken(userID int64) (string, error) {
	claims := jwt.RegisteredClaims{
		Subject:   fmt.Sprintf("%d", userID),
		ExpiresAt: jwt.NewNumericDate(time.Now().Add(refreshTokenDuration)),
		IssuedAt:  jwt.NewNumericDate(time.Now()),
		Issuer:    "proxmoxpanel-refresh",
	}
	token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
	return token.SignedString(m.privateKey)
}

// ValidateAccessToken vérifie et décode un JWT d'accès.
func (m *JWTManager) ValidateAccessToken(tokenStr string) (*Claims, error) {
	token, err := jwt.ParseWithClaims(tokenStr, &Claims{}, func(t *jwt.Token) (any, error) {
		if _, ok := t.Method.(*jwt.SigningMethodRSA); !ok {
			return nil, fmt.Errorf("méthode de signature inattendue : %v", t.Header["alg"])
		}
		return m.publicKey, nil
	})
	if err != nil {
		return nil, fmt.Errorf("validation token : %w", err)
	}

	claims, ok := token.Claims.(*Claims)
	if !ok || !token.Valid {
		return nil, errors.New("token invalide")
	}
	return claims, nil
}

// ValidateRefreshToken vérifie un token de renouvellement et retourne le userID.
func (m *JWTManager) ValidateRefreshToken(tokenStr string) (int64, error) {
	token, err := jwt.ParseWithClaims(tokenStr, &jwt.RegisteredClaims{}, func(t *jwt.Token) (any, error) {
		if _, ok := t.Method.(*jwt.SigningMethodRSA); !ok {
			return nil, fmt.Errorf("méthode de signature inattendue : %v", t.Header["alg"])
		}
		return m.publicKey, nil
	})
	if err != nil {
		return 0, fmt.Errorf("validation refresh token : %w", err)
	}

	claims, ok := token.Claims.(*jwt.RegisteredClaims)
	if !ok || !token.Valid {
		return 0, errors.New("refresh token invalide")
	}

	if claims.Issuer != "proxmoxpanel-refresh" {
		return 0, errors.New("émetteur token invalide")
	}

	var userID int64
	fmt.Sscanf(claims.Subject, "%d", &userID)
	return userID, nil
}

// RefreshTokenDuration retourne la durée de validité du refresh token.
func RefreshTokenDuration() time.Duration {
	return refreshTokenDuration
}
