package auth

import (
	"errors"
	"time"

	"github.com/golang-jwt/jwt/v5"
	"gogs.dmsc.dev/arp/models"
	"golang.org/x/crypto/bcrypt"
)

// Token expiration: 10 years
const TokenExpiration = 10 * 365 * 24 * time.Hour

// JWT secret key - in production this should be loaded from environment
var jwtSecret = []byte("your-secret-key-change-in-production")

// Claims represents the JWT claims
type Claims struct {
	UserID      uint        `json:"user_id"`
	Email       string      `json:"email"`
	Roles       []RoleClaim `json:"roles"`
	Permissions []string    `json:"permissions"`
	jwt.RegisteredClaims
}

// RoleClaim represents a role in the JWT
type RoleClaim struct {
	ID   uint   `json:"id"`
	Name string `json:"name"`
}

// HashPassword hashes a password using bcrypt
func HashPassword(password string) (string, error) {
	bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
	return string(bytes), err
}

// CheckPassword verifies a password against a hash
func CheckPassword(password, hash string) bool {
	err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
	return err == nil
}

// GenerateToken creates a JWT token for a user
func GenerateToken(user models.User) (string, error) {
	// Build role claims
	roles := make([]RoleClaim, len(user.Roles))
	for i, role := range user.Roles {
		roles[i] = RoleClaim{
			ID:   role.ID,
			Name: role.Name,
		}
	}

	// Build permission codes (deduplicated)
	permSet := make(map[string]bool)
	for _, role := range user.Roles {
		for _, perm := range role.Permissions {
			permSet[perm.Code] = true
		}
	}
	permissions := make([]string, 0, len(permSet))
	for perm := range permSet {
		permissions = append(permissions, perm)
	}

	claims := Claims{
		UserID:      user.ID,
		Email:       user.Email,
		Roles:       roles,
		Permissions: permissions,
		RegisteredClaims: jwt.RegisteredClaims{
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(TokenExpiration)),
			IssuedAt:  jwt.NewNumericDate(time.Now()),
		},
	}

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

// ValidateToken validates a JWT token and returns the claims
func 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, errors.New("unexpected signing method")
		}
		return jwtSecret, nil
	})

	if err != nil {
		return nil, err
	}

	if claims, ok := token.Claims.(*Claims); ok && token.Valid {
		return claims, nil
	}

	return nil, errors.New("invalid token")
}

// SetJWTSecret sets the JWT secret key (for testing or configuration)
func SetJWTSecret(secret []byte) {
	jwtSecret = secret
}
