<?php

declare(strict_types=1);

namespace App\Framework\Http\Versioning;

use App\Framework\Http\Request;

/**
 * Extracts API version from HTTP requests based on versioning strategy
 */
final readonly class VersionExtractor
{
    public function __construct(
        private VersioningConfig $config
    ) {
    }

    public function extractVersion(Request $request): ?ApiVersion
    {
        foreach ($this->config->strategies as $strategy) {
            $version = $this->extractFromStrategy($request, $strategy);
            if ($version !== null) {
                return $version;
            }
        }

        return $this->config->defaultVersion;
    }

    private function extractFromStrategy(Request $request, VersioningStrategy $strategy): ?ApiVersion
    {
        return match($strategy) {
            VersioningStrategy::HEADER => $this->extractFromHeader($request, $strategy->getDefaultHeaderName()),
            VersioningStrategy::CUSTOM_HEADER => $this->extractFromHeader($request, $this->config->customHeaderName),
            VersioningStrategy::URL_PATH => $this->extractFromUrlPath($request),
            VersioningStrategy::QUERY_PARAMETER => $this->extractFromQueryParameter($request),
            VersioningStrategy::ACCEPT_HEADER => $this->extractFromAcceptHeader($request)
        };
    }

    private function extractFromHeader(Request $request, string $headerName): ?ApiVersion
    {
        $version = $request->headers->getFirst($headerName);
        if (empty($version)) {
            return null;
        }

        try {
            return ApiVersion::fromString($version);
        } catch (\Throwable) {
            return null;
        }
    }

    private function extractFromUrlPath(Request $request): ?ApiVersion
    {
        // Extract version from URL path like /api/v1/users or /v2/posts
        $path = $request->path;

        // Match patterns like /api/v1/, /v1/, /api/v1.0/, etc.
        if (preg_match('/\/v(\d+(?:\.\d+)?(?:\.\d+)?)(?:\/|$)/', $path, $matches)) {
            try {
                return ApiVersion::fromString($matches[1]);
            } catch (\Throwable) {
                return null;
            }
        }

        return null;
    }

    private function extractFromQueryParameter(Request $request): ?ApiVersion
    {
        $version = $request->queryParams[$this->config->queryParameterName] ?? null;
        if (empty($version)) {
            return null;
        }

        try {
            return ApiVersion::fromString((string) $version);
        } catch (\Throwable) {
            return null;
        }
    }

    private function extractFromAcceptHeader(Request $request): ?ApiVersion
    {
        $accept = $request->headers->getFirst('Accept');
        if (empty($accept)) {
            return null;
        }

        // Parse Accept header like "application/vnd.api+json;version=1.0"
        // or "application/json;version=2.0"
        if (preg_match('/version=([^;,\s]+)/', $accept, $matches)) {
            try {
                return ApiVersion::fromString($matches[1]);
            } catch (\Throwable) {
                return null;
            }
        }

        return null;
    }
}
