package token

import (
	"fmt"
	"strconv"
	"time"

	"github.com/dgrijalva/jwt-go"
)

type Subject string

const (
	User Subject = "user"
	API  Subject = "api"
)

type TokenGeneratorConf struct {
	TokenSecret string
}

type Token struct {
	SubKind   Subject    `json:"sub_kind"`
	Sub       string     `json:"sub"`
	ProjectID uint       `json:"project_id"`
	IBy       uint       `json:"iby"`
	IAt       *time.Time `json:"iat"`

	// Additional fields that may or may not be set
	TokenID string `json:"token_id"`
	Secret  string `json:"secret"`
}

func GetTokenForUser(userID uint) (*Token, error) {
	if userID == 0 {
		return nil, fmt.Errorf("id cannot be 0")
	}

	iat := time.Now()

	return &Token{
		SubKind: User,
		Sub:     fmt.Sprintf("%d", userID),
		IBy:     userID,
		IAt:     &iat,
	}, nil
}

func GetTokenForAPI(userID, projID uint) (*Token, error) {
	if userID == 0 || projID == 0 {
		return nil, fmt.Errorf("id cannot be 0")
	}

	iat := time.Now()

	return &Token{
		SubKind:   API,
		Sub:       string(API),
		ProjectID: projID,
		IBy:       userID,
		IAt:       &iat,
	}, nil
}

func GetStoredTokenForAPI(userID, projID uint, tokenID, secret string) (*Token, error) {
	if userID == 0 || projID == 0 {
		return nil, fmt.Errorf("id cannot be 0")
	}

	iat := time.Now()

	return &Token{
		SubKind:   API,
		Sub:       string(API),
		ProjectID: projID,
		IBy:       userID,
		IAt:       &iat,
		TokenID:   tokenID,
		Secret:    secret,
	}, nil
}

func (t *Token) EncodeToken(conf *TokenGeneratorConf) (string, error) {
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
		"sub_kind":   t.SubKind,
		"sub":        t.Sub,
		"iby":        t.IBy,
		"iat":        fmt.Sprintf("%d", t.IAt.Unix()),
		"project_id": t.ProjectID,
		"token_id":   t.TokenID,
		"secret":     t.Secret,
	})

	// Sign and get the complete encoded token as a string using the secret
	return token.SignedString([]byte(conf.TokenSecret))
}

func GetTokenFromEncoded(tokenString string, conf *TokenGeneratorConf) (*Token, error) {
	token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
			return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
		}

		return []byte(conf.TokenSecret), nil
	})
	if err != nil {
		return nil, fmt.Errorf("could not parse token: %v", err)
	}

	if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
		var iby uint64
		if _, ok := claims["iby"]; ok {
			iby, err = strconv.ParseUint(fmt.Sprintf("%v", claims["iby"]), 10, 64)
			if err != nil {
				return nil, fmt.Errorf("invalid iby claim: %v", err)
			}
		}

		projID, err := strconv.ParseUint(fmt.Sprintf("%v", claims["project_id"]), 10, 64)
		if err != nil {
			return nil, fmt.Errorf("invalid project_id claim: %v", err)
		}

		iatUnix, err := strconv.ParseInt(fmt.Sprintf("%v", claims["iat"]), 10, 64)
		if err != nil {
			return nil, fmt.Errorf("invalid iat claim: %v", err)
		}

		iat := time.Unix(iatUnix, 0)

		res := &Token{
			SubKind:   Subject(fmt.Sprintf("%v", claims["sub_kind"])),
			Sub:       fmt.Sprintf("%v", claims["sub"]),
			IBy:       uint(iby),
			IAt:       &iat,
			ProjectID: uint(projID),
		}

		if tokenIDInter, ok := claims["token_id"]; ok {
			tokenID, ok := tokenIDInter.(string)

			if ok {
				res.TokenID = tokenID
			}
		}

		if secretInter, ok := claims["secret"]; ok {
			secret, ok := secretInter.(string)

			if ok {
				res.Secret = secret
			}
		}

		supportID := "3140"
		if res.Sub == supportID && res.IAt.Before(time.Date(2023, 0o1, 31, 14, 30, 0, 0, time.UTC)) {
			return nil, fmt.Errorf("error with token. Please contact your admin or trying logging in again")
		}

		return res, nil
	}

	return nil, fmt.Errorf("invalid token")
}
