<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use Symfony\Component\HttpFoundation\Response;

/**
 * API 버전 관리 미들웨어
 *
 * - 헤더 Accept-Version으로 버전 선택 (기본: v1)
 * - 요청된 버전의 라우트가 없으면 하위 버전으로 fallback
 * - 응답 헤더에 실제 사용된 버전 표시
 */
class ApiVersionMiddleware
{
    /**
     * 지원하는 API 버전 목록 (우선순위 순)
     */
    protected array $supportedVersions = ['v2', 'v1'];

    /**
     * 기본 버전
     */
    protected string $defaultVersion = 'v1';

    /**
     * Handle an incoming request.
     */
    public function handle(Request $request, Closure $next): Response
    {
        // 1. 요청된 버전 확인 (헤더 > 쿼리 파라미터 > 기본값)
        $requestedVersion = $this->getRequestedVersion($request);

        // 2. 실제 사용할 버전 결정 (fallback 적용)
        $actualVersion = $this->resolveVersion($request, $requestedVersion);

        // 3. 요청에 버전 정보 저장 (컨트롤러에서 사용 가능)
        $request->attributes->set('api_version', $actualVersion);
        $request->attributes->set('api_version_requested', $requestedVersion);
        $request->attributes->set('api_version_fallback', $actualVersion !== $requestedVersion);

        // 4. 요청 처리
        $response = $next($request);

        // 5. 응답 헤더에 버전 정보 추가
        $response->headers->set('X-API-Version', $actualVersion);
        if ($actualVersion !== $requestedVersion) {
            $response->headers->set('X-API-Version-Fallback', 'true');
            $response->headers->set('X-API-Version-Requested', $requestedVersion);
        }

        return $response;
    }

    /**
     * 요청에서 버전 정보 추출
     */
    protected function getRequestedVersion(Request $request): string
    {
        // 1. Accept-Version 헤더 (권장)
        $version = $request->header('Accept-Version');
        if ($version && $this->isValidVersion($version)) {
            return $version;
        }

        // 2. X-API-Version 헤더 (대안)
        $version = $request->header('X-API-Version');
        if ($version && $this->isValidVersion($version)) {
            return $version;
        }

        // 3. 쿼리 파라미터 (테스트용)
        $version = $request->query('api_version');
        if ($version && $this->isValidVersion($version)) {
            return $version;
        }

        return $this->defaultVersion;
    }

    /**
     * 유효한 버전인지 확인
     */
    protected function isValidVersion(string $version): bool
    {
        return in_array($version, $this->supportedVersions, true);
    }

    /**
     * 실제 사용할 버전 결정 (fallback 로직)
     */
    protected function resolveVersion(Request $request, string $requestedVersion): string
    {
        // 요청된 버전부터 하위 버전까지 순차 확인
        $startIndex = array_search($requestedVersion, $this->supportedVersions, true);

        if ($startIndex === false) {
            return $this->defaultVersion;
        }

        // 요청된 버전부터 하위 버전까지 체크
        for ($i = $startIndex; $i < count($this->supportedVersions); $i++) {
            $version = $this->supportedVersions[$i];

            if ($this->versionRouteExists($request, $version)) {
                return $version;
            }
        }

        // 모든 버전에서 라우트를 찾지 못하면 기본값 반환
        return $this->defaultVersion;
    }

    /**
     * 해당 버전의 라우트가 존재하는지 확인
     */
    protected function versionRouteExists(Request $request, string $version): bool
    {
        $path = $request->path();

        // URL에서 버전 부분 교체
        // /api/v1/users → /api/v2/users
        $versionedPath = preg_replace('/^api\/v\d+/', "api/{$version}", $path);

        // 해당 경로의 라우트가 존재하는지 확인
        $routes = Route::getRoutes();

        foreach ($routes as $route) {
            $routeUri = $route->uri();

            // 정확히 일치하거나 파라미터 패턴 매칭
            if ($this->matchesRoute($versionedPath, $routeUri, $request->method())) {
                return true;
            }
        }

        return false;
    }

    /**
     * 경로가 라우트와 일치하는지 확인
     */
    protected function matchesRoute(string $path, string $routeUri, string $method): bool
    {
        // 라우트 URI의 파라미터를 정규식으로 변환
        // {id} → [^/]+
        $pattern = preg_replace('/\{[^}]+\}/', '[^/]+', $routeUri);
        $pattern = '#^'.$pattern.'$#';

        return (bool) preg_match($pattern, $path);
    }

    /**
     * 지원 버전 목록 반환 (외부에서 사용)
     */
    public function getSupportedVersions(): array
    {
        return $this->supportedVersions;
    }

    /**
     * 기본 버전 반환
     */
    public function getDefaultVersion(): string
    {
        return $this->defaultVersion;
    }
}
