// Package auth gère l'authentification JWT et les mots de passe.
package auth

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"strings"
	"time"
)

var (
	ErrInvalidToken = errors.New("invalid token")
	ErrExpiredToken = errors.New("token expired")
)

// Claims représente les claims du JWT.
type Claims struct {
	Sub   int64  `json:"sub"`            // User ID
	Email string `json:"email"`          // Email
	Name  string `json:"name"`           // Nom
	App   string `json:"app"`            // Application ID
	Exp   int64  `json:"exp"`            // Expiration (Unix timestamp)
	Iat   int64  `json:"iat"`            // Issued at
}

// JWT gère la génération et validation des tokens.
type JWT struct {
	secret     []byte
	expiration time.Duration
}

// NewJWT crée un nouveau gestionnaire JWT.
func NewJWT(secret string, expiration time.Duration) *JWT {
	return &JWT{
		secret:     []byte(secret),
		expiration: expiration,
	}
}

// Secret retourne le secret utilisé (pour comparaison lors du reload).
func (j *JWT) Secret() string {
	return string(j.secret)
}

// Generate génère un nouveau token JWT.
func (j *JWT) Generate(userID int64, email, name, appID string) (string, error) {
	now := time.Now()

	claims := Claims{
		Sub:   userID,
		Email: email,
		Name:  name,
		App:   appID,
		Iat:   now.Unix(),
		Exp:   now.Add(j.expiration).Unix(),
	}

	return j.encode(claims)
}

// Validate valide un token et retourne les claims.
func (j *JWT) Validate(token string) (*Claims, error) {
	parts := strings.Split(token, ".")
	if len(parts) != 3 {
		return nil, ErrInvalidToken
	}

	// Vérifier la signature
	signatureInput := parts[0] + "." + parts[1]
	expectedSig := j.sign(signatureInput)
	if parts[2] != expectedSig {
		return nil, ErrInvalidToken
	}

	// Décoder les claims
	claimsJSON, err := base64.RawURLEncoding.DecodeString(parts[1])
	if err != nil {
		return nil, ErrInvalidToken
	}

	var claims Claims
	if err := json.Unmarshal(claimsJSON, &claims); err != nil {
		return nil, ErrInvalidToken
	}

	// Vérifier l'expiration
	if time.Now().Unix() > claims.Exp {
		return nil, ErrExpiredToken
	}

	return &claims, nil
}

// encode encode les claims en JWT.
func (j *JWT) encode(claims Claims) (string, error) {
	// Header
	header := map[string]string{
		"alg": "HS256",
		"typ": "JWT",
	}
	headerJSON, _ := json.Marshal(header)
	headerB64 := base64.RawURLEncoding.EncodeToString(headerJSON)

	// Payload
	claimsJSON, err := json.Marshal(claims)
	if err != nil {
		return "", err
	}
	claimsB64 := base64.RawURLEncoding.EncodeToString(claimsJSON)

	// Signature
	signatureInput := headerB64 + "." + claimsB64
	signature := j.sign(signatureInput)

	return signatureInput + "." + signature, nil
}

// sign signe les données avec HMAC-SHA256.
func (j *JWT) sign(data string) string {
	h := hmac.New(sha256.New, j.secret)
	h.Write([]byte(data))
	return base64.RawURLEncoding.EncodeToString(h.Sum(nil))
}

// ExtractToken extrait le token du header Authorization.
func ExtractToken(authHeader string) (string, error) {
	if authHeader == "" {
		return "", fmt.Errorf("missing authorization header")
	}

	parts := strings.SplitN(authHeader, " ", 2)
	if len(parts) != 2 || strings.ToLower(parts[0]) != "bearer" {
		return "", fmt.Errorf("invalid authorization header format")
	}

	return parts[1], nil
}
