package authidentity

import (
	"crypto/rand"
	"crypto/subtle"
	"encoding/base64"
	"encoding/hex"
	"fmt"
	"strings"

	"github.com/tidbcloud/AgentTeam/internal/core/secrets"

	"golang.org/x/crypto/argon2"
)

const (
	mobileVerificationHashPrefix      = "$agentteam-mobile-otp"
	mobileVerificationSaltLength      = 16
	mobileVerificationHashMemoryKiB   = 8 * 1024
	mobileVerificationHashIterations  = 1
	mobileVerificationHashParallelism = 1
	mobileVerificationHashLength      = 32
)

// HashMobileVerificationCode returns a purpose-specific hash for low-entropy
// mobile verification codes. Online guesses are bounded by the token attempt
// counter, so this uses lower Argon2id cost than long-lived account passwords.
func HashMobileVerificationCode(code string) (string, error) {
	salt := make([]byte, mobileVerificationSaltLength)
	if _, err := rand.Read(salt); err != nil {
		return "", fmt.Errorf("read mobile verification salt: %w", err)
	}
	hash := mobileVerificationCodeHash(code, salt)
	encodedSalt := base64.RawStdEncoding.EncodeToString(salt)
	encodedHash := base64.RawStdEncoding.EncodeToString(hash)
	return fmt.Sprintf("%s$v=1$m=%d,t=%d,p=%d$%s$%s",
		mobileVerificationHashPrefix,
		mobileVerificationHashMemoryKiB,
		mobileVerificationHashIterations,
		mobileVerificationHashParallelism,
		encodedSalt,
		encodedHash,
	), nil
}

// MobileVerificationCodeMatchesHash compares a mobile verification code with
// the current purpose-specific hash format. Legacy SHA-256 hashes remain
// verifiable only for already-issued short-lived codes.
func MobileVerificationCodeMatchesHash(code string, hash string) (bool, error) {
	if strings.HasPrefix(hash, mobileVerificationHashPrefix+"$") {
		return mobileVerificationCodeMatchesCurrentHash(code, hash)
	}
	if isLegacyMobileVerificationSHA256Hash(hash) {
		return secrets.TokenMatchesHash(code, hash), nil
	}
	return false, fmt.Errorf("unsupported mobile verification code hash format")
}

func mobileVerificationCodeMatchesCurrentHash(code string, encoded string) (bool, error) {
	parts := strings.Split(encoded, "$")
	if len(parts) != 6 || parts[1] != strings.TrimPrefix(mobileVerificationHashPrefix, "$") || parts[2] != "v=1" {
		return false, fmt.Errorf("invalid mobile verification code hash format")
	}

	var memory uint32
	var iterations uint32
	var lanes uint8
	if _, err := fmt.Sscanf(parts[3], "m=%d,t=%d,p=%d", &memory, &iterations, &lanes); err != nil {
		return false, fmt.Errorf("parse mobile verification hash params: %w", err)
	}
	if memory != mobileVerificationHashMemoryKiB ||
		iterations != mobileVerificationHashIterations ||
		lanes != mobileVerificationHashParallelism {
		return false, fmt.Errorf("invalid mobile verification hash params")
	}
	salt, err := base64.RawStdEncoding.DecodeString(parts[4])
	if err != nil {
		return false, fmt.Errorf("decode mobile verification salt: %w", err)
	}
	if len(salt) != mobileVerificationSaltLength {
		return false, fmt.Errorf("invalid mobile verification salt length")
	}
	expectedHash, err := base64.RawStdEncoding.DecodeString(parts[5])
	if err != nil {
		return false, fmt.Errorf("decode mobile verification hash body: %w", err)
	}
	if len(expectedHash) != mobileVerificationHashLength {
		return false, fmt.Errorf("invalid mobile verification hash length")
	}
	actualHash := argon2.IDKey([]byte(code), salt, iterations, memory, lanes, uint32(len(expectedHash)))
	return subtle.ConstantTimeCompare(actualHash, expectedHash) == 1, nil
}

func mobileVerificationCodeHash(code string, salt []byte) []byte {
	return argon2.IDKey(
		[]byte(code),
		salt,
		mobileVerificationHashIterations,
		mobileVerificationHashMemoryKiB,
		mobileVerificationHashParallelism,
		mobileVerificationHashLength,
	)
}

func isLegacyMobileVerificationSHA256Hash(hash string) bool {
	if len(hash) != 64 {
		return false
	}
	decoded, err := hex.DecodeString(hash)
	return err == nil && len(decoded) == 32
}
