package utils

import (
	"ai_developer_admin/models"
	"crypto/rand"
	"errors"
	"fmt"
	"io"
	"time"

	"github.com/beego/beego/logs"
	"github.com/golang-jwt/jwt"
	"golang.org/x/crypto/scrypt"
)

const (
	SecretKEY              string = "JWT-Secret-Key"
	DEFAULT_EXPIRE_SECONDS int    = 6000 // default expired 10 minutes
	PasswordHashBytes             = 16
)

type MyCustomClaims struct {
	UserID   int    `json:"userID"`
	Username string `json:"userneme"`
	Role     int    `json:"role"`
	jwt.StandardClaims
}

// generate token
//func GenerateToken(user *models.User, userID int, issuedAt int64, expireAt int64) (tokenString string, err error) {
//
//	// Create the Claims
//	mySigningKey := []byte(SecretKEY)
//	logs.Info("Token will be expired at ", time.Unix(expireAt, 0))
//
//	claims := MyCustomClaims{
//		userID,
//		jwt.StandardClaims{
//			Issuer:    user.Username,
//			IssuedAt:  issuedAt,
//			ExpiresAt: expireAt,
//		},
//	}
//
//	// Create the token using your claims
//	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
//	// Signs the token with a secret
//	tokenStr, err := token.SignedString(mySigningKey)
//	if err != nil {
//		return "", errors.New("error: failed to generate token")
//	}
//
//	return tokenStr, nil
//}

func GenerateToken(user *models.User, userID int, expiredSeconds int) (tokenString string, err error) {
	if expiredSeconds == 0 {
		expiredSeconds = DEFAULT_EXPIRE_SECONDS
	}

	// Create the Claims
	mySigningKey := []byte(SecretKEY)
	expireAt := time.Now().Add(time.Second * time.Duration(expiredSeconds)).Unix()
	logs.Info("Token will be expired at ", time.Unix(expireAt, 0))

	claims := MyCustomClaims{
		userID,
		user.Username,
		user.Role,
		jwt.StandardClaims{
			Issuer:    user.Username,
			IssuedAt:  time.Now().Unix(),
			ExpiresAt: expireAt,
		},
	}

	// Create the token using your claims
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	// Signs the token with a secret
	tokenStr, err := token.SignedString(mySigningKey)
	if err != nil {
		return "", errors.New("error: failed to generate token")
	}

	return tokenStr, nil
}

// validate token
func ValidateToken(tokenString string) (*models.JwtPayload, error) {
	token, err := jwt.ParseWithClaims(tokenString, &MyCustomClaims{},
		func(token *jwt.Token) (interface{}, error) {
			return []byte(SecretKEY), nil
		})

	claims, ok := token.Claims.(*MyCustomClaims)
	if ok && token.Valid {
		logs.Info("%v %v", claims.UserID, claims.StandardClaims.ExpiresAt)
		logs.Info("Token will be expired at ", time.Unix(claims.StandardClaims.ExpiresAt, 0))

		return &models.JwtPayload{
			Username:  claims.StandardClaims.Issuer,
			UserID:    claims.UserID,
			Role:      claims.Role,
			IssuedAt:  claims.StandardClaims.IssuedAt,
			ExpiresAt: claims.StandardClaims.ExpiresAt,
		}, nil
	} else {
		logs.Info(err.Error())
		return nil, errors.New("error: failed to validate token")
	}
}

// update token
func RefreshToken(tokenString string) (newTokenString string, err error) {
	// get previous token
	token, err := jwt.ParseWithClaims(tokenString, &MyCustomClaims{},
		func(token *jwt.Token) (interface{}, error) {
			return []byte(SecretKEY), nil
		})

	claims, ok := token.Claims.(*MyCustomClaims)
	if !ok || !token.Valid {
		return "", err
	}

	mySigningKey := []byte(SecretKEY)
	expireAt := time.Now().Add(time.Second * time.Duration(DEFAULT_EXPIRE_SECONDS)).Unix() //new expired
	newClaims := MyCustomClaims{
		claims.UserID,
		claims.Username,
		claims.Role,
		jwt.StandardClaims{
			Issuer:    claims.StandardClaims.Issuer, //name of token issue
			IssuedAt:  time.Now().Unix(),            //time of token issue
			ExpiresAt: expireAt,
		},
	}

	// generate new token with new claims
	newToken := jwt.NewWithClaims(jwt.SigningMethodHS256, newClaims)
	// sign the token with a secret
	tokenStr, err := newToken.SignedString(mySigningKey)
	if err != nil {
		return "", errors.New("error: failed to generate new fresh json web token")
	}

	return tokenStr, nil
}

// generate salt
func GenerateSalt() (salt string, err error) {
	buf := make([]byte, PasswordHashBytes)
	if _, err := io.ReadFull(rand.Reader, buf); err != nil {
		return "", errors.New("error: failed to generate user's salt")
	}

	return fmt.Sprintf("%x", buf), nil
}

// generate password hash
func GeneratePassHash(password string, salt string) (hash string, err error) {
	h, err := scrypt.Key([]byte(password), []byte(salt), 16384, 8, 1, PasswordHashBytes)
	if err != nil {
		return "", errors.New("error: failed to generate password hash")
	}

	return fmt.Sprintf("%x", h), nil
}

func GenerateKongToken(jwtCredential *models.JWTResponse, custom_id string) (tokenString string, err error) {
	// Create the Claims
	mySigningKey := []byte(jwtCredential.Secret)

	claims := &jwt.StandardClaims{
		Issuer: jwtCredential.Key,
	}

	// Create the token using your claims
	method := jwt.SigningMethodHS256
	if jwtCredential.Algorithm == "HS384" {
		method = jwt.SigningMethodHS384
	}
	if jwtCredential.Algorithm == "HS512" {
		method = jwt.SigningMethodHS512
	}
	if jwtCredential.Algorithm == "HS256" {
		method = jwt.SigningMethodHS256
	}
	token := jwt.NewWithClaims(method, claims)
	// Signs the token with a secret
	tokenStr, err := token.SignedString(mySigningKey)
	if err != nil {
		return "", errors.New("error: failed to generate token")
	}

	return tokenStr, nil
}
