<?php

namespace App\Libraries;

use Modules\Trip\Models\SubtripModel;

/**
 * Server-side fare recompute for booking/payment (offer + seat class parity with triplist).
 */
class BookingOfferFareService
{
    private const MONEY_TOLERANCE = 0.05;

    /**
     * @param list<string> $seatNumbers
     * @return array{
     *   list_price: float,
     *   offer_discount: float,
     *   net_subtotal: float,
     *   tax: float,
     *   roundtrip_discount: float,
     *   grand_total: float,
     *   seats: list<array{seat: string, seat_class_id: string, base_fare: float, discount: float, final_fare: float}>
     * }
     */
    public static function calculateLegFare(
        int $tripId,
        int $subtripId,
        array $seatNumbers,
        ?string $offerCode = null,
        float $tax = 0.0,
        float $roundTripDiscountAmount = 0.0
    ): array {
        $seatNumbers = array_values(array_filter(array_map('trim', $seatNumbers), static fn ($s) => $s !== ''));
        $pricing = self::loadSubtripPricing($subtripId);
        $subtrip = $pricing['subtrip'];
        $mainTripId = (int) ($subtrip->trip_id ?? $tripId);
        $companyId = (int) ($subtrip->company_id ?? 0);
        $adultFair = (float) ($subtrip->adult_fair ?? 0);

        $activeOffer = null;
        $offerCode = trim((string) $offerCode);
        if ($offerCode !== '') {
            $activeOffer = OfferTripEligibility::findActiveOfferByCode($offerCode);
        }

        $listPrice = 0.0;
        $offerDiscount = 0.0;
        $seatBreakdown = [];

        foreach ($seatNumbers as $seatNo) {
            $seatPricing = self::resolveSeatFare($seatNo, $pricing['rows'], $adultFair);
            $baseFare = $seatPricing['base_fare'];
            $seatClassId = $seatPricing['seat_class_id'];
            $discount = 0.0;
            $finalFare = $baseFare;

            if ($activeOffer !== null
                && OfferTripEligibility::appliesToSubtrip($activeOffer, $mainTripId, $subtripId, $companyId)
                && OfferTripEligibility::seatClassAllowed($activeOffer, $seatClassId)
            ) {
                $priced = OfferTripEligibility::calculateSeatFare($activeOffer, $baseFare);
                $discount = $priced['discountAmount'];
                $finalFare = $priced['finalFare'];
            }

            $listPrice += $baseFare;
            $offerDiscount += $discount;
            $seatBreakdown[] = [
                'seat' => $seatNo,
                'seat_class_id' => $seatClassId,
                'base_fare' => round($baseFare, 2),
                'discount' => round($discount, 2),
                'final_fare' => round($finalFare, 2),
            ];
        }

        $listPrice = round($listPrice, 2);
        $offerDiscount = round($offerDiscount, 2);
        $netSubtotal = round(max(0, $listPrice - $offerDiscount), 2);
        $roundTripDiscountAmount = round(max(0, $roundTripDiscountAmount), 2);
        $tax = round(max(0, $tax), 2);
        $grandTotal = round(max(0, $netSubtotal - $roundTripDiscountAmount + $tax), 2);

        return [
            'list_price' => $listPrice,
            'offer_discount' => $offerDiscount,
            'net_subtotal' => $netSubtotal,
            'tax' => $tax,
            'roundtrip_discount' => $roundTripDiscountAmount,
            'grand_total' => $grandTotal,
            'seats' => $seatBreakdown,
            'offer_applied' => $activeOffer !== null && $offerDiscount > 0,
            'offer_code' => $offerCode,
        ];
    }

    /**
     * @return array{ok: bool, message?: string, expected?: array<string, mixed>}
     */
    public static function validateOneWayLeg(
        int $tripId,
        int $subtripId,
        array $seatNumbers,
        ?string $offerCode,
        float $clientGrandTotal,
        ?float $clientDiscount,
        float $tax = 0.0
    ): array {
        $offerCode = trim((string) $offerCode);
        $activeOffer = $offerCode !== '' ? OfferTripEligibility::findActiveOfferByCode($offerCode) : null;

        if ($offerCode !== '' && $activeOffer === null) {
            // Coupon or unknown code — do not block (legacy coupons are not recomputed here).
            if ($clientGrandTotal < 0) {
                return self::fail('Invalid payment amount.', []);
            }

            return ['ok' => true];
        }

        if ($offerCode !== '' && $activeOffer !== null) {
            $pricing = self::loadSubtripPricing($subtripId);
            $mainTripId = (int) ($pricing['subtrip']->trip_id ?? $tripId);
            $companyId = (int) ($pricing['subtrip']->company_id ?? 0);
            if (! OfferTripEligibility::appliesToSubtrip($activeOffer, $mainTripId, $subtripId, $companyId)) {
                return self::fail('This offer is not valid for the selected trip.', []);
            }
        }

        $expected = self::calculateLegFare($tripId, $subtripId, $seatNumbers, $offerCode, $tax, 0.0);

        if (! self::moneyClose($clientGrandTotal, $expected['grand_total'])) {
            return self::fail(
                'Payment amount does not match the fare for this trip and seats.',
                $expected
            );
        }

        if ($clientDiscount !== null && ! self::moneyClose((float) $clientDiscount, $expected['offer_discount'])) {
            return self::fail(
                'Offer discount does not match the server fare calculation.',
                $expected
            );
        }

        return ['ok' => true, 'expected' => $expected];
    }

    /**
     * @return array{ok: bool, message?: string, expected?: array<string, mixed>}
     */
    public static function validateRoundTrip(
        int $outTripId,
        int $outSubtripId,
        array $outSeats,
        float $outBaseFare,
        int $returnTripId,
        int $returnSubtripId,
        array $returnSeats,
        float $returnBaseFare,
        ?string $offerCode,
        float $clientGrandTotal,
        ?float $clientDiscount,
        float $tax,
        float $roundTripDiscountPercent
    ): array {
        $outExpected = self::calculateLegFare($outTripId, $outSubtripId, $outSeats, $offerCode, 0.0, 0.0);
        $returnExpected = self::calculateLegFare($returnTripId, $returnSubtripId, $returnSeats, $offerCode, 0.0, 0.0);

        $totalBase = $outExpected['list_price'] + $returnExpected['list_price'];
        $totalOfferDiscount = $outExpected['offer_discount'] + $returnExpected['offer_discount'];

        $roundTripDiscountAmount = 0.0;
        if ($roundTripDiscountPercent > 0 && $roundTripDiscountPercent <= 100 && $totalBase > 0) {
            $roundTripDiscountAmount = round(($totalBase * $roundTripDiscountPercent) / 100, 2);
        } elseif ($roundTripDiscountPercent > 0) {
            $roundTripDiscountAmount = round($roundTripDiscountPercent, 2);
        }

        $netSubtotal = round(max(0, $totalBase - $totalOfferDiscount - $roundTripDiscountAmount), 2);
        $grandTotal = round(max(0, $netSubtotal + max(0, $tax)), 2);

        $expected = [
            'list_price' => round($totalBase, 2),
            'offer_discount' => round($totalOfferDiscount, 2),
            'net_subtotal' => $netSubtotal,
            'roundtrip_discount' => $roundTripDiscountAmount,
            'tax' => round(max(0, $tax), 2),
            'grand_total' => $grandTotal,
            'outbound' => $outExpected,
            'return' => $returnExpected,
        ];

        $offerCode = trim((string) $offerCode);
        if ($offerCode !== '' && OfferTripEligibility::findActiveOfferByCode($offerCode) === null) {
            if ($clientGrandTotal < 0) {
                return self::fail('Invalid payment amount.', $expected);
            }

            return ['ok' => true, 'expected' => $expected];
        }

        if (! self::moneyClose($clientGrandTotal, $grandTotal)) {
            return self::fail(
                'Payment amount does not match the fare for this round trip.',
                $expected
            );
        }

        if ($clientDiscount !== null && ! self::moneyClose((float) $clientDiscount, $totalOfferDiscount)) {
            return self::fail(
                'Offer discount does not match the server fare calculation.',
                $expected
            );
        }

        return ['ok' => true, 'expected' => $expected];
    }

    /**
     * @return array{subtrip: object, rows: list<array<string, mixed>>}
     */
    private static function loadSubtripPricing(int $subtripId): array
    {
        $subtrip = model(SubtripModel::class)
            ->select('subtrips.*, trips.seatclass, trips.company_id')
            ->join('trips', 'subtrips.trip_id = trips.id')
            ->where('subtrips.id', $subtripId)
            ->first();

        if (! is_object($subtrip)) {
            throw new \InvalidArgumentException('Invalid subtrip_id.');
        }

        $raw = $subtrip->subtrip_seatclass ?? null;
        if ($raw === null || $raw === '') {
            $raw = $subtrip->seatclass ?? '[]';
        }

        $rows = json_decode((string) $raw, true);
        if (! is_array($rows)) {
            $rows = [];
        }

        $rows[] = [
            'seatClass' => '999',
            'seatNo' => [],
            'fare' => (float) ($subtrip->adult_fair ?? 0),
        ];

        return [
            'subtrip' => $subtrip,
            'rows' => $rows,
        ];
    }

    /**
     * @param list<array<string, mixed>> $rows
     * @return array{seat_class_id: string, base_fare: float}
     */
    private static function resolveSeatFare(string $seatNo, array $rows, float $adultFair): array
    {
        foreach ($rows as $row) {
            if (! is_array($row)) {
                continue;
            }
            $seatList = $row['seatNo'] ?? [];
            if (! is_array($seatList) || $seatList === []) {
                continue;
            }
            if (in_array($seatNo, array_map('strval', $seatList), true)) {
                return [
                    'seat_class_id' => (string) ($row['seatClass'] ?? '999'),
                    'base_fare' => (float) ($row['fare'] ?? $adultFair),
                ];
            }
        }

        return [
            'seat_class_id' => '999',
            'base_fare' => $adultFair,
        ];
    }

    private static function moneyClose(float $a, float $b): bool
    {
        return abs(round($a, 2) - round($b, 2)) <= self::MONEY_TOLERANCE;
    }

    /**
     * @param array<string, mixed> $expected
     * @return array{ok: false, message: string, expected: array<string, mixed>}
     */
    private static function fail(string $message, array $expected): array
    {
        return [
            'ok' => false,
            'message' => $message,
            'expected' => $expected,
        ];
    }
}
