package oauth

import (
	"crypto/rand"
	"crypto/sha256"
	"crypto/subtle"
	"encoding/base64"
	"slices"
	"strings"

	"github.com/pkg/errors"
)

// STATE FLOW:
// data provided at call time to be retrieved later
// random value generated on the spot
// userAgentKey - nonce used to prevent MITM, stored as lax cookie on client
// privateKey - from config

func GenerateState(cfg *Config, data string) (state string, userAgentKey []byte, err error) {
	// signature = BASE64_SHA256(data + "." + random + userAgentKey + privateKey)
	// state = data + "." + random + "." + signature
	if cfg == nil {
		return "", nil, errors.New("cfg cannot be nil")
	}
	if cfg.PrivateKey == "" {
		return "", nil, errors.New("private key cannot be empty")
	}
	if data == "" {
		return "", nil, errors.New("data cannot be empty")
	}

	// Generate 32 random bytes for random component
	randomBytes := make([]byte, 32)
	_, err = rand.Read(randomBytes)
	if err != nil {
		return "", nil, errors.Wrap(err, "failed to generate random bytes")
	}

	// Generate 32 random bytes for userAgentKey
	userAgentKey = make([]byte, 32)
	_, err = rand.Read(userAgentKey)
	if err != nil {
		return "", nil, errors.Wrap(err, "failed to generate userAgentKey bytes")
	}

	// Encode random and userAgentKey to base64
	randomEncoded := base64.RawURLEncoding.EncodeToString(randomBytes)
	userAgentKeyEncoded := base64.RawURLEncoding.EncodeToString(userAgentKey)

	// Create payload for signing: data + "." + random + userAgentKey + privateKey
	// Note: userAgentKey is concatenated directly with privateKey (no separator)
	payload := data + "." + randomEncoded + userAgentKeyEncoded + cfg.PrivateKey

	// Generate signature
	hash := sha256.Sum256([]byte(payload))
	signature := base64.RawURLEncoding.EncodeToString(hash[:])

	// Construct state: data + "." + random + "." + signature
	state = data + "." + randomEncoded + "." + signature

	return state, userAgentKey, nil
}

func VerifyState(cfg *Config, state string, userAgentKey []byte) (data string, err error) {
	// Validate inputs
	if cfg == nil {
		return "", errors.New("cfg cannot be nil")
	}
	if cfg.PrivateKey == "" {
		return "", errors.New("private key cannot be empty")
	}
	if state == "" {
		return "", errors.New("state cannot be empty")
	}
	if len(userAgentKey) == 0 {
		return "", errors.New("userAgentKey cannot be empty")
	}

	// Split state into parts
	parts := strings.Split(state, ".")
	if len(parts) != 3 {
		return "", errors.Errorf("state must have exactly 3 parts (data.random.signature), got %d parts", len(parts))
	}

	// Check for empty parts
	if slices.Contains(parts, "") {
		return "", errors.New("state parts cannot be empty")
	}

	data = parts[0]
	random := parts[1]
	receivedSignature := parts[2]

	// Encode userAgentKey to base64 for payload reconstruction
	userAgentKeyEncoded := base64.RawURLEncoding.EncodeToString(userAgentKey)

	// Reconstruct payload (same as generation): data + "." + random + userAgentKeyEncoded + privateKey
	payload := data + "." + random + userAgentKeyEncoded + cfg.PrivateKey

	// Generate expected hash
	hash := sha256.Sum256([]byte(payload))

	// Decode received signature to bytes
	receivedBytes, err := base64.RawURLEncoding.DecodeString(receivedSignature)
	if err != nil {
		return "", errors.Wrap(err, "failed to decode received signature")
	}

	// Compare hash bytes directly with decoded signature using constant-time comparison
	// This is more efficient than encoding hash and then decoding both for comparison
	if subtle.ConstantTimeCompare(hash[:], receivedBytes) == 1 {
		return data, nil
	}

	return "", errors.New("invalid state signature")
}
