package services

import (
	"errors"
	"strconv"
	"time"

	configs "goFiber/config"
	"log"

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

const (
	TokenTypeAccess  = "access"
	TokenTypeRefresh = "refresh"
)

type JWTClaim struct {
	UserID    uint   `json:"user_id"`
	Email     string `json:"email"`
	IsAdmin   bool   `json:"is_admin"`
	FirstName string `json:"first_name"`
	LastName  string `json:"last_name"`
	TokenType string `json:"token_type"`
	jwt.RegisteredClaims
}

type JWTService struct{}

func NewJWTService() *JWTService {
	return &JWTService{}
}

func (s *JWTService) GenerateToken(
	userID uint,
	email string,
	isAdmin bool,
	firstName string,
	lastName string,
	tokenType string,
	expiration time.Duration,
) (string, error) {
	now := time.Now()
	claims := &JWTClaim{
		UserID:    userID,
		Email:     email,
		IsAdmin:   isAdmin,
		FirstName: firstName,
		LastName:  lastName,
		TokenType: tokenType,
		RegisteredClaims: jwt.RegisteredClaims{
			Subject:   strconv.FormatUint(uint64(userID), 10),
			ExpiresAt: jwt.NewNumericDate(now.Add(expiration)),
			IssuedAt:  jwt.NewNumericDate(now),
		},
	}

	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	return token.SignedString([]byte(configs.AppConfig.JWTSecret))
}

func (s *JWTService) GenerateTokenPair(
	userID uint,
	email string,
	isAdmin bool,
	firstName string,
	lastName string,
) (string, string, error) {
	access, err := s.GenerateToken(
		userID,
		email,
		isAdmin,
		firstName,
		lastName,
		TokenTypeAccess,
		time.Duration(configs.AppConfig.AccessTokenExpireMinutes)*time.Minute,
	)
	if err != nil {
		return "", "", err
	}

	refresh, err := s.GenerateToken(
		userID,
		email,
		isAdmin,
		firstName,
		lastName,
		TokenTypeRefresh,
		time.Duration(configs.AppConfig.RefreshTokenExpireDays)*24*time.Hour,
	)
	if err != nil {
		return "", "", err
	}

	// Log generated tokens (access + refresh)
	log.Printf("Generated token pair for user=%d email=%s access_exp=%dm refresh_exp=%dd", userID, email, configs.AppConfig.AccessTokenExpireMinutes, configs.AppConfig.RefreshTokenExpireDays)
	log.Printf("access: %s", access)
	log.Printf("refresh: %s", refresh)

	return access, refresh, nil
}

func (s *JWTService) ValidateToken(signedToken string) (*JWTClaim, error) {
	token, err := jwt.ParseWithClaims(signedToken, &JWTClaim{}, func(token *jwt.Token) (interface{}, error) {
		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
			return nil, errors.New("unexpected signing method")
		}
		return []byte(configs.AppConfig.JWTSecret), nil
	})
	if err != nil {
		return nil, err
	}

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

	return claims, nil
}
