<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\Route;

class CheckOpenApiCoverage extends Command
{
    protected $signature = 'clinic:check-openapi {--file=docs/openapi.yaml : OpenAPI document path}';

    protected $description = 'Fail when an implemented /api/v1 route is missing from the OpenAPI contract.';

    public function handle(): int
    {
        $file = base_path((string) $this->option('file'));
        if (! is_file($file)) {
            $this->error("OpenAPI document not found: {$file}");

            return self::FAILURE;
        }

        $documented = $this->parseOperations((string) file_get_contents($file));
        $implemented = [];

        foreach (Route::getRoutes() as $route) {
            $uri = $route->uri();
            if (! str_starts_with($uri, 'api/v1')) {
                continue;
            }

            $path = substr($uri, strlen('api/v1')) ?: '/';
            if (! str_starts_with($path, '/')) {
                $path = '/'.$path;
            }

            foreach ($route->methods() as $method) {
                $method = strtolower($method);
                if (in_array($method, ['head', 'options'], true)) {
                    continue;
                }
                $implemented[$method.' '.$path] = true;
            }
        }

        $missing = array_values(array_diff(array_keys($implemented), array_keys($documented)));
        sort($missing);

        if ($missing !== []) {
            $this->error('OpenAPI is missing implemented operations:');
            foreach ($missing as $operation) {
                $this->line('  - '.$operation);
            }

            return self::FAILURE;
        }

        $this->info(sprintf('OpenAPI covers all %d implemented API operations.', count($implemented)));

        return self::SUCCESS;
    }

    /** @return array<string, true> */
    private function parseOperations(string $yaml): array
    {
        $operations = [];
        $insidePaths = false;
        $path = null;

        foreach (preg_split('/\R/', $yaml) ?: [] as $line) {
            if ($line === 'paths:') {
                $insidePaths = true;

                continue;
            }
            if ($insidePaths && $line !== '' && ! str_starts_with($line, ' ')) {
                break;
            }
            if (! $insidePaths) {
                continue;
            }

            if (preg_match('/^  (\/[^:]+):\s*$/', $line, $matches)) {
                $path = $matches[1];

                continue;
            }

            if ($path && preg_match('/^    (get|post|put|patch|delete):\s*$/', $line, $matches)) {
                $operations[$matches[1].' '.$path] = true;
            }
        }

        return $operations;
    }
}
