<?php

declare(strict_types=1);

namespace Yatra\Hooks;

use Yatra\Database\Tables\BookingsTable;
use Yatra\Database\Tables\TripAvailabilityDatesTable;
use Yatra\Repositories\AvailabilityRepository;
use Yatra\Repositories\BookingDepartureRepository;
use Yatra\Repositories\BookingRepository;
use Yatra\Repositories\DepartureRepository;
use Yatra\Services\CapacityService;
use Yatra\Services\WaitlistPromotionService;

class AvailabilityInventoryHooks
{
    public static function init(): void
    {
        add_action('yatra_booking_created', [self::class, 'onBookingCreated'], 10, 2);
        add_action('yatra_booking_deleted', [self::class, 'onBookingDeleted'], 10, 2);
        add_action('yatra_booking_status_changed', [self::class, 'onBookingStatusChanged'], 10, 3);
        
        // Add hooks to ensure departure capacity sync
        add_action('yatra_availability_updated', [self::class, 'onAvailabilityUpdate'], 10, 1);
        add_action('yatra_departure_saved', [self::class, 'onDepartureSave'], 10, 1);
    }

    public static function onBookingCreated(int $bookingId, $booking = null): void
    {
        self::syncForBooking($bookingId, $booking);
    }

    public static function onBookingDeleted(int $bookingId, $booking = null): void
    {
        self::syncForBooking($bookingId, $booking);
    }

    public static function onBookingStatusChanged(int $bookingId, string $oldStatus, string $newStatus): void
    {
        self::syncForBooking($bookingId, null);
    }

    private static function syncForBooking(int $bookingId, $booking = null): void
    {
        try {
            if (!is_object($booking)) {
                $repo = new BookingRepository();
                $booking = $repo->find($bookingId);
            }

            if (!is_object($booking)) {
                return;
            }

            $tripId = (int) ($booking->trip_id ?? 0);
            $availabilityId = isset($booking->availability_id) ? (int) $booking->availability_id : 0;
            $travelDate = (string) ($booking->travel_date ?? '');

            if ($tripId > 0 && $availabilityId <= 0 && $travelDate !== '') {
                $availabilityId = self::resolveAvailabilityIdForBooking($bookingId, $tripId, $travelDate);
                if ($availabilityId > 0) {
                    (new BookingRepository())->update($bookingId, ['availability_id' => $availabilityId]);
                }
            }

            if ($tripId <= 0 || $availabilityId <= 0) {
                return;
            }

            self::syncAvailabilityId($availabilityId, $tripId);
        } catch (\Throwable $e) {
            return;
        }
    }

    private static function syncAvailabilityId(int $availabilityId, int $tripId): void
    {
        $availabilityRepo = new AvailabilityRepository();
        $availability = $availabilityRepo->find($availabilityId);

        if (!$availability) {
            return;
        }

        $departureTime = isset($availability->departure_time) ? (string) $availability->departure_time : '';

        $bookingRepo = new BookingRepository();
        $bookedCount = $bookingRepo->getTotalTravelersByTripAndAvailability(
            $tripId,
            $availabilityId,
            BookingRepository::getCapacityConsumingBookingStatuses()
        );

        $seatsTotal = (int) ($availability->seats_total ?? 0);
        $seatsReserved = $bookedCount;
        $seatsAvailable = max(0, $seatsTotal - $bookedCount);

        $status = (string) ($availability->status ?? 'available');
        if (!in_array($status, ['blocked', 'closed', 'cancelled'], true)) {
            if ($seatsAvailable === 0) {
                $status = 'sold_out';
            } elseif ($seatsTotal > 0 && $seatsAvailable <= (int) ceil($seatsTotal * 0.2)) {
                $status = 'limited';
            } else {
                $status = 'available';
            }
        }

        // Update availability using repository
        $availabilityRepo->update($availabilityId, [
            'seats_available' => $seatsAvailable,
            'seats_reserved' => $seatsReserved,
            'status' => $status,
        ]);

        self::syncDepartureWithAvailability($tripId, $availability, $bookedCount, $seatsTotal);

        WaitlistPromotionService::processAvailableSlots($tripId, $availabilityId);
    }

    private static function syncDepartureWithAvailability(int $tripId, object $availability, int $bookedCount, int $seatsTotal): void
    {
        $departureRepo = new DepartureRepository();
        $date = (string) ($availability->departure_date ?? '');
        $time = (string) ($availability->departure_time ?? '');
        $normalizedTime = '';
        if ($time !== '') {
            $ts = strtotime($time);
            if ($ts !== false) {
                $normalizedTime = date('H:i:s', $ts);
            }
        }

        if ($date === '') {
            return;
        }

        // Find a matching departure by trip + date (+ time if provided)
        $departure = null;
        $candidateTimes = array_values(array_unique(array_filter([$normalizedTime, $time], function ($t) {
            return $t !== '';
        })));

        foreach ($candidateTimes as $candidateTime) {
            $departure = $departureRepo->findByTripIdAndStartDate($tripId, $date, $candidateTime);
            if ($departure) {
                break;
            }
        }
        if (!$departure) {
            $departure = $departureRepo->findByTripIdAndStartDate($tripId, $date, null);
        }

        if (!$departure) {
            return;
        }

        $updated = [
            'max_capacity' => $seatsTotal,
            'booked_count' => min($seatsTotal, max(0, $bookedCount)),
        ];

        $departureRepo->update((int) $departure->id, $updated);
    }

    /**
     * Hook to sync departure capacity when availability is updated
     */
    public static function onAvailabilityUpdate(int $availabilityId): void
    {
        $availabilityRepo = new AvailabilityRepository();
        $availability = $availabilityRepo->find($availabilityId);

        if ($availability) {
            self::syncAvailabilityId($availabilityId, (int) $availability->trip_id);
        }
    }

    /**
     * Hook to ensure departure capacity always matches availability on save
     */
    public static function onDepartureSave(int $departureId): void
    {
        // Prevent infinite loops
        static $processing = [];
        if (isset($processing[$departureId])) {
            return;
        }
        $processing[$departureId] = true;
        
        $departureRepo = new DepartureRepository();
        $departure = $departureRepo->find($departureId);
        
        if ($departure) {
            $date = $departure->start_date ?: $departure->date;
            
            // Get capacity from availability
            $capacityService = new CapacityService();
            $correctCapacity = $capacityService->getCapacityForDate($departure->trip_id, $date);
            
            if ($correctCapacity > 0 && $departure->max_capacity !== $correctCapacity) {
                // Remove hook temporarily to prevent infinite loop
                remove_action('yatra_departure_saved', [self::class, 'onDepartureSave'], 10);
                
                $departureRepo->update($departureId, [
                    'max_capacity' => $correctCapacity
                ]);
                
                // Re-add hook
                add_action('yatra_departure_saved', [self::class, 'onDepartureSave'], 10, 1);
            }
        }
        
        unset($processing[$departureId]);
    }

    /**
     * Infer yatra_new_trip_availability_dates.id when the booking row missed availability_id
     * (common when checkout omits it). Uses booking→departure time to disambiguate multiple slots per day.
     */
    private static function resolveAvailabilityIdForBooking(int $bookingId, int $tripId, string $travelDate): int
    {
        $availabilityRepo = new AvailabilityRepository();
        $bdRepo = new BookingDepartureRepository();
        $depId = $bdRepo->getDepartureIdForBooking($bookingId);

        if ($depId) {
            $depRepo = new DepartureRepository();
            $dep = $depRepo->find($depId);
            if (is_object($dep)) {
                $date = (string) ($dep->start_date ?? $dep->date ?? '');
                if ($date === '') {
                    $date = $travelDate;
                }
                $timeRaw = isset($dep->time) ? trim((string) $dep->time) : '';
                if ($timeRaw !== '' && $timeRaw !== '00:00:00') {
                    $candidates = array_values(array_unique(array_filter([
                        $timeRaw,
                        self::normalizeTimeForAvailabilityMatch($timeRaw),
                    ])));
                    foreach ($candidates as $t) {
                        $row = $availabilityRepo->findByTripIdAndDateTime($tripId, $date, $t, true);
                        if ($row && !empty($row->id)) {
                            return (int) $row->id;
                        }
                    }
                }
                $row = $availabilityRepo->findByTripIdAndDateTime($tripId, $date, null, true);
                if ($row && !empty($row->id)) {
                    return (int) $row->id;
                }
            }
        }

        $records = $availabilityRepo->findByTripAndDate($tripId, $travelDate);
        if (count($records) === 1 && !empty($records[0]->id)) {
            return (int) $records[0]->id;
        }

        return 0;
    }

    private static function normalizeTimeForAvailabilityMatch(string $time): string
    {
        $ts = strtotime($time);

        return $ts !== false ? date('H:i:s', $ts) : $time;
    }
}
