<?php

namespace App\Libraries;

use Modules\Ticket\Models\TicketModel;
use Modules\Trip\Models\PickdropModel;

/**
 * Shared journey time + cancellation amounts for admin cancel UI and passenger API.
 */
class TicketCancellationSupport
{
    public static function getTimeDifferenceInHours(string $journeyDate, string $journeyTime): float
    {
        $journeyDateTime = $journeyDate . ' ' . $journeyTime;
        try {
            $journeyDateTimeObject = get_egypt_time($journeyDateTime);
        } catch (\Exception $e) {
            return 0.0;
        }
        $currentDateTime = get_egypt_time('now');
        $interval = $currentDateTime->diff($journeyDateTimeObject);
        $hoursDifference = $interval->h + ($interval->days * 24);
        if ($interval->invert == 0) {
            $hoursDifference += $interval->i / 60;

            return (float) ceil($hoursDifference);
        }

        return (float) -round($hoursDifference);
    }

    /**
     * Load ticket with joins (same shape as Cancel::new).
     */
    public static function loadTrackRowForCancellation(int $ticketId): ?object
    {
        $ticketModel = new TicketModel();
        $picdropModel = new PickdropModel();

        $track = $ticketModel
            ->select('tickets.*, trips.company_id, schedules.start_time AS journeytimes, journeylists.journeydate')
            ->join('trips', 'trips.id = tickets.trip_id', 'left')
            ->join('subtrips', 'subtrips.trip_id = trips.id', 'left')
            ->join('schedules', 'schedules.id = trips.schedule_id', 'left')
            ->join('journeylists', 'journeylists.booking_id = tickets.booking_id', 'left')
            ->where('tickets.id', $ticketId)
            ->first();

        if (empty($track)) {
            return null;
        }

        $pickdrop = $picdropModel->select('pickdrops.id as pickdropid,pickdrops.*,stands.*')
            ->join('stands', 'stands.id = pickdrops.stand_id')
            ->where('trip_id', $track->subtrip_id)
            ->where('type', 1)
            ->first();
        if (empty($pickdrop)) {
            $pickdrop = $picdropModel->select('pickdrops.id as pickdropid,pickdrops.*,stands.*')
                ->join('stands', 'stands.id = pickdrops.stand_id')
                ->where('trip_id', $track->trip_id)
                ->where('type', 1)
                ->first();
        }
        $track->journeytime = $pickdrop->time ?? $track->journeytimes;

        return $track;
    }

    /**
     * Resolves journey date/time strings and remaining hours before departure.
     *
     * @return array{journey_date: string, journey_time: string, remaining_hours: float, company_id: int}|null
     */
    public static function resolveJourneyContext(object $track_table): ?array
    {
        $picdropModel = new PickdropModel();

        $journeyDate = '';
        $journeyTime = '';
        $company_id = (int) ($track_table->company_id ?? 0);

        if (isset($track_table->journeydate)) {
            $journeyDate = date('Y-m-d', strtotime($track_table->journeydate));
        }
        if ($journeyDate === '' && isset($track_table->journeydata)) {
            $journeyDate = date('Y-m-d', strtotime($track_table->journeydata));
        }

        if (isset($track_table->journeytime)) {
            $journeyTime = $track_table->journeytime;

            $allStops = $picdropModel->where('trip_id', $track_table->subtrip_id)->orderBy('id', 'ASC')->findAll();
            if (empty($allStops)) {
                $allStops = $picdropModel->where('trip_id', $track_table->trip_id)->orderBy('id', 'ASC')->findAll();
            }

            if (! empty($allStops) && isset($track_table->journeytimes) && $journeyDate !== '') {
                $last_stop_time = $track_table->journeytimes;
                $current_day_offset = 0;
                $ticket_pickup_offset = 0;
                $found_pickup = false;

                foreach ($allStops as $stop) {
                    $item_time = $stop->time;
                    if (strtotime('1970-01-01 ' . $item_time) < strtotime('1970-01-01 ' . $last_stop_time)) {
                        $current_day_offset++;
                    }
                    $last_stop_time = $item_time;

                    if ((int) $stop->type === 1 && ! $found_pickup) {
                        $ticket_pickup_offset = $current_day_offset;
                        $found_pickup = true;
                        break;
                    }
                }

                if ($found_pickup && $ticket_pickup_offset > 0) {
                    $journeyDate = date('Y-m-d', strtotime($journeyDate . " +{$ticket_pickup_offset} day"));
                }
            }
        }

        if ($journeyDate === '' || $journeyTime === '') {
            return null;
        }

        $remaining = self::getTimeDifferenceInHours($journeyDate, $journeyTime);

        return [
            'journey_date' => $journeyDate,
            'journey_time' => $journeyTime,
            'remaining_hours' => $remaining,
            'company_id' => $company_id,
        ];
    }

    /**
     * @return array{cancel_charge: float|int, refunded_amount: float|int, total_amount: float|int, remaining_hours: float, journey_passed: bool}
     */
    public static function computeCancellationAmounts(object $track_table): array
    {
        $total_amount = (float) ($track_table->paidamount ?? 0);
        $ctx = self::resolveJourneyContext($track_table);
        if ($ctx === null) {
            return [
                'cancel_charge' => 0,
                'refunded_amount' => round($total_amount),
                'total_amount' => round($total_amount),
                'remaining_hours' => 0.0,
                'journey_passed' => false,
            ];
        }

        $journeyTs = strtotime($ctx['journey_date'] . ' ' . $ctx['journey_time']);
        $journeyPassed = $journeyTs !== false && $journeyTs <= time();

        $bookby = (string) ($track_table->bookby_user_type ?? '');
        $diff = $ctx['remaining_hours'];

        if ($diff <= 0 || $journeyPassed) {
            return [
                'cancel_charge' => 0,
                'refunded_amount' => round($total_amount),
                'total_amount' => round($total_amount),
                'remaining_hours' => $diff,
                'journey_passed' => $journeyPassed,
            ];
        }

        $slab = CancellationPolicy::findSlab($diff, $bookby, $ctx['company_id']);
        $amounts = CancellationPolicy::amountsFromSlab($slab, $total_amount);

        return [
            'cancel_charge' => $amounts['cancel_charge'],
            'refunded_amount' => $amounts['refunded_amount'],
            'total_amount' => round($total_amount),
            'remaining_hours' => $diff,
            'journey_passed' => false,
        ];
    }

    /**
     * Fee breakdown for passenger app/website before user confirms cancellation.
     * Uses global_cancellation_policies when bookby_user_type is "passanger".
     *
     * @return array<string, mixed>|null
     */
    public static function buildPassengerCancellationPreview(int $ticketId): ?array
    {
        $track = self::loadTrackRowForCancellation($ticketId);
        if ($track === null) {
            return null;
        }

        $bookby = (string) ($track->bookby_user_type ?? '');
        $ticketAmount = (float) ($track->paidamount ?? 0);
        $base = [
            'ticket_id' => $ticketId,
            'booking_id' => $track->booking_id ?? null,
            'ticket_amount' => $ticketAmount,
            'computed_at' => date('c'),
        ];

        if ($bookby !== 'passanger') {
            return array_merge($base, [
                'can_cancel' => false,
                'reason' => 'not_passenger_online_booking',
                'cancel_fee' => 0.0,
                'refund_amount' => 0.0,
                'charge_percent' => 0.0,
                'policy_tier_hours' => null,
                'remaining_hours' => 0.0,
                'journey_passed' => false,
            ]);
        }

        if ((int) ($track->cancel_status ?? 0) === 1) {
            return array_merge($base, [
                'can_cancel' => false,
                'reason' => 'already_cancelled',
                'cancel_fee' => 0.0,
                'refund_amount' => 0.0,
                'charge_percent' => 0.0,
                'policy_tier_hours' => null,
                'remaining_hours' => 0.0,
                'journey_passed' => false,
            ]);
        }

        $amounts = self::computeCancellationAmounts($track);
        $ctx = self::resolveJourneyContext($track);
        $slab = null;
        if ($ctx !== null && $amounts['remaining_hours'] > 0 && ! $amounts['journey_passed']) {
            $slab = CancellationPolicy::findSlab($amounts['remaining_hours'], $bookby, $ctx['company_id']);
        }

        return array_merge($base, [
            'can_cancel' => ! $amounts['journey_passed'],
            'reason' => $amounts['journey_passed'] ? 'journey_passed' : null,
            'journey_passed' => (bool) $amounts['journey_passed'],
            'remaining_hours' => (float) $amounts['remaining_hours'],
            'journey_date' => $ctx['journey_date'] ?? null,
            'journey_time' => $ctx['journey_time'] ?? null,
            'charge_percent' => $slab !== null ? (float) $slab->charges : 0.0,
            'policy_tier_hours' => $slab !== null ? (int) $slab->hours : null,
            'cancel_fee' => (float) $amounts['cancel_charge'],
            'refund_amount' => (float) $amounts['refunded_amount'],
        ]);
    }

    /**
     * App / website integration guide (also returned on passenger ticket APIs).
     *
     * @return array<string, mixed>
     */
    public static function getCancellationIntegrationGuide(): array
    {
        return [
            'base_url' => 'modules/api/v1/passangers',
            'steps' => [
                [
                    'step' => 1,
                    'when' => 'Ticket list screen (optional)',
                    'method' => 'GET',
                    'path' => 'tickets?include_cancellation_preview=1',
                    'note' => 'Optional. Omit the query param on existing integrations (default unchanged). Use cancellation_preview only for badges, not the confirm dialog.',
                ],
                [
                    'step' => 2,
                    'when' => 'User taps Cancel',
                    'method' => 'GET',
                    'path' => 'tickets/cancel-preview?booking_id={booking_id}&ticket_id={ticket_id}',
                    'note' => 'Required. Show data.summary.cancel_fee and data.summary.refund_amount in the confirmation modal.',
                ],
                [
                    'step' => 3,
                    'when' => 'User taps Yes / Confirm (recommended)',
                    'method' => 'GET',
                    'path' => 'tickets/cancel-preview?booking_id={booking_id}&ticket_id={ticket_id}',
                    'note' => 'Call again immediately before cancel. If amounts changed, show updated modal once more.',
                ],
                [
                    'step' => 4,
                    'when' => 'User confirms cancellation',
                    'method' => 'POST',
                    'path' => 'tickets/cancel',
                    'body' => 'booking_id (required), ticket_id (recommended), detail (optional)',
                    'note' => 'Server recalculates fees at this moment. Show cancel_fee and refund_amount from this response.',
                ],
            ],
            'auth' => 'Authorization: Bearer {passenger_jwt}',
            'user_notice' => 'Cancellation fee and refund are based on time remaining when you confirm cancellation. If you wait, the fee may increase when a stricter policy tier applies.',
            'do_not' => [
                'Do not calculate cancel_fee or refund_amount on the client.',
                'Do not use ticket list cancellation_preview as the final amount after the user waits on the modal.',
            ],
            'reason_codes' => [
                'journey_passed' => 'Trip already started; cannot cancel online.',
                'already_cancelled' => 'Ticket already cancelled.',
                'not_passenger_online_booking' => 'Only online passenger bookings can use this API.',
            ],
        ];
    }
}
