package usecase

import (
	"crypto/rand"
	"encoding/hex"
	"fmt"
	"time"

	"apps/backend/internal/module/token/domain"

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

// JWTIssuer implements domain.Issuer (stand-alone token/usecase JWT spirit; HS256 dual secret).
type JWTIssuer struct {
	AccessSecret  []byte
	RefreshSecret []byte
	AccessTTL     time.Duration
	RefreshTTL    time.Duration
}

func NewJWTIssuer(accessSecret, refreshSecret string, accessExpireSec, refreshExpireSec int64) *JWTIssuer {
	if accessExpireSec <= 0 {
		accessExpireSec = 86400
	}
	if refreshExpireSec <= 0 {
		refreshExpireSec = 604800
	}
	return &JWTIssuer{
		AccessSecret:  []byte(accessSecret),
		RefreshSecret: []byte(refreshSecret),
		AccessTTL:     time.Duration(accessExpireSec) * time.Second,
		RefreshTTL:    time.Duration(refreshExpireSec) * time.Second,
	}
}

func newJTI() string {
	var b [16]byte
	_, _ = rand.Read(b[:])
	return hex.EncodeToString(b[:])
}

func (i *JWTIssuer) Issue(uid int64, roles []string) (*domain.TokenPair, string, time.Time, error) {
	now := time.Now()
	accessExp := now.Add(i.AccessTTL)
	refreshExp := now.Add(i.RefreshTTL)
	refreshJTI := newJTI()

	access := jwt.NewWithClaims(jwt.SigningMethodHS256, domain.Claims{
		UID: uid, Roles: roles, Kind: "access",
		RegisteredClaims: jwt.RegisteredClaims{
			ExpiresAt: jwt.NewNumericDate(accessExp),
			IssuedAt:  jwt.NewNumericDate(now),
			Subject:   fmt.Sprintf("%d", uid),
		},
	})
	accessStr, err := access.SignedString(i.AccessSecret)
	if err != nil {
		return nil, "", time.Time{}, err
	}

	refresh := jwt.NewWithClaims(jwt.SigningMethodHS256, domain.Claims{
		UID: uid, Roles: roles, JTI: refreshJTI, Kind: "refresh",
		RegisteredClaims: jwt.RegisteredClaims{
			ExpiresAt: jwt.NewNumericDate(refreshExp),
			IssuedAt:  jwt.NewNumericDate(now),
			Subject:   fmt.Sprintf("%d", uid),
			ID:        refreshJTI,
		},
	})
	refreshStr, err := refresh.SignedString(i.RefreshSecret)
	if err != nil {
		return nil, "", time.Time{}, err
	}

	return &domain.TokenPair{
		AccessToken:  accessStr,
		RefreshToken: refreshStr,
		TokenType:    "Bearer",
		ExpiresIn:    int64(i.AccessTTL.Seconds()),
	}, refreshJTI, refreshExp, nil
}

func (i *JWTIssuer) ParseAccess(tokenStr string) (*domain.Claims, error) {
	return i.parse(tokenStr, i.AccessSecret, "access")
}

func (i *JWTIssuer) ParseRefresh(tokenStr string) (*domain.Claims, error) {
	return i.parse(tokenStr, i.RefreshSecret, "refresh")
}

func (i *JWTIssuer) parse(tokenStr string, secret []byte, kind string) (*domain.Claims, error) {
	tok, err := jwt.ParseWithClaims(tokenStr, &domain.Claims{}, func(t *jwt.Token) (interface{}, error) {
		if t.Method != jwt.SigningMethodHS256 {
			return nil, fmt.Errorf("unexpected signing method")
		}
		return secret, nil
	})
	if err != nil {
		return nil, domain.ErrInvalidToken
	}
	claims, ok := tok.Claims.(*domain.Claims)
	if !ok || !tok.Valid || claims.Kind != kind {
		return nil, domain.ErrInvalidToken
	}
	return claims, nil
}
