package auth

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

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

const (
	passwordTime    uint32 = 3
	passwordMemory  uint32 = 64 * 1024
	passwordThreads uint8  = 4
	passwordKeyLen  uint32 = 32
	passwordSaltLen        = 16
)

func randomBytes(length int) ([]byte, error) {
	buf := make([]byte, length)
	if _, err := rand.Read(buf); err != nil {
		return nil, err
	}
	return buf, nil
}

func randomToken(length int) (string, error) {
	buf, err := randomBytes(length)
	if err != nil {
		return "", err
	}
	return base64.RawURLEncoding.EncodeToString(buf), nil
}

func randomHex(length int) (string, error) {
	buf, err := randomBytes(length)
	if err != nil {
		return "", err
	}
	return hex.EncodeToString(buf), nil
}

func hashSecret(secret string) string {
	sum := sha256.Sum256([]byte(secret))
	return hex.EncodeToString(sum[:])
}

func constantTimeEqualHex(a, b string) bool {
	if len(a) != len(b) {
		return false
	}
	return subtle.ConstantTimeCompare([]byte(a), []byte(b)) == 1
}

func hashPassword(password string) (string, error) {
	salt, err := randomBytes(passwordSaltLen)
	if err != nil {
		return "", err
	}
	hash := argon2.IDKey([]byte(password), salt, passwordTime, passwordMemory, passwordThreads, passwordKeyLen)
	return fmt.Sprintf("$argon2id$v=19$m=%d,t=%d,p=%d$%s$%s",
		passwordMemory,
		passwordTime,
		passwordThreads,
		base64.RawStdEncoding.EncodeToString(salt),
		base64.RawStdEncoding.EncodeToString(hash),
	), nil
}

func verifyPassword(password, encoded string) (bool, error) {
	parts := strings.Split(encoded, "$")
	if len(parts) != 6 || parts[1] != "argon2id" {
		return false, fmt.Errorf("invalid password hash")
	}

	var memory uint64
	var timeCost uint64
	var threads uint64
	for _, param := range strings.Split(parts[3], ",") {
		keyValue := strings.SplitN(param, "=", 2)
		if len(keyValue) != 2 {
			return false, fmt.Errorf("invalid password hash parameters")
		}
		value, err := strconv.ParseUint(keyValue[1], 10, 32)
		if err != nil {
			return false, fmt.Errorf("parse password hash parameter: %w", err)
		}
		switch keyValue[0] {
		case "m":
			memory = value
		case "t":
			timeCost = value
		case "p":
			threads = value
		}
	}
	if memory == 0 || timeCost == 0 || threads == 0 {
		return false, fmt.Errorf("missing password hash parameters")
	}

	salt, err := base64.RawStdEncoding.DecodeString(parts[4])
	if err != nil {
		return false, fmt.Errorf("decode password salt: %w", err)
	}
	expected, err := base64.RawStdEncoding.DecodeString(parts[5])
	if err != nil {
		return false, fmt.Errorf("decode password hash: %w", err)
	}

	actual := argon2.IDKey([]byte(password), salt, uint32(timeCost), uint32(memory), uint8(threads), uint32(len(expected)))
	return subtle.ConstantTimeCompare(actual, expected) == 1, nil
}

func normalizeScopes(scopes []Scope) []Scope {
	if len(scopes) == 0 {
		return []Scope{ScopeDocsRead, ScopeDocsWrite, ScopeSyncRead, ScopeSyncWrite}
	}
	seen := make(map[Scope]struct{}, len(scopes))
	var normalized []Scope
	for _, scope := range scopes {
		switch scope {
		case ScopeDocsRead, ScopeDocsWrite, ScopeSyncRead, ScopeSyncWrite, ScopeAdmin:
			if _, ok := seen[scope]; !ok {
				seen[scope] = struct{}{}
				normalized = append(normalized, scope)
			}
		}
	}
	if len(normalized) == 0 {
		return []Scope{ScopeDocsRead}
	}
	return normalized
}

func scopesToString(scopes []Scope) string {
	normalized := normalizeScopes(scopes)
	parts := make([]string, 0, len(normalized))
	for _, scope := range normalized {
		parts = append(parts, string(scope))
	}
	return strings.Join(parts, ",")
}

func scopesFromString(raw string) []Scope {
	if raw == "" {
		return nil
	}
	parts := strings.Split(raw, ",")
	scopes := make([]Scope, 0, len(parts))
	for _, part := range parts {
		trimmed := strings.TrimSpace(part)
		if trimmed != "" {
			scopes = append(scopes, Scope(trimmed))
		}
	}
	return normalizeScopes(scopes)
}

func hasScope(scopes []Scope, required Scope) bool {
	for _, scope := range scopes {
		if scope == ScopeAdmin || scope == required {
			return true
		}
	}
	return false
}

func HasScope(scopes []Scope, required Scope) bool {
	return hasScope(scopes, required)
}
