<?php

namespace Modules\Reporting\Exports;

use Illuminate\Database\Eloquent\Builder;
use Maatwebsite\Excel\Concerns\FromQuery;
use Maatwebsite\Excel\Concerns\ShouldAutoSize;
use Maatwebsite\Excel\Concerns\WithCustomCsvSettings;
use Maatwebsite\Excel\Concerns\WithEvents;
use Maatwebsite\Excel\Concerns\WithHeadings;
use Maatwebsite\Excel\Concerns\WithMapping;
use Maatwebsite\Excel\Events\AfterSheet;
use Modules\Booking\Models\Booking;
use Modules\Payment\Enums\PaymentStatus;
use Modules\Payment\Models\Payment;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;

/**
 * One row per Booking, with its "best" Payment (completed, else most recent)
 * joined on, plus a bold TOTAL row summing passenger count, price, and
 * payment amount. Backs both the CSV and Excel exports of the Bookings &
 * Revenue report — Excel::download() picks the writer, this class supplies
 * the columns once for both formats.
 */
class BookingsRevenueExport implements FromQuery, ShouldAutoSize, WithCustomCsvSettings, WithEvents, WithHeadings, WithMapping
{
    public function __construct(private readonly Builder $query) {}

    public function query(): Builder
    {
        return $this->query;
    }

    /**
     * @return array<int, string>
     */
    public function headings(): array
    {
        return [
            'Booking Ref', 'Travel Date', 'Route', 'Channel', 'Status',
            'Passenger Name', 'Passenger Count', 'Price', 'Payment Status',
            'Payment Amount', 'Driver Name', 'Driver Phone', 'Car Plate',
            'Car Model', 'Vehicle Options',
        ];
    }

    /**
     * @return array<int, mixed>
     */
    public function map($booking): array
    {
        /** @var Booking $booking */
        $payment = $this->bestPayment($booking);

        return [
            $booking->booking_ref,
            $booking->travel_date?->toDateString(),
            $booking->route ? $booking->route->name : '',
            $booking->created_by_channel?->value,
            $booking->status->value,
            $booking->passenger_name,
            $booking->vehicleOptions->sum('passenger_count'),
            (float) $booking->price,
            $payment?->status?->value ?? '',
            $payment ? (float) $payment->amount : null,
            $booking->driver_name,
            $booking->driver_phone,
            $booking->car_plate_number,
            $booking->car_model,
            $booking->vehicleOptions
                ->map(fn ($v) => str($v->vehicle_option->value)->headline().' x'.$v->passenger_count)
                ->implode('; '),
        ];
    }

    /**
     * Appends a bold TOTAL row (passenger count, price, payment amount)
     * below the last data row. Re-fetches the already-filtered query rather
     * than accumulating during map() — FromQuery streams rows in chunks, so
     * there's no single point with the full result set to total as it's
     * written; report-sized result sets make a second fetch cheap enough to
     * trade for keeping the chunked write untouched.
     *
     * @return array<string, callable>
     */
    public function registerEvents(): array
    {
        return [
            AfterSheet::class => function (AfterSheet $event): void {
                $bookings = (clone $this->query)->get();

                $totalPassengers = $bookings->sum(fn (Booking $b) => $b->vehicleOptions->sum('passenger_count'));
                $totalPrice = $bookings->sum('price');
                $totalPaid = $bookings->sum(fn (Booking $b) => $this->bestPayment($b)?->amount ?? 0);

                $worksheet = $event->getDelegate();
                $row = $worksheet->getHighestRow() + 1;
                $lastColumn = Coordinate::stringFromColumnIndex(count($this->headings()));

                $worksheet->setCellValue("A{$row}", 'TOTAL');
                $worksheet->setCellValue($this->columnFor('Passenger Count').$row, $totalPassengers);
                $worksheet->setCellValue($this->columnFor('Price').$row, $totalPrice);
                $worksheet->setCellValue($this->columnFor('Payment Amount').$row, $totalPaid);
                $worksheet->getStyle("A{$row}:{$lastColumn}{$row}")->getFont()->setBold(true);
            },
        ];
    }

    private function columnFor(string $heading): string
    {
        return Coordinate::stringFromColumnIndex(array_search($heading, $this->headings(), true) + 1);
    }

    private function bestPayment(Booking $booking): ?Payment
    {
        return $booking->payments->sortByDesc(
            fn (Payment $p) => $p->status === PaymentStatus::Completed ? 1 : 0
        )->first();
    }

    /**
     * Excel's CSV import guesses encoding from the system locale unless a
     * UTF-8 BOM is present, so passenger names/routes containing Burmese
     * (or other non-Latin) text open correctly instead of as mojibake.
     *
     * @return array<string, mixed>
     */
    public function getCsvSettings(): array
    {
        return [
            'use_bom' => true,
        ];
    }
}
