<?php

declare(strict_types=1);

namespace App\Framework\Logging\ValueObjects;

use App\Framework\Http\ServerRequest;
use App\Framework\Ulid\Ulid;
use Ramsey\Uuid\Uuid;

/**
 * Correlation ID Value Object für Request-/Transaction-Tracking
 *
 * Correlation IDs ermöglichen das Verfolgen von zusammenhängenden Operations
 * über Service-Grenzen, Async-Jobs und verteilte Systeme hinweg.
 *
 * Unterstützt verschiedene ID-Formate:
 * - UUID v4 (Standard, via ramsey/uuid)
 * - ULID (sortierbar, zeitbasiert, via Framework Ulid)
 * - Custom String (z.B. von externen Systemen)
 */
final readonly class CorrelationId implements \Stringable, \JsonSerializable
{
    private function __construct(
        private string $value
    ) {
        $this->validate($value);
    }

    /**
     * Erstellt CorrelationId aus String
     *
     * @throws \InvalidArgumentException wenn Format ungültig
     */
    public static function fromString(string $value): self
    {
        return new self($value);
    }

    /**
     * Generiert neue UUID-basierte CorrelationId
     *
     * Nutzt ramsey/uuid für RFC 4122 konforme UUIDs
     */
    public static function generate(): self
    {
        return new self(Uuid::uuid4()->toString());
    }

    /**
     * Erstellt CorrelationId aus UUID
     */
    public static function fromUuid(Uuid $uuid): self
    {
        return new self($uuid->toString());
    }

    /**
     * Generiert neue ULID-basierte CorrelationId
     *
     * Vorteil: Sortierbar und zeitbasiert
     * Nutzt Framework Ulid-Modul
     */
    public static function generateUlid(): self
    {
        return new self((string) Ulid::generate());
    }

    /**
     * Erstellt CorrelationId aus ULID
     */
    public static function fromUlid(Ulid $ulid): self
    {
        return new self((string) $ulid);
    }

    /**
     * Erstellt CorrelationId aus ServerRequest
     *
     * Liest Standard-Header (X-Correlation-ID, X-Request-ID)
     * oder generiert neue ID falls nicht vorhanden
     *
     * Framework-konforme Methode für HTTP-Request-Handling
     */
    public static function fromServerRequest(
        ServerRequest $request,
        string $headerName = 'X-Correlation-ID'
    ): self {
        // Primärer Header
        if ($request->hasHeader($headerName)) {
            $value = trim($request->getHeaderLine($headerName));
            if ($value !== '') {
                try {
                    return self::fromString($value);
                } catch (\InvalidArgumentException) {
                    // Invalid format - generate new one
                }
            }
        }

        // Fallback: X-Request-ID
        if ($request->hasHeader('X-Request-ID')) {
            $value = trim($request->getHeaderLine('X-Request-ID'));
            if ($value !== '') {
                try {
                    return self::fromString($value);
                } catch (\InvalidArgumentException) {
                    // Invalid format - generate new one
                }
            }
        }

        return self::generate();
    }

    /**
     * Erstellt CorrelationId aus PHP Superglobals
     *
     * Legacy-Methode für Kompatibilität mit bestehendem Code.
     * Bevorzuge fromServerRequest() wo möglich.
     *
     * @param string $headerName Header-Name (default: X-Correlation-ID)
     */
    public static function fromGlobals(string $headerName = 'X-Correlation-ID'): self
    {
        $headerKey = 'HTTP_' . str_replace('-', '_', strtoupper($headerName));

        // Primärer Header
        if (isset($_SERVER[$headerKey]) && is_string($_SERVER[$headerKey])) {
            $value = trim($_SERVER[$headerKey]);
            if ($value !== '') {
                try {
                    return self::fromString($value);
                } catch (\InvalidArgumentException) {
                    // Invalid format - continue
                }
            }
        }

        // Fallback: X-Request-ID
        if (isset($_SERVER['HTTP_X_REQUEST_ID']) && is_string($_SERVER['HTTP_X_REQUEST_ID'])) {
            $value = trim($_SERVER['HTTP_X_REQUEST_ID']);
            if ($value !== '') {
                try {
                    return self::fromString($value);
                } catch (\InvalidArgumentException) {
                    // Invalid format - continue
                }
            }
        }

        return self::generate();
    }

    /**
     * Gibt Correlation ID als String zurück
     */
    public function toString(): string
    {
        return $this->value;
    }

    /**
     * Prüft, ob ID im UUID-Format ist
     */
    public function isUuid(): bool
    {
        return Uuid::isValid($this->value);
    }

    /**
     * Prüft, ob ID im ULID-Format ist
     */
    public function isUlid(): bool
    {
        return Ulid::isValid($this->value);
    }

    /**
     * Gibt Kurzform der ID zurück (erste 8 Zeichen)
     *
     * Nützlich für Logging und Debugging
     */
    public function toShortString(): string
    {
        return substr($this->value, 0, 8);
    }

    /**
     * Vergleicht zwei CorrelationIds
     */
    public function equals(self $other): bool
    {
        return $this->value === $other->value;
    }

    /**
     * Validiert das ID-Format
     *
     * @throws \InvalidArgumentException
     */
    private function validate(string $value): void
    {
        if (trim($value) === '') {
            throw new \InvalidArgumentException('Correlation ID cannot be empty');
        }

        if (strlen($value) > 255) {
            throw new \InvalidArgumentException('Correlation ID too long (max 255 characters)');
        }

        // Erlaubte Zeichen: alphanumerisch, Bindestriche, Unterstriche
        if (preg_match('/^[a-zA-Z0-9_-]+$/', $value) !== 1) {
            throw new \InvalidArgumentException(
                'Correlation ID contains invalid characters (allowed: a-z, A-Z, 0-9, -, _)'
            );
        }
    }

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

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