<?php

namespace App\Services\ApiExplorer;

use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\File;

/**
 * OpenAPI JSON 파싱 서비스
 */
class OpenApiParserService
{
    private ?array $spec = null;

    private string $specPath;

    public function __construct()
    {
        $this->specPath = config('api-explorer.openapi_path');
    }

    /**
     * 전체 스펙 파싱
     */
    public function parse(): array
    {
        if ($this->spec !== null) {
            return $this->spec;
        }

        $cacheKey = config('api-explorer.cache.key', 'api_explorer_openapi_spec');
        $cacheTtl = config('api-explorer.cache.ttl', 3600);

        if (config('api-explorer.cache.enabled', true)) {
            $fileModified = File::exists($this->specPath) ? File::lastModified($this->specPath) : 0;
            $cacheKeyWithMtime = $cacheKey.'_'.$fileModified;

            $this->spec = Cache::remember($cacheKeyWithMtime, $cacheTtl, function () {
                return $this->loadSpec();
            });
        } else {
            $this->spec = $this->loadSpec();
        }

        return $this->spec;
    }

    /**
     * 스펙 파일 로드
     */
    private function loadSpec(): array
    {
        if (! File::exists($this->specPath)) {
            return [
                'openapi' => '3.0.0',
                'info' => ['title' => 'API', 'version' => '1.0.0'],
                'paths' => [],
                'tags' => [],
            ];
        }

        $content = File::get($this->specPath);
        $spec = json_decode($content, true);

        if (json_last_error() !== JSON_ERROR_NONE) {
            return [
                'openapi' => '3.0.0',
                'info' => ['title' => 'API', 'version' => '1.0.0'],
                'paths' => [],
                'tags' => [],
                'error' => 'JSON 파싱 오류: '.json_last_error_msg(),
            ];
        }

        return $spec;
    }

    /**
     * 엔드포인트 목록 추출
     */
    public function getEndpoints(): Collection
    {
        $spec = $this->parse();
        $endpoints = collect();

        foreach ($spec['paths'] ?? [] as $path => $methods) {
            foreach ($methods as $method => $details) {
                if (! in_array(strtoupper($method), ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS', 'HEAD'])) {
                    continue;
                }

                $operationId = $details['operationId'] ?? $this->generateOperationId($method, $path);

                $endpoints->push([
                    'operationId' => $operationId,
                    'path' => $path,
                    'method' => strtoupper($method),
                    'summary' => $details['summary'] ?? '',
                    'description' => $details['description'] ?? '',
                    'tags' => $details['tags'] ?? [],
                    'parameters' => $details['parameters'] ?? [],
                    'requestBody' => $details['requestBody'] ?? null,
                    'responses' => $details['responses'] ?? [],
                    'security' => $details['security'] ?? [],
                    'deprecated' => $details['deprecated'] ?? false,
                ]);
            }
        }

        return $endpoints->sortBy([
            ['tags', 'asc'],
            ['path', 'asc'],
            ['method', 'asc'],
        ])->values();
    }

    /**
     * 단일 엔드포인트 조회
     */
    public function getEndpoint(string $operationId): ?array
    {
        return $this->getEndpoints()->firstWhere('operationId', $operationId);
    }

    /**
     * 태그 목록 추출
     */
    public function getTags(): array
    {
        $spec = $this->parse();

        // 정의된 태그
        $definedTags = collect($spec['tags'] ?? [])->keyBy('name');

        // 실제 사용된 태그
        $usedTags = $this->getEndpoints()
            ->pluck('tags')
            ->flatten()
            ->unique()
            ->filter()
            ->values();

        // 병합
        return $usedTags->map(function ($tagName) use ($definedTags) {
            $tagInfo = $definedTags->get($tagName, []);

            return [
                'name' => $tagName,
                'description' => $tagInfo['description'] ?? '',
            ];
        })->toArray();
    }

    /**
     * 검색
     */
    public function search(string $query): Collection
    {
        if (empty($query)) {
            return $this->getEndpoints();
        }

        $query = mb_strtolower($query);

        return $this->getEndpoints()->filter(function ($endpoint) use ($query) {
            // 엔드포인트 경로
            if (str_contains(mb_strtolower($endpoint['path']), $query)) {
                return true;
            }

            // summary
            if (str_contains(mb_strtolower($endpoint['summary']), $query)) {
                return true;
            }

            // description
            if (str_contains(mb_strtolower($endpoint['description']), $query)) {
                return true;
            }

            // operationId
            if (str_contains(mb_strtolower($endpoint['operationId']), $query)) {
                return true;
            }

            // 태그
            foreach ($endpoint['tags'] as $tag) {
                if (str_contains(mb_strtolower($tag), $query)) {
                    return true;
                }
            }

            // 파라미터명
            foreach ($endpoint['parameters'] as $param) {
                if (str_contains(mb_strtolower($param['name'] ?? ''), $query)) {
                    return true;
                }
                if (str_contains(mb_strtolower($param['description'] ?? ''), $query)) {
                    return true;
                }
            }

            return false;
        })->values();
    }

    /**
     * 필터링
     */
    public function filter(array $filters): Collection
    {
        $endpoints = $this->getEndpoints();

        // 메서드 필터
        if (! empty($filters['methods'])) {
            $methods = array_map('strtoupper', (array) $filters['methods']);
            $endpoints = $endpoints->filter(fn ($e) => in_array($e['method'], $methods));
        }

        // 태그 필터
        if (! empty($filters['tags'])) {
            $tags = (array) $filters['tags'];
            $endpoints = $endpoints->filter(function ($e) use ($tags) {
                return count(array_intersect($e['tags'], $tags)) > 0;
            });
        }

        // 검색어
        if (! empty($filters['search'])) {
            $endpoints = $this->searchInCollection($endpoints, $filters['search']);
        }

        return $endpoints->values();
    }

    /**
     * 컬렉션 내 검색
     */
    private function searchInCollection(Collection $endpoints, string $query): Collection
    {
        $query = mb_strtolower($query);

        return $endpoints->filter(function ($endpoint) use ($query) {
            return str_contains(mb_strtolower($endpoint['path']), $query)
                || str_contains(mb_strtolower($endpoint['summary']), $query)
                || str_contains(mb_strtolower($endpoint['description']), $query)
                || str_contains(mb_strtolower($endpoint['operationId']), $query);
        });
    }

    /**
     * 태그별 그룹핑
     */
    public function getEndpointsByTag(): Collection
    {
        return $this->getEndpoints()->groupBy(function ($endpoint) {
            return $endpoint['tags'][0] ?? '기타';
        });
    }

    /**
     * operationId 생성
     */
    private function generateOperationId(string $method, string $path): string
    {
        $cleaned = preg_replace('/[^a-zA-Z0-9]/', '_', $path);
        $cleaned = preg_replace('/_+/', '_', $cleaned);
        $cleaned = trim($cleaned, '_');

        return strtolower($method).'_'.$cleaned;
    }

    /**
     * API 서버 정보
     */
    public function getServers(): array
    {
        $spec = $this->parse();

        return $spec['servers'] ?? [];
    }

    /**
     * API 기본 정보
     */
    public function getInfo(): array
    {
        $spec = $this->parse();

        return $spec['info'] ?? [];
    }

    /**
     * 캐시 초기화
     */
    public function clearCache(): void
    {
        $cacheKey = config('api-explorer.cache.key', 'api_explorer_openapi_spec');
        Cache::forget($cacheKey);

        // 파일 수정 시간 포함된 키도 삭제
        if (File::exists($this->specPath)) {
            $fileModified = File::lastModified($this->specPath);
            Cache::forget($cacheKey.'_'.$fileModified);
        }

        $this->spec = null;
    }
}
