<?php

namespace App\Http\Controllers;

use App\Domain\Facility\Models\Facility;
use App\Domain\Facility\Models\SpaceReservation;
use App\Domain\Facility\Services\SpaceLayoutService;
use App\Domain\Shared\Context\BranchContext;
use App\Domain\Training\Models\TrainingGroup;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Gate;

/**
 * Printable schedule sheets.
 *
 * Rendered as branded HTML and printed from the browser rather than through a
 * PDF library: Arabic shaping survives intact, the academy's own fonts and
 * brand colour render exactly, and nothing queues on the server.
 */
class SchedulePrintController extends Controller
{
    private const ARABIC_DAYS = [
        0 => 'الأحد', 1 => 'الإثنين', 2 => 'الثلاثاء', 3 => 'الأربعاء',
        4 => 'الخميس', 5 => 'الجمعة', 6 => 'السبت',
    ];

    public function __construct(private SpaceLayoutService $layouts) {}

    /** One facility, one day, broken down by the physical grid. */
    public function facilityDay(Request $request, Facility $facility)
    {
        Gate::authorize('schedules.view');

        $date = $this->dateFrom($request);
        $reservations = $this->reservationsFor($facility, $date, $date);
        $layout = $this->layouts->resolveLayout($facility->id, $date->toDateString(), '12:00')
            ?? $facility->activeLayout;

        $segments = $layout
            ? $layout->segments()->orderBy('sort_order')->get()
            : collect();

        $slots = $this->timeSlots($facility, $reservations);

        // segment id -> slot -> booking
        $cells = [];
        foreach ($reservations as $reservation) {
            $booking = $this->describe($reservation);
            foreach ($this->slotsCovered($slots, $reservation) as $slotKey) {
                foreach (($reservation->segment_ids ?: []) as $segmentId) {
                    $cells[$slotKey][$segmentId] = $booking;
                }
            }
        }

        return view('print.facility-day-board', [
            'facility' => $facility->load('branch'),
            'date' => $date,
            'dayLabel' => self::ARABIC_DAYS[$date->dayOfWeek],
            'segments' => $segments,
            'slots' => $slots,
            'cells' => $cells,
            'reservations' => $reservations,
            'legendGroups' => $this->legend($reservations),
        ]);
    }

    /** One facility, one week. */
    public function facilityWeek(Request $request, Facility $facility)
    {
        Gate::authorize('schedules.view');

        $weekStart = $this->dateFrom($request)->startOfWeek(Carbon::SATURDAY);
        $weekEnd = (clone $weekStart)->addDays(6);

        $reservations = $this->reservationsFor($facility, $weekStart, $weekEnd);
        $slots = $this->timeSlots($facility, $reservations);

        $days = [];
        for ($i = 0; $i < 7; $i++) {
            $day = (clone $weekStart)->addDays($i);
            $days[] = [
                'key' => $day->toDateString(),
                'name' => self::ARABIC_DAYS[$day->dayOfWeek],
                'date' => $day,
            ];
        }

        $grid = [];
        foreach ($reservations as $reservation) {
            $dayKey = $reservation->reservation_date->toDateString();
            $booking = $this->describe($reservation);
            foreach ($this->slotsCovered($slots, $reservation) as $slotKey) {
                $grid[$dayKey][$slotKey][] = $booking;
            }
        }

        return view('print.facility-week-board', [
            'facility' => $facility->load('branch'),
            'weekStart' => $weekStart,
            'weekEnd' => $weekEnd,
            'days' => $days,
            'slots' => $slots,
            'grid' => $grid,
            'totalBookings' => $reservations->count(),
            'legendGroups' => $this->legend($reservations),
        ]);
    }

    /** Every trainer's day, cut into pocket cards. */
    public function trainerDay(Request $request)
    {
        Gate::authorize('schedules.view');

        $date = $this->dateFrom($request);

        // The branch used to come off the query string — a picker anyone could
        // set, checked against nothing. Worse, the only link to this sheet
        // passes no `branch` at all, so the ->when() never fired and the cards
        // printed every branch's trainers, groups and head-counts on one page.
        // SpaceReservation carries BranchScope now; the sheet is the branch the
        // request is in, and nothing on the URL can change that.
        $reservations = SpaceReservation::with(['facility', 'reservable'])
            ->where('status', 'confirmed')
            ->whereDate('reservation_date', $date->toDateString())
            ->orderBy('start_time')
            ->get()
            ->tap(fn ($rows) => $rows->loadMorph('reservable', [
                TrainingGroup::class => ['headTrainer'],
            ]));

        $byTrainer = [];
        foreach ($reservations as $reservation) {
            $group = $reservation->reservable instanceof TrainingGroup ? $reservation->reservable : null;
            if (! $group?->head_trainer_id) {
                continue;
            }

            $byTrainer[$group->head_trainer_id][] = [
                'group' => $group->name_ar,
                'group_id' => $group->id,
                'start' => substr((string) $reservation->start_time, 0, 5),
                'end' => substr((string) $reservation->end_time, 0, 5),
                'facility' => $reservation->facility?->name_ar ?? '—',
                'segments' => $this->segmentLabel($reservation),
                'count' => $group->current_count,
            ];
        }

        $trainers = User::whereIn('id', array_keys($byTrainer))
            ->orderBy('name_ar')
            ->get()
            ->map(fn ($u) => [
                'name' => $u->name_ar ?? $u->name,
                'slots' => $byTrainer[$u->id] ?? [],
            ])
            ->toArray();

        return view('print.trainer-day-cards', [
            'date' => $date,
            'dayLabel' => self::ARABIC_DAYS[$date->dayOfWeek],
            'trainers' => $trainers,
            // By construction a branch the user is entitled to see; null only
            // when they are genuinely viewing every branch.
            'branchName' => app(BranchContext::class)->currentBranch()?->name_ar,
        ]);
    }

    // ─── Helpers ──────────────────────────────────────────────────

    private function dateFrom(Request $request): Carbon
    {
        try {
            return $request->filled('date')
                ? Carbon::parse($request->string('date')->toString())->startOfDay()
                : now()->startOfDay();
        } catch (\Throwable) {
            return now()->startOfDay();
        }
    }

    private function reservationsFor(Facility $facility, Carbon $from, Carbon $to)
    {
        return SpaceReservation::with('reservable')
            ->where('facility_id', $facility->id)
            ->where('status', 'confirmed')
            ->whereBetween('reservation_date', [$from->toDateString(), $to->toDateString()])
            ->orderBy('reservation_date')
            ->orderBy('start_time')
            ->get()
            ->tap(fn ($rows) => $rows->loadMorph('reservable', [
                TrainingGroup::class => ['headTrainer'],
            ]));
    }

    /**
     * Half-hour rows spanning the facility's operating hours, widened when a
     * booking falls outside them — a sheet that hides a real booking is worse
     * than a sheet with an extra row.
     */
    private function timeSlots(Facility $facility, $reservations): array
    {
        $start = $facility->operating_start ? substr((string) $facility->operating_start, 0, 5) : '08:00';
        $end = $facility->operating_end ? substr((string) $facility->operating_end, 0, 5) : '22:00';

        foreach ($reservations as $reservation) {
            $start = min($start, substr((string) $reservation->start_time, 0, 5));
            $end = max($end, substr((string) $reservation->end_time, 0, 5));
        }

        $cursor = Carbon::createFromFormat('H:i', $start);
        $stop = Carbon::createFromFormat('H:i', $end);
        if ($stop->lte($cursor)) {
            $stop = (clone $cursor)->addHours(2);
        }

        $slots = [];
        while ($cursor->lt($stop)) {
            $next = (clone $cursor)->addMinutes(30);
            $slots[] = [
                'key' => $cursor->format('H:i'),
                'label' => $cursor->format('H:i'),
                'start' => $cursor->format('H:i'),
                'end' => $next->format('H:i'),
            ];
            $cursor = $next;
        }

        return $slots;
    }

    /** Which half-hour rows a booking sits in. */
    private function slotsCovered(array $slots, SpaceReservation $reservation): array
    {
        $start = substr((string) $reservation->start_time, 0, 5);
        $end = substr((string) $reservation->end_time, 0, 5);

        return array_column(
            array_filter($slots, fn ($s) => $s['start'] < $end && $s['end'] > $start),
            'key'
        );
    }

    private function describe(SpaceReservation $reservation): array
    {
        $group = $reservation->reservable instanceof TrainingGroup ? $reservation->reservable : null;

        return [
            'title' => $group?->name_ar ?? $reservation->title ?? __('محجوز'),
            'group_id' => $group?->id ?? 0,
            'start' => substr((string) $reservation->start_time, 0, 5),
            'end' => substr((string) $reservation->end_time, 0, 5),
            'trainer' => $group?->headTrainer?->name_ar ?? $group?->headTrainer?->name,
            'segments' => $this->segmentLabel($reservation),
        ];
    }

    private function segmentLabel(SpaceReservation $reservation): ?string
    {
        $ids = $reservation->segment_ids ?: [];
        if (! $ids) {
            return null;
        }

        return \App\Domain\Facility\Models\SpaceSegment::whereIn('id', $ids)
            ->orderBy('sort_order')
            ->pluck('code')
            ->implode('، ');
    }

    private function legend($reservations): array
    {
        return $reservations
            ->map(fn ($r) => $r->reservable instanceof TrainingGroup ? $r->reservable : null)
            ->filter()
            ->unique('id')
            ->map(fn ($g) => ['id' => $g->id, 'name' => $g->name_ar])
            ->values()
            ->toArray();
    }
}
