<?php

namespace Modules\Transport\Services;

use App\Models\TransportDriverModel;
use App\Models\TransportScheduleModel;
use App\Models\TransportScheduleStopModel;

/**
 * Builds driver schedule handoff packs for PDF / CSV / Word export.
 */
class DriverScheduleExportService
{
    protected TransportScheduleModel $schedules;
    protected TransportScheduleStopModel $scheduleStops;
    protected TransportDriverModel $drivers;

    public function __construct()
    {
        $this->schedules     = new TransportScheduleModel();
        $this->scheduleStops = new TransportScheduleStopModel();
        $this->drivers       = new TransportDriverModel();
    }

    /**
     * @param array<string, mixed> $input
     * @return array{driver_id: ?int, date_from: string, date_to: string, status: ?string}
     */
    public function resolveFilters(array $input): array
    {
        $driverId = isset($input['driver_id']) && $input['driver_id'] !== ''
            ? (int) $input['driver_id']
            : null;
        if ($driverId !== null && $driverId <= 0) {
            $driverId = null;
        }

        $dateFrom = trim((string) ($input['date_from'] ?? ''));
        $dateTo   = trim((string) ($input['date_to'] ?? ''));
        if ($dateFrom === '' || ! $this->isValidDate($dateFrom)) {
            $dateFrom = date('Y-m-d');
        }
        if ($dateTo === '' || ! $this->isValidDate($dateTo)) {
            $dateTo = $dateFrom;
        }
        if ($dateTo < $dateFrom) {
            $dateTo = $dateFrom;
        }

        $status = trim((string) ($input['status'] ?? ''));
        if ($status !== '' && ! in_array($status, TransportScheduleModel::STATUSES, true)) {
            $status = null;
        } else {
            $status = $status !== '' ? $status : null;
        }

        return [
            'driver_id' => $driverId,
            'date_from' => $dateFrom,
            'date_to'   => $dateTo,
            'status'    => $status,
        ];
    }

    /**
     * @return list<array<string, mixed>>
     */
    public function activeDrivers(): array
    {
        return $this->drivers->getActive();
    }

    /**
     * @param array{driver_id: ?int, date_from: string, date_to: string, status: ?string} $filters
     * @return list<array<string, mixed>>
     */
    public function fetchTrips(array $filters): array
    {
        $builder = $this->schedules
            ->scopeForList(null, $filters['status'], $filters['date_from'], $filters['date_to']);

        if (! empty($filters['driver_id'])) {
            $builder->where('transport_schedules.driver_id', (int) $filters['driver_id']);
        } else {
            $builder->where('transport_schedules.driver_id IS NOT NULL');
        }

        return $builder
            ->orderBy('employees.full_name', 'ASC')
            ->orderBy('transport_schedules.schedule_date', 'ASC')
            ->orderBy('transport_schedules.departure_time', 'ASC')
            ->findAll();
    }

    /**
     * @param list<array<string, mixed>> $trips
     *
     * @return array<int, list<array>>
     */
    public function stopsForTrips(array $trips): array
    {
        $ids = array_values(array_filter(array_map(
            static fn (array $trip): int => (int) ($trip['id'] ?? 0),
            $trips
        )));

        return $this->scheduleStops->getForSchedulesGrouped($ids);
    }

    /**
     * Group trips by driver for on-screen preview and sectioned exports.
     *
     * @param list<array<string, mixed>> $trips
     * @return list<array{driver_id: int, driver_name: string, driver_number: string, trips: list<array<string, mixed>>}>
     */
    public function groupByDriver(array $trips): array
    {
        $groups = [];
        foreach ($trips as $trip) {
            $driverId = (int) ($trip['driver_id'] ?? 0);
            if ($driverId <= 0) {
                continue;
            }
            if (! isset($groups[$driverId])) {
                $groups[$driverId] = [
                    'driver_id'     => $driverId,
                    'driver_name'   => (string) ($trip['driver_name'] ?: ('Driver #' . $driverId)),
                    'driver_number' => (string) ($trip['driver_number'] ?? ''),
                    'trips'         => [],
                ];
            }
            $groups[$driverId]['trips'][] = $trip;
        }

        return array_values($groups);
    }

    /**
     * @param list<array<string, mixed>> $trips
     * @param array{driver_id: ?int, date_from: string, date_to: string, status: ?string} $filters
     * @return array{filename: string, title: string, subtitle: string, headers: list<string>, rows: list<list<string>>, sections: list<array{title: string, headers: list<string>, rows: list<list<string>>}>}
     */
    public function buildExportPayload(array $trips, array $filters): array
    {
        $stopsByTrip = $this->stopsForTrips($trips);

        $headers = [
            'Trip #',
            'Date',
            'Depart',
            'Arrive',
            'Return departs',
            'Back at base',
            'Route',
            'Vehicle',
            'Title',
            'Status',
            'Transport legs',
        ];

        $scopeLabel = 'All drivers';
        if (! empty($filters['driver_id'])) {
            $name = $trips[0]['driver_name'] ?? null;
            $scopeLabel = $name ? ('Driver: ' . $name) : ('Driver #' . $filters['driver_id']);
        }

        $statusLabel = $filters['status']
            ? ucwords(str_replace('_', ' ', $filters['status']))
            : 'Any status';

        $subtitle = sprintf(
            '%s · %s to %s · %s',
            $scopeLabel,
            $filters['date_from'],
            $filters['date_to'],
            $statusLabel
        );

        $groups   = $this->groupByDriver($trips);
        $sections = [];
        $flatRows = [];

        foreach ($groups as $group) {
            $sectionRows = [];
            foreach ($group['trips'] as $trip) {
                $tripId = (int) ($trip['id'] ?? 0);
                $row = [
                    (string) ($trip['number'] ?? ''),
                    (string) ($trip['schedule_date'] ?? ''),
                    $this->formatTime($trip['departure_time'] ?? null),
                    $this->formatTime($trip['arrival_time'] ?? null),
                    $this->formatTime($trip['return_departure_time'] ?? null),
                    $this->formatTime($trip['return_arrival_time'] ?? null),
                    (string) ($trip['route_name'] ?? '—'),
                    (string) ($trip['vehicle_registration'] ?? '—'),
                    (string) ($trip['title'] ?? ''),
                    ucwords(str_replace('_', ' ', (string) ($trip['status'] ?? ''))),
                    $this->legsSummary($stopsByTrip[$tripId] ?? [], $trip['notes'] ?? null),
                ];
                $sectionRows[] = $row;
                $flatRows[]    = array_merge(
                    [
                        $group['driver_name'],
                        $group['driver_number'],
                    ],
                    $row
                );
            }

            $label = $group['driver_name'];
            if ($group['driver_number'] !== '') {
                $label .= ' (' . $group['driver_number'] . ')';
            }
            $sections[] = [
                'title'   => $label,
                'headers' => $headers,
                'rows'    => $sectionRows,
            ];
        }

        $flatHeaders = array_merge(['Driver', 'Driver #'], $headers);

        $filename = 'driver_schedules_' . $filters['date_from'] . '_to_' . $filters['date_to'];
        if (! empty($filters['driver_id'])) {
            $filename .= '_driver_' . (int) $filters['driver_id'];
        }

        return [
            'filename' => $filename,
            'title'    => 'Driver Schedule Export',
            'subtitle' => $subtitle,
            'headers'  => $flatHeaders,
            'rows'     => $flatRows,
            'sections' => $sections,
        ];
    }

    /**
     * @param list<array> $stops
     */
    protected function legsSummary(array $stops, ?string $notes): string
    {
        if ($stops !== []) {
            $parts = [];
            foreach ($stops as $stop) {
                $leg = $this->stopLegLabel($stop['address'] ?? '');
                $parts[] = sprintf(
                    '%s %s %s %s',
                    $leg,
                    $stop['employee_name'] ?? ('#' . ($stop['employee_id'] ?? '')),
                    $this->formatTime($stop['pickup_time'] ?? null),
                    $this->stopLegText($stop['address'] ?? '')
                );
            }

            return implode(' | ', $parts);
        }

        return $this->pickupSummary($notes);
    }

    protected function stopLegLabel(?string $address): string
    {
        $address = (string) $address;
        if (str_starts_with($address, '[BOARD]')) {
            return 'Board';
        }
        if (str_starts_with($address, '[DROP]')) {
            return 'Drop';
        }
        if (str_starts_with($address, '[RETURN]')) {
            return 'Return';
        }

        return 'Stop';
    }

    protected function stopLegText(?string $address): string
    {
        return preg_replace('/^\[(BOARD|DROP|RETURN)\]\s*/', '', (string) $address) ?: '';
    }

    protected function pickupSummary(?string $notes): string
    {
        $notes = trim((string) $notes);
        if ($notes === '') {
            return '';
        }

        if (preg_match('/(?:Boarding\s*\/\s*passengers|Pickups):\s*(.+)$/is', $notes, $m)) {
            $block = trim($m[1]);
            $lines = preg_split('/\r\n|\r|\n/', $block) ?: [];
            $lines = array_values(array_filter(array_map('trim', $lines)));

            return implode(' | ', $lines);
        }

        return preg_replace('/\s+/', ' ', $notes) ?? $notes;
    }

    protected function formatTime(mixed $time): string
    {
        $time = (string) ($time ?? '');
        if ($time === '') {
            return '—';
        }

        return substr($time, 0, 5);
    }

    protected function isValidDate(string $date): bool
    {
        $dt = \DateTime::createFromFormat('Y-m-d', $date);

        return $dt !== false && $dt->format('Y-m-d') === $date;
    }
}
