<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use App\Models\Account;
use App\Models\Booking;
use App\Models\Customer;
use App\Models\Driver;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\View\View;
use Symfony\Component\HttpFoundation\StreamedResponse;

/**
 * Replaces the old project's bookingreports/getBookingCharts/getbookingsales
 * screens — a status breakdown and a revenue-over-time trend, both scoped
 * to a selectable date range instead of the old hard-coded 2020-2023 chart.
 *
 * RPT-01..04 from the system analysis added: a CSV export, a custom
 * arbitrary date range (not just the 5 fixed presets), period-over-period
 * comparison arrows on each stat card, and a separate driver-performance
 * report.
 */
class ReportController extends Controller
{
    public function index(Request $request): View
    {
        $companyId = $request->user()->company_id;
        [$from, $to, $range] = $this->resolveRange($request);

        // PERF-04: every stat/chart below recomputes from a full bookings
        // scan on each load — cheap to cache since a dashboard number a few
        // minutes stale is fine, and this is the one screen in the app that
        // previously had zero caching (the FCM access token is the only
        // other thing cached anywhere today).
        $cacheKey = sprintf('reports.aggregates.%s.%s.%s', $companyId ?? 'all', $from->timestamp, $to->timestamp);

        [$stats, $comparison, $statusBreakdown, $revenueByDay, $topVehicleTypes] = Cache::remember(
            $cacheKey,
            now()->addMinutes(5),
            function () use ($companyId, $from, $to) {
                $stats = $this->computeStats($companyId, $from, $to);

                // Period-over-period comparison: the immediately preceding window
                // of the same length, so "this week" compares against "last week"
                // and a custom 10-day range compares against the 10 days before it.
                $rangeSeconds = $from->diffInSeconds($to);
                $previousTo = (clone $from)->subSecond();
                $previousFrom = (clone $previousTo)->subSeconds($rangeSeconds);
                $previousStats = $this->computeStats($companyId, $previousFrom, $previousTo);

                $comparison = collect($stats)->mapWithKeys(fn ($value, $key) => [
                    $key => $this->percentChange($value, $previousStats[$key]),
                ]);

                $base = Booking::when($companyId, fn ($q) => $q->where('bookings.company_id', $companyId))
                    ->whereBetween('pickup_datetime', [$from, $to]);
                $completed = (clone $base)->whereHas('status', fn ($q) => $q->where('name', 'Completed'));

                $statusBreakdown = (clone $base)
                    ->join('booking_statuses', 'bookings.status_id', '=', 'booking_statuses.id')
                    ->select('booking_statuses.name', DB::raw('count(*) as total'))
                    ->groupBy('booking_statuses.name')
                    ->pluck('total', 'name');

                $revenueByDay = (clone $completed)
                    ->select(DB::raw('DATE(pickup_datetime) as day'), DB::raw('SUM(fare_amount) as total'))
                    ->groupBy('day')
                    ->orderBy('day')
                    ->pluck('total', 'day');

                $topVehicleTypes = (clone $base)
                    ->join('vehicle_types', 'bookings.vehicle_type_id', '=', 'vehicle_types.id')
                    ->select('vehicle_types.name', DB::raw('count(*) as total'))
                    ->groupBy('vehicle_types.name')
                    ->orderByDesc('total')
                    ->limit(5)
                    ->pluck('total', 'name');

                return [$stats, $comparison, $statusBreakdown, $revenueByDay, $topVehicleTypes];
            }
        );

        return view('admin.reports.index', compact(
            'stats', 'comparison', 'statusBreakdown', 'revenueByDay', 'topVehicleTypes', 'range', 'from', 'to'
        ));
    }

    /**
     * RPT-01: streams a CSV of every booking in the selected range rather
     * than loading them all into memory first — the same range/from/to
     * query params the dashboard uses, so "export what I'm looking at"
     * just works.
     */
    public function export(Request $request): StreamedResponse
    {
        $companyId = $request->user()->company_id;
        [$from, $to] = $this->resolveRange($request);

        $bookings = Booking::with(['customer', 'driver', 'status', 'vehicleType'])
            ->when($companyId, fn ($q) => $q->where('company_id', $companyId))
            ->whereBetween('pickup_datetime', [$from, $to])
            ->orderBy('pickup_datetime')
            ->get();

        $filename = 'bookings-'.$from->format('Ymd').'-to-'.$to->format('Ymd').'.csv';

        $callback = function () use ($bookings) {
            $handle = fopen('php://output', 'w');
            fputcsv($handle, ['Booking Number', 'Pickup Date/Time', 'Customer', 'Driver', 'Vehicle Type', 'Status', 'Distance', 'Fare']);

            foreach ($bookings as $booking) {
                fputcsv($handle, [
                    $booking->booking_number,
                    $booking->pickup_datetime->format('Y-m-d H:i'),
                    $booking->customer->name ?? '—',
                    $booking->driver->full_name ?? 'Unassigned',
                    $booking->vehicleType->name ?? '—',
                    $booking->status->name ?? '—',
                    $booking->distance.' '.$booking->distance_unit,
                    $booking->fare_amount,
                ]);
            }

            fclose($handle);
        };

        return response()->stream($callback, 200, [
            'Content-Type' => 'text/csv',
            'Content-Disposition' => 'attachment; filename="'.$filename.'"',
        ]);
    }

    /**
     * RPT-02: per-driver breakdown — the original dashboard had zero
     * driver-level numbers, only company-wide totals.
     */
    public function drivers(Request $request): View
    {
        $companyId = $request->user()->company_id;
        [$from, $to, $range] = $this->resolveRange($request);

        $cacheKey = sprintf('reports.drivers.%s.%s.%s', $companyId ?? 'all', $from->timestamp, $to->timestamp);

        $drivers = Cache::remember($cacheKey, now()->addMinutes(5), fn () => Driver::when($companyId, fn ($q) => $q->where('company_id', $companyId))
            ->withCount([
                'bookings as completed_jobs' => fn ($q) => $q->whereHas('status', fn ($q2) => $q2->where('name', 'Completed'))
                    ->whereBetween('pickup_datetime', [$from, $to]),
                'bookings as cancelled_jobs' => fn ($q) => $q->whereHas('status', fn ($q2) => $q2->where('name', 'Cancelled'))
                    ->whereBetween('pickup_datetime', [$from, $to]),
            ])
            ->withSum([
                'bookings as revenue' => fn ($q) => $q->whereHas('status', fn ($q2) => $q2->where('name', 'Completed'))
                    ->whereBetween('pickup_datetime', [$from, $to]),
            ], 'fare_amount')
            ->withAvg('ratingsReceived', 'stars')
            ->withCount('ratingsReceived')
            ->orderByDesc('completed_jobs')
            ->get());

        return view('admin.reports.drivers', compact('drivers', 'range', 'from', 'to'));
    }

    /**
     * RPT-06: top customers/accounts by spend and a repeat-customer rate —
     * confirmed absent from the original dashboard, which had no per-
     * customer breakdown at all.
     */
    public function customers(Request $request): View
    {
        $companyId = $request->user()->company_id;
        [$from, $to, $range] = $this->resolveRange($request);

        $cacheKey = sprintf('reports.customers.%s.%s.%s', $companyId ?? 'all', $from->timestamp, $to->timestamp);

        [$topCustomers, $topAccounts, $repeatRate, $activeCustomers] = Cache::remember(
            $cacheKey,
            now()->addMinutes(5),
            function () use ($companyId, $from, $to) {
                $completed = fn ($q) => $q->whereHas('status', fn ($q2) => $q2->where('name', 'Completed'))
                    ->whereBetween('pickup_datetime', [$from, $to]);

                $topCustomers = Customer::when($companyId, fn ($q) => $q->where('company_id', $companyId))
                    ->withCount(['bookings as completed_jobs' => $completed])
                    ->withSum(['bookings as revenue' => $completed], 'fare_amount')
                    ->having('completed_jobs', '>', 0)
                    ->orderByDesc('revenue')
                    ->limit(10)
                    ->get();

                $topAccounts = Account::when($companyId, fn ($q) => $q->where('company_id', $companyId))
                    ->withCount(['bookings as completed_jobs' => $completed])
                    ->withSum(['bookings as revenue' => $completed], 'fare_amount')
                    ->having('completed_jobs', '>', 0)
                    ->orderByDesc('revenue')
                    ->limit(10)
                    ->get();

                // .count() on a withCount()+having() chain isn't reliable —
                // Eloquent's aggregate() query strips the original select
                // list (including the withCount subselect the having clause
                // references) when swapping it for COUNT(*). Materializing
                // just the id column and counting in PHP sidesteps that.
                $jobCounts = Customer::when($companyId, fn ($q) => $q->where('company_id', $companyId))
                    ->withCount(['bookings as completed_jobs' => $completed])
                    ->having('completed_jobs', '>', 0)
                    ->get(['id', 'company_id'])
                    ->pluck('completed_jobs');

                $activeCustomers = $jobCounts->count();
                $repeatCustomers = $jobCounts->filter(fn ($n) => $n > 1)->count();
                $repeatRate = $activeCustomers ? round(($repeatCustomers / $activeCustomers) * 100, 1) : 0.0;

                return [$topCustomers, $topAccounts, $repeatRate, $activeCustomers];
            }
        );

        return view('admin.reports.customers', compact('topCustomers', 'topAccounts', 'repeatRate', 'activeCustomers', 'range', 'from', 'to'));
    }

    /**
     * @return array{0: Carbon, 1: Carbon, 2: string}
     */
    private function resolveRange(Request $request): array
    {
        if ($request->filled('from') && $request->filled('to')) {
            return [
                Carbon::parse($request->input('from'))->startOfDay(),
                Carbon::parse($request->input('to'))->endOfDay(),
                'custom',
            ];
        }

        $range = $request->input('range', 'this_month');
        [$from, $to] = match ($range) {
            'today' => [now()->startOfDay(), now()->endOfDay()],
            'this_week' => [now()->startOfWeek(), now()->endOfWeek()],
            'last_month' => [now()->subMonthNoOverflow()->startOfMonth(), now()->subMonthNoOverflow()->endOfMonth()],
            'this_year' => [now()->startOfYear(), now()->endOfYear()],
            default => [now()->startOfMonth(), now()->endOfMonth()],
        };

        return [$from, $to, $range];
    }

    /**
     * @return array{total_bookings: int, completed_bookings: int, cancelled_bookings: int, total_revenue: float}
     */
    private function computeStats(?int $companyId, Carbon $from, Carbon $to): array
    {
        $base = Booking::when($companyId, fn ($q) => $q->where('company_id', $companyId))
            ->whereBetween('pickup_datetime', [$from, $to]);

        $completed = (clone $base)->whereHas('status', fn ($q) => $q->where('name', 'Completed'));

        return [
            'total_bookings' => (clone $base)->count(),
            'completed_bookings' => (clone $completed)->count(),
            'cancelled_bookings' => (clone $base)->whereHas('status', fn ($q) => $q->where('name', 'Cancelled'))->count(),
            'total_revenue' => round((float) (clone $completed)->sum('fare_amount'), 2),
        ];
    }

    /**
     * Null means "no meaningful comparison" (previous period had zero) —
     * the view shows a neutral "New" label rather than a nonsensical +∞%.
     */
    private function percentChange(float $current, float $previous): ?float
    {
        if ($previous == 0.0) {
            return $current > 0 ? null : 0.0;
        }

        return round((($current - $previous) / $previous) * 100, 1);
    }
}
