<?php

namespace App\Service\SentinelHub;

use Psr\Cache\CacheItemPoolInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;

class SentinelHubClient
{
    // Copernicus Data Space Ecosystem (CDSE) — gratuit, financé par l'UE
    // https://documentation.dataspace.copernicus.eu/APIs/SentinelHub/Overview/Authentication.html
    private const AUTH_URL    = 'https://identity.dataspace.copernicus.eu/auth/realms/CDSE/protocol/openid-connect/token';
    private const PROCESS_URL = 'https://sh.dataspace.copernicus.eu/api/v1/process';
    private const CATALOG_URL = 'https://sh.dataspace.copernicus.eu/api/v1/catalog/1.0.0/search';

    public function __construct(
        private HttpClientInterface $httpClient,
        private CacheItemPoolInterface $cache,
        private string $clientId,
        private string $clientSecret,
    ) {}

    private function getToken(): string
    {
        $cacheKey = 'sentinel_hub_token';
        $item = $this->cache->getItem($cacheKey);

        if ($item->isHit()) {
            return $item->get();
        }

        $response = $this->httpClient->request('POST', self::AUTH_URL, [
            'body' => [
                'grant_type'    => 'client_credentials',
                'client_id'     => $this->clientId,
                'client_secret' => $this->clientSecret,
            ],
        ]);

        $token = $response->toArray()['access_token'];
        $item->set($token)->expiresAfter(3500);
        $this->cache->save($item);

        return $token;
    }

    /**
     * Retourne la couverture nuageuse (%) de la meilleure scène disponible.
     * collection : 'sentinel-2-l2a' (défaut) ou 'sentinel-3-olci'
     */
    /** @param array<int, float> $bbox */
    public function getCloudCoverage(array $bbox, \DateTimeImmutable $date, string $collection = 'sentinel-2-l2a'): float
    {
        // Sentinel-3 OLCI n'expose pas eo:cloud_cover dans le Catalog ; on renvoie 0
        // pour laisser le Process API décider.
        if ($collection === 'sentinel-3-olci') {
            return 0.0;
        }

        $token = $this->getToken();
        $dateStr = $date->format('Y-m-d');

        $response = $this->httpClient->request('POST', self::CATALOG_URL, [
            'headers' => ['Authorization' => "Bearer {$token}"],
            'json' => [
                'bbox'        => $bbox,
                'datetime'    => "{$dateStr}T00:00:00Z/{$dateStr}T23:59:59Z",
                'collections' => [$collection],
                'limit'       => 1,
            ],
        ]);

        $data = $response->toArray();

        if (empty($data['features'])) {
            return 100.0; // aucune scène disponible → rejet
        }

        return (float) ($data['features'][0]['properties']['eo:cloud_cover'] ?? 100.0);
    }

    /**
     * Télécharge un raster UINT8 binaire (1=sargasse, 0=non, 255=nuage/nodata)
     * calculé via evalscript AFAI/FAI, et le sauvegarde dans $outputPath.
     * collection : 'sentinel-2-l2a' (défaut) ou 'sentinel-3-olci'
     */
    /**
     * @param array<int, float> $bbox
     * @return array{tileId: string, afaiMean: float, afaiStd: float}
     */
    public function downloadAfaiBinary(
        array $bbox,
        \DateTimeImmutable $date,
        string $outputPath,
        string $collection = 'sentinel-2-l2a',
    ): array {
        $token = $this->getToken();
        $dateStr = $date->format('Y-m-d');

        $evalscript = $collection === 'sentinel-3-olci'
            ? $this->getFaiBinaryEvalscriptS3()
            : $this->getAfaiBinaryEvalscript();

        $response = $this->httpClient->request('POST', self::PROCESS_URL, [
            'headers' => ['Authorization' => "Bearer {$token}"],
            'json' => [
                'input' => [
                    'bounds' => [
                        'bbox'       => $bbox,
                        'properties' => ['crs' => 'http://www.opengis.net/def/crs/OGC/1.3/CRS84'],
                    ],
                    'data' => [[
                        'type'       => $collection,
                        'dataFilter' => [
                            'timeRange' => [
                                'from' => "{$dateStr}T00:00:00Z",
                                'to'   => "{$dateStr}T23:59:59Z",
                            ],
                        ],
                    ]],
                ],
                'output' => [
                    'width'     => 666,
                    'height'    => 1222,
                    'responses' => [[
                        'identifier' => 'default',
                        'format'     => ['type' => 'image/tiff'],
                    ]],
                ],
                'evalscript' => $evalscript,
            ],
        ]);

        file_put_contents($outputPath, $response->getContent());

        $headers = $response->getHeaders();

        return [
            'tileId'   => $headers['x-process-request-id'][0] ?? uniqid('tile_'),
            'afaiMean' => 0.0,
            'afaiStd'  => 0.0,
        ];
    }

    private function getAfaiBinaryEvalscript(): string
    {
        // Longueurs d'onde Sentinel-2 (nm) — constantes capteur
        // B04 (Red) : 664.5 | B08 (NIR) : 832.8 | B11 (SWIR1) : 1613.7
        //
        // Seuil AFAI : 0.015 (relevé de 0.005)
        // → 0.005 génère des faux positifs massifs (sun glint, eaux côtières turbides)
        // → 0.015 correspond à une densité minimale réelle de Sargassum fluitans/natans
        //    selon Hu (2009), Wang & Hu (2016) : AFAI > 0.010-0.020 pour détection fiable
        return <<<'EVALSCRIPT'
//VERSION=3
function setup() {
  return {
    input: [{ bands: ["B03", "B04", "B08", "B11", "CLP"], units: "REFLECTANCE" }],
    output: { bands: 1, sampleType: "UINT8" }
  };
}

function evaluatePixel(sample) {
  if (sample.CLP > 0.4) { return [255]; } // nodata : nuage

  // Masque nuage
  if (sample.CLP > 0.3) { return [255]; }

  // SWIR élevé = artifact atmo, cirrus fin, aérosol, eau côtière turbide
  if (sample.B11 > 0.03) { return [0]; }

  // NIR élevé = sun glint ou eau peu profonde
  if (sample.B08 > 0.06) { return [0]; }

  // Masque eau : NDWI = (Green - NIR) / (Green + NIR) — terre si <= 0
  const ndwiDenom = sample.B03 + sample.B08;
  if (ndwiDenom === 0) { return [0]; }
  const ndwi = (sample.B03 - sample.B08) / ndwiDenom;
  if (ndwi <= 0.05) { return [0]; } // marge pour eau côtière peu profonde

  const lambdaRed  = 664.5;
  const lambdaNir  = 832.8;
  const lambdaSwir = 1613.7;
  const ratio = (lambdaNir - lambdaRed) / (lambdaSwir - lambdaRed);

  const afai = sample.B08 - sample.B04 - (sample.B11 - sample.B04) * ratio;

  // Seuil 0.020 : calibré pour Sargassum fluitans/natans en Caraïbe
  // (Hu 2009 recommande post-filtrage spatial que l'on fait via PostGIS)
  return [afai > 0.020 ? 1 : 0];
}
EVALSCRIPT;
    }

    /**
     * Evalscript Sentinel-3 OLCI — FAI (Floating Algae Index) via MCI.
     *
     * OLCI bands (nm) : Oa08=665 (Red), Oa11=708.75 (RedEdge), Oa17=865 (NIR)
     * MCI = Oa17 − Oa08 − (Oa11 − Oa08) × (865−665)/(708.75−665)
     * Seuil empirique retenu : MCI > 0.008
     */
    private function getFaiBinaryEvalscriptS3(): string
    {
        // Seuil MCI : 0.015 (relevé de 0.008)
        // → même logique que S2 : 0.008 trop sensible pour la Caraïbe
        return <<<'EVALSCRIPT'
//VERSION=3
function setup() {
  return {
    input: [{ bands: ["B06", "B08", "B11", "B17"], units: "REFLECTANCE" }],
    output: { bands: 1, sampleType: "UINT8" }
  };
}

function evaluatePixel(sample) {
  // NIR élevé = sun glint
  if (sample.B17 > 0.06) { return [0]; }

  // Masque eau : NDWI = (Green B06 560nm - NIR B17 865nm) / (Green + NIR)
  const ndwiDenom = sample.B06 + sample.B17;
  if (ndwiDenom === 0) { return [0]; }
  const ndwi = (sample.B06 - sample.B17) / ndwiDenom;
  if (ndwi <= 0.05) { return [0]; }

  const lambdaRed     = 665.0;
  const lambdaRedEdge = 708.75;
  const lambdaNir     = 865.0;

  const baseline = sample.B08 + (sample.B11 - sample.B08) * (lambdaNir - lambdaRed) / (lambdaRedEdge - lambdaRed);
  const mci = sample.B17 - baseline;

  return [mci > 0.020 ? 1 : 0];
}
EVALSCRIPT;
    }
}
