package auth

import (
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"
	"crypto/sha256"
	"encoding/base64"
	"errors"
	"io"
	"time"

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

type GitHubOAuthStateClaims struct {
	StaffID uint `json:"staffId"`
	jwt.RegisteredClaims
}

func IssueGitHubOAuthState(staffID uint) (string, error) {
	claims := GitHubOAuthStateClaims{
		StaffID: staffID,
		RegisteredClaims: jwt.RegisteredClaims{
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(15 * time.Minute)),
			IssuedAt:  jwt.NewNumericDate(time.Now()),
		},
	}
	t := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	return t.SignedString([]byte(JWTSecret()))
}

func ParseGitHubOAuthState(state string) (uint, error) {
	t, err := jwt.ParseWithClaims(state, &GitHubOAuthStateClaims{}, func(t *jwt.Token) (any, error) {
		return []byte(JWTSecret()), nil
	})
	if err != nil {
		return 0, err
	}
	claims, ok := t.Claims.(*GitHubOAuthStateClaims)
	if !ok || !t.Valid || claims.StaffID == 0 {
		return 0, errors.New("invalid oauth state")
	}
	return claims.StaffID, nil
}

func tokenCipher() (cipher.AEAD, error) {
	sum := sha256.Sum256([]byte(JWTSecret()))
	block, err := aes.NewCipher(sum[:])
	if err != nil {
		return nil, err
	}
	return cipher.NewGCM(block)
}

func EncryptSecret(plain string) (string, error) {
	if plain == "" {
		return "", errors.New("empty secret")
	}
	gcm, err := tokenCipher()
	if err != nil {
		return "", err
	}
	nonce := make([]byte, gcm.NonceSize())
	if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
		return "", err
	}
	sealed := gcm.Seal(nonce, nonce, []byte(plain), nil)
	return base64.StdEncoding.EncodeToString(sealed), nil
}

func DecryptSecret(encoded string) (string, error) {
	raw, err := base64.StdEncoding.DecodeString(encoded)
	if err != nil {
		return "", err
	}
	gcm, err := tokenCipher()
	if err != nil {
		return "", err
	}
	if len(raw) < gcm.NonceSize() {
		return "", errors.New("ciphertext too short")
	}
	nonce, ciphertext := raw[:gcm.NonceSize()], raw[gcm.NonceSize():]
	plain, err := gcm.Open(nil, nonce, ciphertext, nil)
	if err != nil {
		return "", err
	}
	return string(plain), nil
}
