<?php

namespace App\Http\Controllers;

use App\Models\Aircraft;
use App\Models\Airline;
use App\Models\Airport;
use App\Models\CrewType;
use App\Models\FlightClass;
use App\Models\FlightReason;
use App\Models\IataEquipmentCode;
use App\Models\Notification;
use App\Models\SeatType;
use App\Models\User;
use App\Models\UserAction;
use App\Models\UserFlight;
use App\Services\FlightStatsService;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Inertia\Inertia;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
class FlightController extends Controller
{
    use AuthorizesRequests;
    public function rules(): array
    {
        return [
            'flight_number'         => ['nullable', 'string', 'max:10'],
            'departure_date'        => ['required', 'date'],
            'arrival_date'          => ['required', 'date', function ($attribute, $value, $fail) {
                $from = request()->input('from_id');
                $to   = request()->input('to_id');

                if (!$from || !$to) return;

                $departureAirport = Airport::find($from);
                $arrivalAirport   = Airport::find($to);

                if (!$departureAirport || !$arrivalAirport) return;

                $departureUtc = Carbon::createFromFormat('Y-m-d\TH:i', request()->input('departure_date'), $departureAirport->timezone)->utc();
                $arrivalUtc   = Carbon::createFromFormat('Y-m-d\TH:i', $value, $arrivalAirport->timezone)->utc();

                if ($arrivalUtc->lessThanOrEqualTo($departureUtc)) {
                    $fail('The arrival time must be after the departure time, accounting for time zones.');
                }
            }],
            'from_id'               => ['required', 'integer', 'exists:airports,id'],
            'to_id'                 => ['required', 'integer', 'exists:airports,id'],
            'airline_id'            => ['nullable', 'integer', 'exists:airlines,id'],
            'aircraft_id'           => ['nullable', 'integer', 'exists:aircraft,id'],
            'aircraft_registration' => ['nullable', 'string', 'max:10'],
            'seat_number'           => ['nullable', 'string', 'max:10'],
            'seat_type_id'          => ['integer', 'exists:seat_types,id'],
            'flight_class_id'       => ['integer', 'exists:flight_classes,id'],
            'flight_reason_id'      => ['integer', 'exists:flight_reasons,id'],
            'note'                  => ['nullable', 'string', 'max:5000'],
            'auto_update'           => ['boolean'],
            'crew_type_id' => ['nullable', 'exists:crew_types,id'],
        ];
    }

    public function lookup(Request $request)
    {
        $number = strtoupper(trim($request->query('number', '')));
        preg_match(
            '/^([A-Z]{3}|[A-Z]{2}|[A-Z]\d|\d[A-Z])(\d+)$/',
            $number,
            $matches
        );
        $code         = $matches[1] ?? null;
        $flightNumber = $matches[2] ?? null;
        $isIata       = $code !== null && strlen($code) === 2;
        $codeColumn   = $isIata ? 'iata_code' : 'icao_code';

        $apiAirlineCodes = [];
        $fromOptions     = [];
        $toOptions       = [];
        $aircraftOptions = [];
        $flightData      = null;

        if (strlen($number) >= 3 && $isIata) {
            $flightStatsApi = new FlightStatsService();
            $flightData = $flightStatsApi->fetchFlightData($code, $flightNumber);

            if ($flightData) {
                $apiAirlineCodes = $flightData->airline_fs_codes;

                $fromOptions = Airport::where('iata_code', $flightData->departure_iata)
                    ->get()
                    ->map(fn($a) => ['value' => $a->id, 'title' => $a->display_name, 'country_code' => strtolower($a->region->country->code)])
                    ->values()
                    ->toArray();

                $toOptions = Airport::where('iata_code', $flightData->arrival_iata)
                    ->get()
                    ->map(fn($a) => ['value' => $a->id, 'title' => $a->display_name, 'country_code' => strtolower($a->region->country->code)])
                    ->values()
                    ->toArray();

                if($flightData->equipment_iata){
                    $equipment = IataEquipmentCode::where('iata_code', $flightData->equipment_iata)->first();
                    if ($equipment) {
                        $bestGuess = $flightStatsApi->guessAircraftFromIata($flightData->equipment_iata);

                        $aircraftOptions = Aircraft::where('designator', $equipment->icao_code)
                            ->get()
                            ->sortBy(fn($a) => $a->id === $bestGuess?->id ? 0 : 1)
                            ->map(fn($a) => ['value' => $a->id, 'title' => $a->display_name])
                            ->values()
                            ->toArray();
                    }
                }
            }
        }

        // Airlines from the typed code + any additional codes from the API, merged and deduped by id
        $allCodes = array_unique(array_filter([$code, ...$apiAirlineCodes]));

        $airlines = Airline::where(function ($q) use ($codeColumn, $code, $allCodes) {
            $q->whereIn($codeColumn, $allCodes);
        })
            ->get()
            ->unique('id')
            ->sortBy(function ($a) use ($code, $flightData) {
                if ($a->iata_code === $flightData?->operating_fs) return 0;
                if ($a->iata_code === $code) return 1;
                return 2;
            })
            ->map(fn($a) => ['value' => $a->id, 'title' => $a->display_name, 'logo_url' => $a->logo_url])
            ->values()
            ->toArray();

        return response()->json([
            'airline_options'  => $airlines,
            'from_options'     => $fromOptions,
            'to_options'       => $toOptions,
            'aircraft_options' => $aircraftOptions,
            'scheduled_departure_time' => $flightData?->scheduled_departure_local?->format('H:i'),
            'scheduled_arrival_time'   => $flightData?->scheduled_arrival_local?->format('H:i'),
        ]);
    }

    private function convertedDates(array $validated): array
    {
        $departureAirport = Airport::find($validated['from_id']);
        $arrivalAirport   = Airport::find($validated['to_id']);

        return [
            Carbon::createFromFormat('Y-m-d\TH:i', $validated['departure_date'], $departureAirport->timezone)->utc(),
            Carbon::createFromFormat('Y-m-d\TH:i', $validated['arrival_date'],   $arrivalAirport->timezone)->utc(),
        ];
    }

    private function recordChanges(UserFlight $flight, array $dirty, array $original, array $updated): void
    {
        $changes = [];
        foreach ($dirty as $field => $newValue) {
            $changes[] = $this->formatChange($field, $flight->getOriginal($field), $newValue);
        }

        UserAction::create([
            'user_id'        => $flight->user_id,
            'data'           => [
                'changes'  => $changes,
                'original' => $original,
                'updated'  => $updated,
            ],
            'type'           => 'flight_updated',
        ]);
    }

    private array $labelCache = [];

    private function resolveLabel(string $field, mixed $value): string
    {
        if (is_null($value)) {
            return 'none';
        }

        $cacheKey = "{$field}:{$value}";

        if (isset($this->labelCache[$cacheKey])) {
            return $this->labelCache[$cacheKey];
        }

        $label = match($field) {

            'airline_id'          => Airline::find($value)?->display_name ?? $value,
            'departure_airport_id',
            'arrival_airport_id'  => Airport::find($value)?->display_name ?? $value,
            'aircraft_id'         => Aircraft::find($value)?->display_name_short ?? $value,
            'seat_type_id'        => SeatType::find($value)?->name ?? $value,
            'flight_class_id'     => FlightClass::find($value)?->name ?? $value,
            'flight_reason_id'    => FlightReason::find($value)?->name ?? $value,
            'crew_type_id'        => CrewType::find($value)?->name ?? $value,
            'departure_date',
            'arrival_date'        => Carbon::parse($value)->format('j F Y \a\t H:iA'),
            default               => (string) $value,
        };

        return $this->labelCache[$cacheKey] = $label;
    }

    private function formatChange(string $field, mixed $from, mixed $to): array
    {
        $label = str($field)->replace('_id', '')->replace('_', ' ')->title();
        $fromLabel = $this->resolveLabel($field, $from);
        $toLabel   = $this->resolveLabel($field, $to);

        return [
            'field' => $field,
            'from'  => $fromLabel,
            'to'    => $toLabel,
        ];
    }

    private function flightPayload(array $validated): array
    {
        [$departureUtc, $arrivalUtc] = $this->convertedDates($validated);
        return [
            'departure_date'        => $departureUtc,
            'arrival_date'          => $arrivalUtc,
            'flight_number'         => $validated['flight_number'],
            'departure_airport_id'  => $validated['from_id'],
            'arrival_airport_id'    => $validated['to_id'],
            'airline_id'            => $validated['airline_id'],
            'aircraft_id'           => $validated['aircraft_id'],
            'aircraft_registration' => $validated['aircraft_registration'],
            'seat_number'           => $validated['seat_number'],
            'seat_type_id'          => $validated['seat_type_id'],
            'flight_class_id'       => $validated['flight_class_id'],
            'flight_reason_id'      => $validated['flight_reason_id'],
            'note'                  => $validated['note'],
            'auto_update'           => $validated['auto_update'],
            'crew_type_id' => $validated['crew_type_id'],
        ];
    }

    public function store(Request $request)
    {
        $validated = $request->validate($this->rules());

        $newFlight = auth()->user()->flights()->create($this->flightPayload($validated));

        UserAction::create([
            'user_id'        => $newFlight->user_id,
            'type'           => $newFlight->departure_date->isFuture() ? 'flight_booked' : 'flight_logged',
            'data'           => [
                'flight' => $newFlight->snapshot($newFlight->id),
            ],
        ]);


        return redirect()->route('profile.departure-board', [Auth::user()->name, $newFlight->id]);
    }


    public function update(Request $request, UserFlight $flight)
    {
        $this->authorize('update', $flight);

        $validated = $request->validate($this->rules());

        $flight->fill($this->flightPayload($validated));

        if (!$flight->isDirty()) {
            return redirect()->route('profile.departure-board', [Auth::user()->name, $flight->id]);
        }

        $dirty = $flight->getDirty();
        $original = $flight->snapshot($flight->id);

        $flight->save();

        $updated = $flight->snapshot($flight->id);
        $this->recordChanges($flight, $dirty, $original, $updated);

        return redirect()->route('profile.departure-board', [$flight->user->name, $flight->id]);
    }

    public function delete(UserFlight $flight, ?string $referrer = 'departure-board')
    {
        $this->authorize('delete', $flight);

        $snapshot = $flight->snapshot($flight->id);
        $isFuture = now()->utc()->isBefore($flight->departure_date);

        UserAction::create([
            'user_id' => $flight->user_id,
            'type'    => $isFuture ? 'flight_cancelled' : 'flight_deleted',
            'data'    => [
                'flight' => $snapshot,
            ],
        ]);

        $flight->delete();

        return redirect()->route('profile.'.$referrer, [Auth::user()->name]);
    }

    public function staticData() : array {
        return [
            'seat_types'     => SeatType::orderBy('id')->get()->toArray(),
            'flight_reasons' => FlightReason::orderBy('id')->get()->toArray(),
            'flight_classes' => FlightClass::orderBy('id')->get()->toArray(),
            'crew_types' => CrewType::orderBy('id')->get()->toArray(),
        ];
    }

    public function add(){
        return Inertia::render('AddFlight', $this->staticData());
    }

    public function edit(UserFlight $flight)
    {
        $this->authorize('update', $flight);
        $flight->load('airline', 'aircraft', 'departureAirport.region.country', 'arrivalAirport.region.country', 'seatType', 'flightClass', 'flightReason');
        $flightData = [
            'id'                    => $flight->id,
            'flight_number'         => $flight->flight_number,
            'departure_date' => $flight->departure_date->setTimezone($flight->departureAirport->timezone)->format('Y-m-d\TH:i'),
            'arrival_date'   => $flight->arrival_date->setTimezone($flight->arrivalAirport->timezone)->format('Y-m-d\TH:i'),
            'aircraft_registration' => $flight->aircraft_registration,
            'seat_number'           => $flight->seat_number,
            'note'                  => $flight->note,
            'auto_update'           => $flight->auto_update,
            'seat_type'             => $flight->seatType->toArray(),
            'flight_class'          => $flight->flightClass->toArray(),
            'crew_type'           =>     $flight->crewType?->toArray() ?? [],
            'flight_reason'         => $flight->flightReason->toArray(),
            'airline_options'       => $flight->airline
                ? [['value' => $flight->airline->id, 'title' => $flight->airline->display_name, 'logo_url' => $flight->airline->logo_url]]
                : [],
            'from_options'          => [['value' => $flight->departureAirport->id, 'title' => $flight->departureAirport->display_name, 'country_code' => strtolower($flight->departureAirport->region->country->code)]],
            'to_options'            => [['value' => $flight->arrivalAirport->id,   'title' => $flight->arrivalAirport->display_name,   'country_code' => strtolower($flight->arrivalAirport->region->country->code)]],
            'aircraft_options'      => $flight->aircraft
                ? [['value' => $flight->aircraft->id, 'title' => $flight->aircraft->display_name]]
                : [],
        ];
        return Inertia::render('AddFlight', [
            'flight' => $flightData,
            ...$this->staticData(),
        ]);
    }
}
