<?php

declare(strict_types=1);

namespace App\Framework\Core\ValueObjects;

/**
 * Idempotency Key Value Object
 *
 * Represents a unique key for ensuring idempotent operations.
 * Multiple requests with the same idempotency key will produce the same result.
 *
 * Use Cases:
 * - Preventing duplicate form submissions
 * - Handling network retries safely
 * - Ensuring at-most-once execution of critical actions
 * - Payment processing deduplication
 * - Queue job deduplication
 * - Webhook deduplication
 *
 * Usage:
 * ```php
 * // Auto-generate
 * $key = IdempotencyKey::generate();
 *
 * // From user-provided header
 * $key = IdempotencyKey::fromString($request->header('Idempotency-Key'));
 *
 * // From request fingerprint
 * $key = IdempotencyKey::fromFingerprint('payment', ['amount' => 100, 'user' => 123]);
 * ```
 */
final readonly class IdempotencyKey
{
    private const MIN_LENGTH = 16;
    private const MAX_LENGTH = 255;

    private function __construct(
        public string $value
    ) {
        $this->validate();
    }

    /**
     * Create from user-provided string
     */
    public static function fromString(string $value): self
    {
        return new self($value);
    }

    /**
     * Generate random idempotency key
     *
     * Uses cryptographically secure random bytes.
     */
    public static function generate(): self
    {
        $randomBytes = random_bytes(32);
        $value = bin2hex($randomBytes);

        return new self($value);
    }

    /**
     * Create from operation fingerprint
     *
     * Generates deterministic key from operation context.
     * Same context = same key = idempotent.
     *
     * @param string $operation Operation identifier (e.g., 'payment', 'order', 'email')
     * @param array<string, mixed> $context Operation context for fingerprinting
     */
    public static function fromFingerprint(string $operation, array $context): self
    {
        // Remove non-deterministic data
        unset($context['_csrf'], $context['timestamp'], $context['_token']);

        // Sort context for deterministic hashing
        ksort($context);

        // Create fingerprint
        $fingerprint = json_encode([
            'operation' => $operation,
            'context' => $context,
        ]);

        // Hash for fixed-length key
        $hash = Hash::sha256($fingerprint)->toString();

        return new self($hash);
    }

    /**
     * Get string representation
     */
    public function toString(): string
    {
        return $this->value;
    }

    /**
     * Equality comparison
     */
    public function equals(self $other): bool
    {
        return hash_equals($this->value, $other->value);
    }

    /**
     * Get storage key for caching
     *
     * Format: "idempotency:{hash}"
     */
    public function toStorageKey(): string
    {
        // Use hash for compact storage keys
        $hash = Hash::sha256($this->value)->toShort(32);

        return "idempotency:{$hash}";
    }

    /**
     * Get masked version for logging (privacy-safe)
     *
     * Shows first 8 characters, masks rest
     */
    public function toMasked(): string
    {
        $length = strlen($this->value);

        if ($length <= 8) {
            return str_repeat('*', $length);
        }

        $visible = substr($this->value, 0, 8);
        $masked = str_repeat('*', min(3, $length - 8));

        return $visible . $masked;
    }

    /**
     * Validate idempotency key
     */
    private function validate(): void
    {
        $length = strlen($this->value);

        if ($length < self::MIN_LENGTH) {
            throw new \InvalidArgumentException(
                "Idempotency key must be at least " . self::MIN_LENGTH . " characters, got {$length}"
            );
        }

        if ($length > self::MAX_LENGTH) {
            throw new \InvalidArgumentException(
                "Idempotency key cannot exceed " . self::MAX_LENGTH . " characters, got {$length}"
            );
        }

        // Allow alphanumeric, hyphens, underscores
        if (! preg_match('/^[a-zA-Z0-9_-]+$/', $this->value)) {
            throw new \InvalidArgumentException(
                'Idempotency key must contain only alphanumeric characters, hyphens, and underscores'
            );
        }
    }

    public function __toString(): string
    {
        return $this->value;
    }
}
