package auth

import (
	"errors"
	"sync"
	"time"

	"github.com/golang-jwt/jwt/v5"
	"github.com/wormhole/wormhole/internal/config"
	"github.com/wormhole/wormhole/internal/store"
)

type Claims struct {
	UserID   uint   `json:"user_id"`
	Username string `json:"username"`
	Role     string `json:"role"`
	jwt.RegisteredClaims
}

type Service struct {
	mu      sync.RWMutex
	cfg     *config.AuthConfig
	limiter *LoginLimiter
}

func NewService(cfg *config.AuthConfig) *Service {
	return &Service{
		cfg:     cfg,
		limiter: NewLoginLimiter(cfg),
	}
}

func (s *Service) ReloadAuth(cfg *config.AuthConfig) {
	s.mu.Lock()
	defer s.mu.Unlock()
	*s.cfg = *cfg
	s.limiter = NewLoginLimiter(cfg)
}

func (s *Service) Login(st *store.Store, ip, username, password string) (string, *store.User, error) {
	s.mu.RLock()
	limiter := s.limiter
	s.mu.RUnlock()
	if retry, blocked := limiter.IsBlocked(ip, username); blocked {
		return "", nil, &LoginBlockedError{RetryAfter: retry}
	}

	user, err := st.GetUserByUsername(username)
	if err != nil {
		limiter.RecordFailure(ip, username)
		time.Sleep(300 * time.Millisecond)
		return "", nil, errors.New("invalid credentials")
	}
	if !user.Status {
		limiter.RecordFailure(ip, username)
		time.Sleep(300 * time.Millisecond)
		return "", nil, errors.New("invalid credentials")
	}
	if !store.CheckPassword(user.PasswordHash, password) {
		limiter.RecordFailure(ip, username)
		time.Sleep(300 * time.Millisecond)
		return "", nil, errors.New("invalid credentials")
	}
	if user.Role == "visitor" {
		limiter.RecordFailure(ip, username)
		time.Sleep(300 * time.Millisecond)
		return "", nil, errors.New("访客账号无法登录管理后台")
	}

	limiter.Reset(ip, username)
	token, err := s.GenerateToken(user)
	if err != nil {
		return "", nil, err
	}
	return token, user, nil
}

func (s *Service) GenerateToken(user *store.User) (string, error) {
	s.mu.RLock()
	cfg := s.cfg
	s.mu.RUnlock()
	claims := Claims{
		UserID:   user.ID,
		Username: user.Username,
		Role:     user.Role,
		RegisteredClaims: jwt.RegisteredClaims{
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(cfg.TokenTTL)),
			IssuedAt:  jwt.NewNumericDate(time.Now()),
		},
	}
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	return token.SignedString([]byte(cfg.JWTSecret))
}

func (s *Service) ParseToken(tokenStr string) (*Claims, error) {
	s.mu.RLock()
	secret := s.cfg.JWTSecret
	s.mu.RUnlock()
	token, err := jwt.ParseWithClaims(tokenStr, &Claims{}, func(t *jwt.Token) (interface{}, error) {
		return []byte(secret), nil
	})
	if err != nil {
		return nil, err
	}
	claims, ok := token.Claims.(*Claims)
	if !ok || !token.Valid {
		return nil, errors.New("invalid token")
	}
	return claims, nil
}

func IsAdmin(role string) bool {
	return role == "admin"
}

func IsVisitor(role string) bool {
	return role == "visitor"
}
