<?php

namespace App\Console\Commands;

use App\DTOs\FlightStatData;
use App\Models\Aircraft;
use App\Models\Notification;
use App\Models\UserFlight;
use App\Services\FlightStatsService;
use Carbon\CarbonInterface;
use Illuminate\Console\Attributes\Description;
use Illuminate\Console\Attributes\Signature;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Log;

#[Signature('app:get-aircraft-info-for-arrived-flights')]
#[Description('Search for flights that have arrived recently and try and determine any new aircraft info.')]
class GetAircraftInfoForArrivedFlights extends Command
{
    public function __construct(protected FlightStatsService $flightStats)
    {
        parent::__construct();
    }

    /**
     * Execute the console command.
     */
    public function handle(): void
    {
        $now = now()->utc();

        $userFlights = UserFlight::where('arrival_date', '<=', $now->copy()->subHour()->toDateTimeString())
            ->where('auto_update', true)
            ->whereNotNull('flight_number')
            ->get();

        $this->info("Found {$userFlights->count()} flights.");

        foreach ($userFlights as $flight) {
            $this->processFlight($flight);
        }
    }

    protected function processFlight(UserFlight $flight): void
    {
        [$airlineCode, $flightNumber] = $this->splitFlightNumber($flight);

        // Case 1: unparseable flight number on our end — disable and move on, no notification.
        if ($airlineCode === null) {
            $this->warn("Could not parse flight number: {$flight->flight_number}");
            Log::warning("Could not parse flight number for auto-update", ['flight_id' => $flight->id, 'flight_number' => $flight->flight_number]);
            $flight->update(['auto_update' => false]);
            return;
        }

        $arrivalDate = $flight->arrival_date->setTimezone($flight->arrivalAirport->timezone);

        $data = $this->flightStats->fetchFlightData($airlineCode, $flightNumber, $arrivalDate);


        // Case 2: valid flight number, but the API had nothing for it.
        if (!$data) {
            $this->warn("No flight data returned for {$airlineCode}{$flightNumber}");
            $this->notifyLookupFailed($flight, $airlineCode, $flightNumber);
            return;
        }

        // Case 3: API returned data, but for the wrong route — treat like a failed lookup.
        if ($data->departure_iata !== $flight->departureAirport->iata_code ||
            $data->arrival_iata !== $flight->arrivalAirport->iata_code) {
            $this->warn("Airport mismatch for {$airlineCode}{$flightNumber} — API: {$data->departure_iata}→{$data->arrival_iata}, expected: {$flight->departureAirport->iata_code}→{$flight->arrivalAirport->iata_code}");
            Log::warning("Airport mismatch for {$airlineCode}{$flightNumber} — API: {$data->departure_iata}→{$data->arrival_iata}, expected: {$flight->departureAirport->iata_code}→{$flight->arrivalAirport->iata_code}");
            $this->notifyLookupFailed($flight, $airlineCode, $flightNumber);
            return;
        }

        // Case 4/5: valid data — apply whatever changed, but always report every field.
        $this->applyAndNotify($flight, $data, $airlineCode, $flightNumber);
    }

    protected function splitFlightNumber(UserFlight $flight): array
    {
        $raw = strtoupper($flight->flight_number);
        $airline = $flight->airline;

        // Preferred: anchor on the airline's known codes rather than guessing.
        foreach (array_filter([$airline?->iata_code, $airline?->icao_code]) as $code) {
            $code = strtoupper($code);
            if (str_starts_with($raw, $code) && preg_match('/^\d+$/', substr($raw, strlen($code)))) {
                return [$code, substr($raw, strlen($code))];
            }
        }

        // Fallback: regex guess.
        // Covers: letter+digit (J2), digit+letter (9K, 4U), and 2-3 letter codes (AA, DAL).
        if (preg_match('/^([A-Z]\d|\d[A-Z]|[A-Z]{2,3})(\d+)$/', $raw, $matches)) {
            return [$matches[1], $matches[2]];
        }

        return [null, null];
    }

    protected function notifyLookupFailed(UserFlight $flight, string $airlineCode, string $flightNumber): void
    {
        Notification::create([
            'user_id' => $flight->user_id,
            'title'   => "Auto update failed for {$flight->flight_number}",
            'body'    => "We tried to find up-to-date information for {$airlineCode}{$flightNumber}, but couldn't find a reliable match. Please manually check the aircraft type, registration and departure/arrival times.",
            'url'     => '/flights/' . $flight->id . '/edit',
        ]);

        $flight->update(['auto_update' => false]);
    }

    protected function applyAndNotify(UserFlight $flight, FlightStatData $data, string $airlineCode, string $flightNumber): void
    {
        // Snapshot originals before we touch anything, so descriptions reflect the real "from" state.
        $originalRegistration = $flight->aircraft_registration;
        $originalDeparture    = $flight->departure_date;
        $originalArrival      = $flight->arrival_date;
        $originalAircraft     = $flight->aircraft;

        $updates = [];
        $aircraftNotFound = false;

        if ($data->aircraft_registration && $data->aircraft_registration !== $originalRegistration) {
            $updates['aircraft_registration'] = $data->aircraft_registration;
        }

        if ($data->estimated_departure_utc?->ne($originalDeparture)) {
            $updates['departure_date'] = $data->estimated_departure_utc;
            $updates['departure_processed_at'] = $data->estimated_departure_utc;
        }

        if ($data->estimated_arrival_utc?->ne($originalArrival)) {
            $updates['arrival_date'] = $data->estimated_arrival_utc;
            $updates['arrival_processed_at'] = $data->estimated_arrival_utc;
        }

        if ($data->equipment_iata && $originalAircraft?->iata_code !== $data->equipment_iata) {
            $match = $this->flightStats->guessAircraftFromIata($data->equipment_iata);

            if ($match) {
                $updates['aircraft_id'] = $match->id;
            } else {
                $aircraftNotFound = true;
                Log::warning("No aircraft match for IATA code {$data->equipment_iata} on flight {$airlineCode}{$flightNumber}", [
                    'flight_id' => $flight->id,
                ]);
            }
        }

        if (!empty($updates)) {
            $flight->update($updates);
            $this->info("Updated flight {$airlineCode}{$flightNumber}: " . implode(', ', array_keys($updates)));
        } else {
            $this->info("No changes for {$airlineCode}{$flightNumber}");

        }

        $flight->update(['auto_update' => false]);

        $newAircraft = isset($updates['aircraft_id']) ? Aircraft::find($updates['aircraft_id']) : $originalAircraft;

        $lines = [
            $this->describeValue(
                'Aircraft Registration',
                $originalRegistration,
                $data->aircraft_registration,
                $updates['aircraft_registration'] ?? null,
            ),
            $this->describeDate('Departure', $originalDeparture, $data->estimated_departure_utc, $updates['departure_date'] ?? null, $flight->departureAirport->timezone),
            $this->describeDate('Arrival', $originalArrival, $data->estimated_arrival_utc, $updates['arrival_date'] ?? null, $flight->arrivalAirport->timezone),
            $this->describeAircraftType($originalAircraft, $newAircraft, $data->equipment_iata, $aircraftNotFound),
        ];

        $title = $aircraftNotFound
            ? "Flight {$airlineCode}{$flightNumber} updated — aircraft type not found"
            : "Flight {$airlineCode}{$flightNumber} updated";

        Notification::create([
            'user_id' => $flight->user_id,
            'title'   => $title,
            'body'    => implode("\n", $lines),
            'url'     => '/u/' . $flight->user->name . '/flight/' . $flight->id,
        ]);
    }

    /**
     * Describe a simple string field: from → to, or "no change" / "not found" as appropriate.
     */
    protected function describeValue(string $label, ?string $from, ?string $apiValue, ?string $applied): string
    {
        $fromLabel = $from ?: 'None';

        if (!$apiValue) {
            return "{$label}: {$fromLabel} (could not find — no change)";
        }

        if (!$applied) {
            return "{$label}: {$apiValue} (no change)";
        }

        return "{$label}: {$fromLabel} → {$apiValue}";
    }

    protected function describeDate(string $label, ?CarbonInterface $from, ?CarbonInterface $apiValue, mixed $applied, string $timezone): string
    {
        $toLocal   = $apiValue?->copy()->setTimezone($timezone);
        $fromLocal = $from?->copy()->setTimezone($timezone);

        if (!$toLocal) {
            $fromLabel = $fromLocal ? $this->formatFlightTime($fromLocal) : 'Unknown';
            return "{$label}: {$fromLabel} (could not find — no change)";
        }

        if (!$applied) {
            return "{$label}: " . $this->formatFlightTime($toLocal) . " (no change)";
        }

        // Only bother showing the date if it actually changed (e.g. delayed past midnight).
        $showDate = !$fromLocal || !$fromLocal->isSameDay($toLocal);
        $showYear = $showDate && $fromLocal && $fromLocal->year !== $toLocal->year;

        $fromLabel = $fromLocal ? $this->formatFlightTime($fromLocal, $showDate, $showYear) : 'Unknown';
        $toLabel   = $this->formatFlightTime($toLocal, $showDate, $showYear);

        return "{$label}: {$fromLabel} → {$toLabel}";
    }

    protected function formatFlightTime(CarbonInterface $time, bool $showDate = false, bool $showYear = false): string
    {
        $datePart = $showDate ? $time->format($showYear ? 'j M Y, ' : 'j M, ') : '';

        return $datePart . $time->format('g:ia');
    }

    protected function describeAircraftType(?Aircraft $from, ?Aircraft $to, ?string $equipmentIata, bool $notFound): string
    {
        $fromLabel = $from?->display_name_short ?? 'None';

        if (!$equipmentIata) {
            return "Aircraft Type: {$fromLabel} (not returned by API — no change)";
        }

        if ($notFound) {
            return "Aircraft Type: {$fromLabel} (API reported equipment code {$equipmentIata}, but no matching aircraft was found in our database — no change)";
        }

        if ($to?->id === $from?->id) {
            return "Aircraft Type: {$fromLabel} (no change)";
        }

        return "Aircraft Type: {$fromLabel} → {$to?->display_name_short}";
    }
}
