<?php

declare(strict_types=1);

namespace App\Framework\Http\ValueObjects;

/**
 * Immutable value object representing an HTTP ETag
 */
final readonly class ETag
{
    public function __construct(
        private string $value,
        private bool $isWeak = false
    ) {
        if (empty($value)) {
            throw new \InvalidArgumentException('ETag value cannot be empty');
        }
    }

    /**
     * Create a strong ETag
     */
    public static function strong(string $value): self
    {
        return new self($value, false);
    }

    /**
     * Create a weak ETag
     */
    public static function weak(string $value): self
    {
        return new self($value, true);
    }

    /**
     * Create ETag from content hash
     */
    public static function fromContent(string $content, bool $weak = false): self
    {
        $hash = hash('sha256', $content);

        return new self($hash, $weak);
    }

    /**
     * Create ETag from file
     */
    public static function fromFile(string $filePath, bool $weak = false): self
    {
        if (! file_exists($filePath)) {
            throw new \InvalidArgumentException("File does not exist: {$filePath}");
        }

        $mtime = filemtime($filePath);
        $size = filesize($filePath);

        if ($mtime === false || $size === false) {
            throw new \InvalidArgumentException("Cannot read file stats: {$filePath}");
        }

        $value = hash('sha256', $filePath . $mtime . $size);

        return new self($value, $weak);
    }

    /**
     * Parse ETag from HTTP header value
     */
    public static function parse(string $headerValue): self
    {
        $headerValue = trim($headerValue);

        if (empty($headerValue)) {
            throw new \InvalidArgumentException('ETag header value cannot be empty');
        }

        // Check for weak ETag prefix
        $isWeak = str_starts_with($headerValue, 'W/');
        if ($isWeak) {
            $headerValue = substr($headerValue, 2);
        }

        // Remove quotes if present
        $value = trim($headerValue, '"');

        return new self($value, $isWeak);
    }

    /**
     * Parse multiple ETags from If-None-Match header
     * @return array<int, string|self>
     */
    public static function parseMultiple(string $headerValue): array
    {
        $headerValue = trim($headerValue);

        if ($headerValue === '*') {
            return ['*'];
        }

        $etags = [];
        $parts = preg_split('/,\s*/', $headerValue);

        if ($parts === false) {
            return [];
        }

        foreach ($parts as $part) {
            if (! empty($part)) {
                try {
                    $etags[] = self::parse($part);
                } catch (\InvalidArgumentException) {
                    // Skip invalid ETags
                    continue;
                }
            }
        }

        return $etags;
    }

    /**
     * Get the raw value without quotes
     */
    public function getValue(): string
    {
        return $this->value;
    }

    /**
     * Check if this is a weak ETag
     */
    public function isWeak(): bool
    {
        return $this->isWeak;
    }

    /**
     * Check if this is a strong ETag
     */
    public function isStrong(): bool
    {
        return ! $this->isWeak;
    }

    /**
     * Get the HTTP header value representation
     */
    public function toHeaderValue(): string
    {
        $prefix = $this->isWeak ? 'W/' : '';

        return $prefix . '"' . $this->value . '"';
    }

    /**
     * Check if this ETag matches another ETag
     *
     * For strong comparison (used in If-Match), both must be strong ETags
     * For weak comparison (used in If-None-Match), weak comparison is allowed
     */
    public function matches(self $other, bool $strongComparison = false): bool
    {
        if ($strongComparison) {
            // Strong comparison: both must be strong and values must match
            return $this->isStrong() && $other->isStrong() && $this->value === $other->value;
        } else {
            // Weak comparison: values must match regardless of weak/strong
            return $this->value === $other->value;
        }
    }

    /**
     * Check if this ETag matches any ETag in a list
     */
    public function matchesAny(array $etags, bool $strongComparison = false): bool
    {
        foreach ($etags as $etag) {
            if ($etag === '*') {
                return true;
            }

            if ($etag instanceof self && $this->matches($etag, $strongComparison)) {
                return true;
            }
        }

        return false;
    }

    /**
     * Create a new ETag with different weak/strong setting
     */
    public function withWeak(bool $weak): self
    {
        return new self($this->value, $weak);
    }

    /**
     * Convert to string for debugging
     */
    public function toString(): string
    {
        return $this->toHeaderValue();
    }

    /**
     * Check equality with another ETag
     */
    public function equals(self $other): bool
    {
        return $this->value === $other->value && $this->isWeak === $other->isWeak;
    }

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