<?php

declare(strict_types=1);

namespace App\Services;

use App\Contracts\Metar as MetarProvider;
use App\Contracts\Service;
use App\Exceptions\AirportNotFound;
use App\Models\Airport;
use App\Support\Dto\PhpvmsApi\AirportData;
use App\Support\Metar;
use App\Support\Units\Distance;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;
use League\Geotools\Coordinate\Coordinate;
use League\Geotools\Geotools;
use PhpUnitsOfMeasure\Exception\NonNumericValue;
use PhpUnitsOfMeasure\Exception\NonStringUnitName;

class AirportService extends Service
{
    public function __construct(
        private readonly MetarProvider $metarProvider
    ) {}

    /**
     * Return the METAR for a given airport
     */
    public function getMetar(?string $icao): ?Metar
    {
        if ($icao === null) {
            return null;
        }

        $icao = trim($icao);
        if ($icao === '') {
            return null;
        }

        $raw_metar = $this->metarProvider->metar($icao);
        if ($raw_metar !== '' && $raw_metar !== '0') {
            return new Metar($raw_metar);
        }

        return null;
    }

    /**
     * Return the METAR for a given airport
     */
    public function getTaf(?string $icao): ?Metar
    {
        if ($icao === null) {
            return null;
        }

        $icao = trim($icao);
        if ($icao === '') {
            return null;
        }

        $raw_taf = $this->metarProvider->taf($icao);
        if ($raw_taf !== '' && $raw_taf !== '0') {
            return new Metar($raw_taf, true);
        }

        return null;
    }

    /**
     * Lookup an airport's information from a remote provider. This handles caching
     * the data internally
     */
    public function lookupAirport(string $icao): array
    {
        $key = config('cache.keys.AIRPORT_VACENTRAL_LOOKUP.key').$icao;

        $airport = Cache::get($key);
        if ($airport) {
            return $airport;
        }

        $response = Http::get('https://api.phpvms.net/v1/airports/'.$icao);

        if (!$response->successful()) {
            return [];
        }

        $airportData = AirportData::from($response->json());

        $airport = $airportData->toArray();

        Cache::add(
            $key,
            $airport,
            config('cache.keys.AIRPORT_VACENTRAL_LOOKUP.time')
        );

        return $airport;
    }

    /**
     * Lookup an airport and save it if it hasn't been found
     *
     * @param string $icao
     */
    public function lookupAirportIfNotFound($icao): ?Airport
    {
        $icao = strtoupper($icao);
        $airport = Airport::find($icao);
        if ($airport !== null) {
            return $airport;
        }

        // Don't lookup the airport, so just add in something generic
        if (!setting('general.auto_airport_lookup')) {
            $airport = new Airport([
                'id'   => $icao,
                'icao' => $icao,
                'name' => $icao,
                'lat'  => 0,
                'lon'  => 0,
            ]);

            $airport->save();

            return $airport;
        }

        $lookup = $this->lookupAirport($icao);
        if ($lookup === []) {
            return null;
        }

        $airport = new Airport($lookup);
        $airport->save();

        return $airport;
    }

    /**
     * Calculate the distance from one airport to another
     */
    public function calculateDistance(string $fromIcao, string $toIcao): ?Distance
    {
        $from = Airport::find($fromIcao, ['lat', 'lon']);
        $to = Airport::find($toIcao, ['lat', 'lon']);

        if (!$from) {
            throw new AirportNotFound($fromIcao);
        }

        if (!$to) {
            throw new AirportNotFound($toIcao);
        }

        // Calculate the distance
        $geotools = new Geotools();
        $start = new Coordinate([$from->lat, $from->lon]);
        $end = new Coordinate([$to->lat, $to->lon]);
        /** @var \League\Geotools\Distance\Distance $dist */
        $dist = $geotools->distance()->setFrom($start)->setTo($end)->in('mi');

        // Convert into a Distance object
        try {
            return new Distance($dist->greatCircle(), 'mi');
        } catch (NonNumericValue|NonStringUnitName) {
            return null;
        }
    }
}
