package middleware

import (
	"log"
	"net/http"
	"os"

	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"encoding/base64"
	"encoding/pem"
	"errors"
	"strconv"
	"strings"
	"time"

	"github.com/gin-gonic/gin"
	_ "github.com/go-sql-driver/mysql"
	"github.com/joho/godotenv"
)

func decrypt(encrypted string) (string, error) {
	err := godotenv.Load()
	if err != nil {
		log.Fatal("Error loading .env file")
	}

	privateKey := os.Getenv("PRIVATE_KEY")

	cipherText, err := base64.StdEncoding.DecodeString(encrypted)
	if err != nil {
		return "", errors.New("cannot decode encrypted text")
	}

	block, _ := pem.Decode([]byte(privateKey))
	if block == nil {
		return "", errors.New("private key error")
	}
	priv, err := x509.ParsePKCS1PrivateKey(block.Bytes)
	if err != nil {
		return "", err
	}
	result, err := rsa.DecryptPKCS1v15(rand.Reader, priv, cipherText)
	if err != nil {
		return "", err
	}
	return string(result), nil
}

func TokenChecker() gin.HandlerFunc {
	return func(c *gin.Context) {
		token := c.GetHeader("OCBO-Token")
		start := strings.Index(token, "ocbo=") + len("ocbo=")
		end := strings.LastIndex(token, "token")

		if token == "" {
			c.JSON(http.StatusUnauthorized, gin.H{"error": "The request is missing an OCBO Token"})
			c.Abort()
			return
		}

		if start == -1 || end == -1 || start >= end {
			c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid OCBO Token format"})
			c.Abort()
			return
		}

		extractedToken := token[start:end]
		extractedToken = strings.ReplaceAll(extractedToken, "~", "/")

		decrypted, err := decrypt(extractedToken)
		if err != nil {
			c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid OCBO Token Value"})
			c.Abort()
			return
		}

		parts := strings.Split(decrypted, "-")
		partCount := len(parts)
		var finalRequest int
		var finalExpiration int

		if partCount == 4 {
			requested := parts[2]
			requestedNum, _ := strconv.Atoi(requested)
			finalRequest = requestedNum
			expiration := parts[3]
			expirationNum, _ := strconv.Atoi(expiration)
			finalExpiration = expirationNum
		} else {
			requested := parts[3]
			requestedNum, _ := strconv.Atoi(requested)
			finalRequest = requestedNum
			expiration := parts[4]
			expirationNum, _ := strconv.Atoi(expiration)
			finalExpiration = expirationNum
		}

		unix := strconv.FormatInt(time.Now().UTC().Unix(), 10)
		unixNum, _ := strconv.Atoi(unix)

		if finalRequest > finalExpiration {
			c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid OCBO Token Value"})
			c.Abort()
			return
		}

		if unixNum > finalExpiration {
			c.JSON(http.StatusUnauthorized, gin.H{"error": "OCBO Token Expired"})
			c.Abort()
			return
		}

		c.Next()
	}
}
