package services

import (
	"errors"
	"fmt"
	"strings"
	"time"

	config "learnsteam/learsteam-quiz-api/configs"
	"learnsteam/learsteam-quiz-api/internal/models"
	"learnsteam/learsteam-quiz-api/internal/repositories"

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

type tokenService struct {
	repository repositories.TokenRepository
}

type TokenService interface {
	Find(string) (*models.Token, error)
	Create(string) (*models.Token, error)
	Update(*models.Token) (*models.Token, error)
	Delete(string) error

	Refresh(string) (*models.Token, error)

	Generate(string, int64) (string, error)
	Verify(tokenString string) (*jwt.Token, error)

	GetJwtToken(string) (*jwt.Token, error)
	ExtractTokenString(string) string
	VerifyTokenString(string) (*jwt.Token, error)
	ValidToken(*jwt.Token) (bool, error)
}

func NewTokenService(repository repositories.TokenRepository) TokenService {
	return &tokenService{
		repository: repository,
	}
}

func (s *tokenService) Find(id string) (*models.Token, error) {
	return s.repository.Find(id)
}

func (s *tokenService) Create(sub string) (*models.Token, error) {
	expiredAt := time.Now().Add(time.Hour * 24 * 30)
	accessToken, err := s.repository.Generate(sub, expiredAt.Unix())
	if err != nil {
		return nil, err
	}

	refreshExpiredAt := time.Now().Add(time.Hour * 24 * 90)
	refreshToken, err := s.repository.Generate(sub, refreshExpiredAt.Unix())
	if err != nil {
		return nil, err
	}

	newToken := &models.Token{
		ID:             uuid.NewString(),
		UserID:         sub,
		Token:          accessToken,
		RefreshToken:   refreshToken,
		Expires:        expiredAt.Unix(),
		RefreshExpires: refreshExpiredAt.Unix(),
	}

	token, err := s.repository.Create(newToken)

	return token, err
}

func (s *tokenService) Update(token *models.Token) (*models.Token, error) {
	return s.repository.Update(token)
}

func (s *tokenService) Delete(id string) error {
	return s.repository.Delete(id)
}

func (s *tokenService) Verify(tokenString string) (*jwt.Token, error) {
	jwtToken, err := jwt.Parse(tokenString, func(jwtToken *jwt.Token) (interface{}, error) {
		if _, ok := jwtToken.Method.(*jwt.SigningMethodHMAC); !ok {
			return nil, fmt.Errorf("unexpected signing method: %v", jwtToken.Header["alg"])
		}
		return []byte(config.SECRET_KEY), nil
	})

	return jwtToken, err
}

func (s *tokenService) Generate(user_id string, expire_at int64) (string, error) {
	claims := jwt.MapClaims{}
	claims["authorized"] = true
	claims["sub"] = user_id
	claims["exp"] = expire_at
	at := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	token, err := at.SignedString([]byte(config.SECRET_KEY))

	return token, err
}

func (s *tokenService) GetJwtToken(tokenString string) (*jwt.Token, error) {
	jwtToken, err := jwt.Parse(tokenString, func(jwtToken *jwt.Token) (interface{}, error) {
		if _, ok := jwtToken.Method.(*jwt.SigningMethodHMAC); !ok {
			return nil, fmt.Errorf("unexpected signing method: %v", jwtToken.Header["alg"])
		}
		return []byte(config.SECRET_KEY), nil
	})
	return jwtToken, err
}

func (s *tokenService) ExtractTokenString(authorization string) string {
	strArr := strings.Split(authorization, " ")
	if len(strArr) == 2 {
		return strArr[1]
	}
	return ""
}

func (s *tokenService) VerifyTokenString(tokenString string) (*jwt.Token, error) {
	jwtToken, err := jwt.Parse(tokenString, func(jwtToken *jwt.Token) (interface{}, error) {
		if _, ok := jwtToken.Method.(*jwt.SigningMethodHMAC); !ok {
			return nil, fmt.Errorf("unexpected signing method: %v", jwtToken.Header["alg"])
		}
		return []byte(config.SECRET_KEY), nil
	})

	if err != nil {
		return nil, err
	}

	return jwtToken, nil
}

func (s *tokenService) ValidToken(jwtToken *jwt.Token) (bool, error) {
	if jwtToken == nil {
		return false, fmt.Errorf("no token")
	}
	return jwtToken.Valid, nil
}

func (s *tokenService) Refresh(refreshToken string) (*models.Token, error) {
	fmt.Println("refresh_token", refreshToken)

	jwtToken, err := jwt.Parse(refreshToken, func(jwtToken *jwt.Token) (interface{}, error) {
		if _, ok := jwtToken.Method.(*jwt.SigningMethodHMAC); !ok {
			return nil, fmt.Errorf("unexpected signing method: %v", jwtToken.Header["alg"])
		}
		return []byte(config.SECRET_KEY), nil
	})

	if err != nil {
		fmt.Println("refresh token is not valid")
	} else {

		fmt.Println("refresh token is  valid")
	}

	if err != nil {
		fmt.Println("error", err.Error())
		return nil, err
	}

	claims, ok := jwtToken.Claims.(jwt.MapClaims)
	if !ok || !jwtToken.Valid {
		return nil, errors.New("refresh token is invalid")
	}

	sub := fmt.Sprintf("%s", claims["sub"])
	if err != nil {
		return nil, errors.New("wrong user")
	}

	token, err := s.repository.FindByRefreshToken(sub, refreshToken)
	if err != nil {
		return nil, errors.New("wrong token")
	}

	expiredAt := time.Now().Add(time.Hour * 24 * 30)
	accessToken, err := s.repository.Generate(sub, expiredAt.Unix())
	if err != nil {
		return nil, err
	}

	refreshExpiredAt := time.Now().Add(time.Hour * 24 * 90)
	refreshToken, err = s.repository.Generate(sub, refreshExpiredAt.Unix())
	if err != nil {
		return nil, err
	}

	token.Token = accessToken
	token.Expires = expiredAt.Unix()
	token.RefreshToken = refreshToken
	token.RefreshExpires = refreshExpiredAt.Unix()

	return s.repository.Update(token)
}
