package auth

import (
	"errors"
	"fmt"
	"strings"
	"tercul/internal/domain"
	"time"

	"github.com/golang-jwt/jwt/v5"
	"tercul/internal/platform/config"
)

var (
	ErrInvalidToken     = errors.New("invalid token")
	ErrExpiredToken     = errors.New("token expired")
	ErrInvalidSignature = errors.New("invalid token signature")
	ErrMissingToken     = errors.New("missing token")
	ErrInsufficientRole = errors.New("insufficient role")
)

// Claims represents the JWT claims
type Claims struct {
	UserID   uint   `json:"user_id"`
	Username string `json:"username"`
	Email    string `json:"email"`
	Role     string `json:"role"`
	jwt.RegisteredClaims
}

// JWTManager handles JWT token operations
type JWTManagement interface {
	GenerateToken(user *domain.User) (string, error)
	ValidateToken(tokenString string) (*Claims, error)
}

type JWTManager struct {
	secretKey []byte
	issuer    string
	duration  time.Duration
}

// NewJWTManager creates a new JWT manager
func NewJWTManager(cfg *config.Config) *JWTManager {
	secretKey := cfg.JWTSecret
	if secretKey == "" {
		secretKey = "default-secret-key-change-in-production"
	}

	durationInHours := cfg.JWTExpiration
	if durationInHours <= 0 {
		durationInHours = 24 // Default to 24 hours
	}
	duration := time.Duration(durationInHours) * time.Hour

	return &JWTManager{
		secretKey: []byte(secretKey),
		issuer:    "tercul-api",
		duration:  duration,
	}
}

// GenerateToken generates a new JWT token for a user
func (j *JWTManager) GenerateToken(user *domain.User) (string, error) {
	now := time.Now()
	claims := &Claims{
		UserID:   user.ID,
		Username: user.Username,
		Email:    user.Email,
		Role:     string(user.Role),
		RegisteredClaims: jwt.RegisteredClaims{
			ExpiresAt: jwt.NewNumericDate(now.Add(j.duration)),
			IssuedAt:  jwt.NewNumericDate(now),
			NotBefore: jwt.NewNumericDate(now),
			Issuer:    j.issuer,
			Subject:   fmt.Sprintf("%d", user.ID),
		},
	}

	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	return token.SignedString(j.secretKey)
}

// ValidateToken validates and parses a JWT token
func (j *JWTManager) ValidateToken(tokenString string) (*Claims, error) {
	token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {
		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
			return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
		}
		return j.secretKey, nil
	})

	if err != nil {
		if errors.Is(err, jwt.ErrTokenExpired) {
			return nil, ErrExpiredToken
		}
		return nil, ErrInvalidToken
	}

	claims, ok := token.Claims.(*Claims)
	if !ok || !token.Valid {
		return nil, ErrInvalidToken
	}

	return claims, nil
}

// ExtractTokenFromHeader extracts token from Authorization header
func (j *JWTManager) ExtractTokenFromHeader(authHeader string) (string, error) {
	if authHeader == "" {
		return "", ErrMissingToken
	}

	parts := strings.Split(authHeader, " ")
	if len(parts) != 2 || parts[0] != "Bearer" {
		return "", ErrInvalidToken
	}

	return parts[1], nil
}

// HasRole checks if the user has the required role
func (j *JWTManager) HasRole(userRole, requiredRole string) bool {
	roleHierarchy := map[string]int{
		"reader":      1,
		"contributor": 2,
		"moderator":   3,
		"admin":       4,
	}

	userLevel, userExists := roleHierarchy[userRole]
	requiredLevel, requiredExists := roleHierarchy[requiredRole]

	if !userExists || !requiredExists {
		return false
	}

	return userLevel >= requiredLevel
}

// RequireRole validates that the user has the required role
func (j *JWTManager) RequireRole(userRole, requiredRole string) error {
	if !j.HasRole(userRole, requiredRole) {
		return ErrInsufficientRole
	}
	return nil
}
