<?php

namespace App\Command;

use Doctrine\DBAL\Connection;
use Psr\Log\LoggerInterface;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\DependencyInjection\Attribute\Autowire;

#[AsCommand(
    name: 'app:generate-tiles',
    description: 'Génère les vector tiles (Tippecanoe) pour observations et prédictions',
)]
class GenerateTilesCommand extends Command
{
    private const TILE_HORIZONS = [6, 12, 24, 48];
    private const MIN_ZOOM      = 5;
    private const MAX_ZOOM      = 12;

    public function __construct(
        private Connection $connection,
        #[Autowire('%kernel.project_dir%')] private string $projectDir,
        private LoggerInterface $logger,
    ) {
        parent::__construct();
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $io = new SymfonyStyle($input, $output);
        $io->title('Génération vector tiles');

        $tilesDir = $this->projectDir . '/public/tiles';

        // Observations
        $io->section('Observations');
        $geojson = $this->queryObservationsGeoJSON();
        if ($geojson !== null) {
            $this->runTippecanoe($geojson, $tilesDir . '/observations', 'observations', $io);
        } else {
            $io->warning('Aucune observation récente — tiles non générées.');
        }

        // Prévisions par horizon
        foreach (self::TILE_HORIZONS as $horizon) {
            $io->section("Prévisions H+{$horizon}");
            $geojson = $this->queryForecastsGeoJSON($horizon);
            if ($geojson !== null) {
                $this->runTippecanoe($geojson, $tilesDir . '/forecasts/h' . $horizon, 'forecasts', $io);
            } else {
                $io->warning("Aucune prévision H+{$horizon} — tiles non générées.");
            }
        }

        $io->success('Génération terminée.');
        return Command::SUCCESS;
    }

    private function queryObservationsGeoJSON(): ?string
    {
        $rows = $this->connection->fetchAllAssociative(
            "SELECT ST_AsGeoJSON(geometry) AS geometry,
                    COALESCE(afai_mean, 0) AS afai,
                    source,
                    confidence,
                    ROUND(COALESCE(coverage_area, 0)::numeric, 1) AS coverage_km2
             FROM sargassum_observation
             WHERE detected_at >= NOW() - INTERVAL '30 days'
               AND coverage_area > 0
             ORDER BY detected_at DESC"
        );

        return $this->buildFeatureCollection($rows, [
            'afai'        => static fn($r) => (float) $r['afai'],
            'source'      => static fn($r) => $r['source'],          // 'Sentinel-2' | 'Sentinel-3'
            'confidence'  => static fn($r) => (float) $r['confidence'],
            'coverage_km2' => static fn($r) => (float) $r['coverage_km2'],
        ]);
    }

    private function queryForecastsGeoJSON(int $horizon): ?string
    {
        $rows = $this->connection->fetchAllAssociative(
            "SELECT ST_AsGeoJSON(geometry) AS geometry, confidence
             FROM sargassum_forecast
             WHERE time_horizon = :horizon
               AND computed_at >= NOW() - INTERVAL '30 days'
             ORDER BY computed_at DESC",
            ['horizon' => $horizon]
        );

        return $this->buildFeatureCollection($rows, [
            'confidence' => static fn($r) => (float) $r['confidence'],
        ]);
    }

    /**
     * @param array<array<string, mixed>> $rows
     * @param array<string, callable(array<string, mixed>): mixed> $props
     */
    private function buildFeatureCollection(array $rows, array $props): ?string
    {
        if (empty($rows)) {
            return null;
        }

        $features = [];
        foreach ($rows as $row) {
            $properties = [];
            foreach ($props as $key => $fn) {
                $properties[$key] = $fn($row);
            }
            $features[] = [
                'type'       => 'Feature',
                'geometry'   => json_decode($row['geometry'], true, 512, JSON_THROW_ON_ERROR),
                'properties' => $properties,
            ];
        }

        return json_encode(
            ['type' => 'FeatureCollection', 'features' => $features],
            JSON_THROW_ON_ERROR
        );
    }

    private function runTippecanoe(string $geojson, string $outputDir, string $layer, SymfonyStyle $io): void
    {
        $parentDir = dirname($outputDir);
        if (!is_dir($parentDir)) {
            mkdir($parentDir, 0755, true);
        }

        $tmpFile = sys_get_temp_dir() . '/tiles_' . uniqid() . '.geojson';
        file_put_contents($tmpFile, $geojson);

        try {
            $cmd = sprintf(
                'tippecanoe -e %s --force --no-tile-compression --layer=%s -Z%d -z%d --drop-densest-as-needed --quiet %s 2>&1',
                escapeshellarg($outputDir),
                escapeshellarg($layer),
                self::MIN_ZOOM,
                self::MAX_ZOOM,
                escapeshellarg($tmpFile)
            );

            exec($cmd, $cmdOutput, $exitCode);

            if ($exitCode !== 0) {
                $errorMsg = implode("\n", $cmdOutput);
                $this->logger->error('tippecanoe failed', ['output' => $errorMsg, 'dir' => $outputDir]);
                $io->error("Tippecanoe a échoué : {$errorMsg}");
            } else {
                $io->success("→ {$outputDir}");
            }
        } finally {
            @unlink($tmpFile);
        }
    }
}
