<?php

namespace App\Http\Controllers;

use Carbon\Carbon;
use Illuminate\Http\Request;
use App\Models\Calendar;
use App\Models\Event;
use App\Services\Calendar\CreateCalendar;
use App\Services\Calendar\CalendarRangeResolver;
use App\Services\Calendar\CalendarViewBuilder;
use App\Services\Calendar\CalendarSettingsPersister;
use App\Services\Event\EventRecurrence;
use Illuminate\Support\Facades\Log;

class CalendarController extends Controller
{
    /**
     * Consolidated calendar dashboard.
     *
     * Query params:
     *  view  = month | week | four   (default: month)
     *  date  = Y-m-d anchor date     (default: today, in user TZ)
     *
     * The view receives a `$payload` array:
     *  ├─ view          current view name
     *  ├─ range         ['start' => Carbon, 'end' => Carbon]
     *  ├─ calendars     keyed by calendar id (for the left-hand toggle list)
     *  └─ events        flat list of VEVENTs in that range
     */
    public function index(
        Request $request,
        EventRecurrence $recurrence,
        CalendarRangeResolver $rangeResolver,
        CalendarViewBuilder $viewBuilder,
        CalendarSettingsPersister $settingsPersister
    )
    {
        /**
         *
         * manage parameters and core variables
         */

        // set the calendar key
        $principal = auth()->user()->principal_uri;

        // user settings
        $user = $request->user();
        $tz = $user->timezone ?? config('app.timezone');
        $timeFormat = $user->getSetting('app.time_format', '12');

        // settings
        $defaults = $settingsPersister->defaults($user, $tz);
        $weekStart = $defaults['week_start'];
        $weekEnd = $defaults['week_end'];

        // get the view and time range
        [$view, $range] = $rangeResolver->resolveRange(
            $request,
            $tz,
            $weekStart,
            $weekEnd,
            $defaults['view'],
            $defaults['date']
        );

        $density = $settingsPersister->resolveDensity($request, $defaults['density']);
        $stepMinutes = $density['step'];
        $labelEvery = $density['label_every'];

        $daytimeHoursEnabled = $settingsPersister->resolveDaytimeHours($request, $defaults['daytime_hours']);
        $daytimeHoursRange = $settingsPersister->daytimeHoursRange();
        $daytimeHoursRows = $daytimeHoursEnabled
            ? intdiv((($daytimeHoursRange['end'] - $daytimeHoursRange['start']) * 60), 15)
            : 96;
        $daytimeHoursForView = ($daytimeHoursEnabled && in_array($view, ['day', 'week', 'four'], true))
            ? $daytimeHoursRange
            : null;

        // date range span and controls
        $span = $rangeResolver->gridSpan($view, $range, $weekStart, $weekEnd);
        $nav  = $rangeResolver->navDates($view, $range['start'], $tz);

        // get the user's visible calendars from the left bar
        $visible = collect($request->query('c', []));

        // keep a stable anchor date for forms that aren't the nav buttons
        $anchorDate = $request->query('date', now($tz)->toDateString());

        // persist settings
        $settingsPersister->persist(
            $user,
            $request,
            $view,
            $range['start'],
            $stepMinutes,
            $daytimeHoursEnabled
        );

        /**
         *
         * calendars
         */

        $calendars = Calendar::query()
            ->dashboardForPrincipal($principal)
            ->get();

        $calendars = $calendars->map(function ($cal) use ($visible) {
            $cal->visible = $visible->isEmpty() || $visible->contains($cal->slug);
            return $cal;
        });

        $calendar_map = $calendars->keyBy('id');

        /**
         *
         * get events for calendars in range
         */

        // get all the events in one query
        $events = Event::forCalendarsInRange(
            $calendars->pluck('id'),
            $span['start'],
            $span['end']
        );

        // build event payload
        $events = $viewBuilder->buildEventPayloads(
            $events,
            $calendar_map,
            $timeFormat,
            $view,
            $range,
            $tz,
            $recurrence,
            $span,
            $daytimeHoursForView,
        );

        $allDayEvents = $events->filter(fn ($event) => !empty($event['all_day']));
        $timeEvents = $events->filter(fn ($event) => empty($event['all_day']));
        $allDayDisplay = $this->buildAllDayDisplay($allDayEvents);
        $hasAllDayEvents = $allDayDisplay->isNotEmpty();

        if ($request->boolean('debug_all_day')) {
            $calendarIds = $calendars->pluck('id')->values();
            $event13CalendarId = Event::where('id', 13)->value('calendarid');
            $rangeStart = $span['start']->toDateTimeString();
            $rangeEnd = $span['end']->toDateTimeString();

            $metaRangeCount = Event::whereIn('calendarid', $calendarIds)
                ->whereHas('meta', function ($meta) use ($rangeStart, $rangeEnd) {
                    $meta->where(function ($range) use ($rangeStart, $rangeEnd) {
                        $range->where('start_at', '<=', $rangeEnd)
                              ->where(function ($bounds) use ($rangeStart) {
                                  $bounds->where('end_at', '>=', $rangeStart)
                                         ->orWhereNull('end_at');
                              });
                    });
                })
                ->count();

            $metaRruleCount = Event::whereIn('calendarid', $calendarIds)
                ->whereHas('meta', function ($meta) {
                    $meta->whereNotNull('extra->rrule');
                })
                ->count();

            $icalRruleCount = Event::whereIn('calendarid', $calendarIds)
                ->where(function ($ical) {
                    $ical->where('calendardata', 'like', '%RRULE%')
                         ->orWhere('calendardata', 'like', '%RDATE%')
                         ->orWhere('calendardata', 'like', '%EXDATE%');
                })
                ->count();

            $event13Range = Event::where('id', 13)
                ->whereHas('meta', function ($meta) use ($rangeStart, $rangeEnd) {
                    $meta->where(function ($range) use ($rangeStart, $rangeEnd) {
                        $range->where('start_at', '<=', $rangeEnd)
                              ->where(function ($bounds) use ($rangeStart) {
                                  $bounds->where('end_at', '>=', $rangeStart)
                                         ->orWhereNull('end_at');
                              });
                    });
                })
                ->exists();

            $event13Rrule = Event::where('id', 13)
                ->whereHas('meta', function ($meta) {
                    $meta->whereNotNull('extra->rrule');
                })
                ->exists();

            $event13Ical = Event::where('id', 13)
                ->where(function ($ical) {
                    $ical->where('calendardata', 'like', '%RRULE%')
                         ->orWhere('calendardata', 'like', '%RDATE%')
                         ->orWhere('calendardata', 'like', '%EXDATE%');
                })
                ->exists();
            Log::info('calendar all-day debug', [
                'view' => $view,
                'range_start' => $range['start']->toDateTimeString(),
                'range_end' => $range['end']->toDateTimeString(),
                'span_start' => $span['start']->toDateTimeString(),
                'span_end' => $span['end']->toDateTimeString(),
                'meta_range_count' => $metaRangeCount,
                'meta_rrule_count' => $metaRruleCount,
                'ical_rrule_count' => $icalRruleCount,
                'event_13_range' => $event13Range,
                'event_13_meta_rrule' => $event13Rrule,
                'event_13_ical_rrule' => $event13Ical,
                'calendar_ids' => $calendarIds->all(),
                'events_for_calendars' => Event::whereIn('calendarid', $calendarIds)->count(),
                'event_13_calendar_id' => $event13CalendarId,
                'event_13_in_scope' => $event13CalendarId ? $calendarIds->contains($event13CalendarId) : false,
                'events_total' => $events->count(),
                'events_all_day' => $allDayEvents->count(),
                'events_time' => $timeEvents->count(),
                'all_day_sample' => $allDayEvents->take(5)->values()->map(fn ($e) => [
                    'id' => $e['id'] ?? null,
                    'occurrence' => $e['occurrence'] ?? null,
                    'start' => $e['start'] ?? null,
                    'end' => $e['end'] ?? null,
                    'timezone' => $e['timezone'] ?? null,
                ])->all(),
            ]);
        }

        /**
         *
         * mini calendar
         */

        // create the mini calendar grid based on the mini cal controls
        $mini_anchor = $request->query('mini', $range['start']->toDateString());
        $mini_anchor_date = $rangeResolver->safeDate($mini_anchor, $tz, $range['start']->toDateString());

        $mini_start = $mini_anchor_date->copy()->startOfMonth();

        $mini_nav = [
            'prev'  => $mini_start->copy()->subMonth()->toDateString(),
            'next'  => $mini_start->copy()->addMonth()->toDateString(),
            'today' => Carbon::today($tz)->startOfMonth()->toDateString(),
            'label' => $mini_start->format('F Y'),
        ];
        $mini_headers = $viewBuilder->weekdayHeaders($tz, $weekStart);

        // compute the mini's 42-day span (Mon..Sun, 6 rows)
        $mini_grid_start = $mini_start->copy()->startOfWeek($weekStart);
        $mini_grid_end   = $mini_start->copy()->endOfMonth()->endOfWeek($weekEnd);
        if ($mini_grid_start->diffInDays($mini_grid_end) + 1 < 42) {
            $mini_grid_end->addWeek();
        }

        // fetch events specifically for the mini-span
        $mini_events = Event::forCalendarsInRange(
            $calendars->pluck('id'),
            $mini_grid_start,
            $mini_grid_end
        );

        $mini_events = $viewBuilder->buildEventPayloads(
            $mini_events,
            $calendar_map,
            $timeFormat,
            $view,
            ['start' => $mini_grid_start, 'end' => $mini_grid_end],
            $tz,
            $recurrence,
            ['start' => $mini_grid_start, 'end' => $mini_grid_end],
            null,
        );

        // now build the mini from mini_events (not from $events)
        $mini = $viewBuilder->buildMiniGrid($mini_start, $mini_events, $tz, $weekStart, $weekEnd);

        /**
         *
         * main calendar grid
         */

        // create the calendar grid of days
        $grid = $viewBuilder->buildCalendarGrid($view, $range, $events, $tz, $span);

        // get the title
        $header = $rangeResolver->headerTitle($view, $range['start'], $range['end']);

        // format the data for the frontend, including separate arrays for events specifically and the big grid
        $payload = [
            'view'          => $view,
            'range'         => $range,
            'nav'           => $nav,
            'header'        => $header,
            'week_start'    => $weekStart,
            'hgroup'        => $viewBuilder->viewHeaders($view, $range, $tz, $weekStart),
            'events'        => $events,    // keyed by occurrence
            'events_time'   => $timeEvents,
            'events_all_day'=> $allDayDisplay,
            'has_all_day'   => $hasAllDayEvents,
            'grid'          => $grid,      // day objects hold only ID-sets
            'mini'          => $mini,      // mini calendar days with events for indicators
            'mini_nav'      => $mini_nav,  // separate mini calendar navigation
            'mini_headers'  => $mini_headers,
            'active'        => [
                'date'      => $range['start']->toDateString(),
                'year'      => $range['start']->format('Y'),
                'month'     => $range['start']->format("F"),
                'day'       => $range['start']->format("d"),
            ],
            'daytime_hours' => [
                'enabled'   => $daytimeHoursEnabled,
                'start'     => $daytimeHoursRange['start'],
                'end'       => $daytimeHoursRange['end'],
                'rows'      => $daytimeHoursRows,
            ],
            'timezone'      => $tz,
            'calendars'     => $calendars->mapWithKeys(function ($cal)
            {
                $color = $cal->meta_color
                    ?? $cal->calendarcolor
                    ?? default_calendar_color();
                $colorFg = $cal->meta_color_fg
                    ?? contrast_text_color($color);

                return [
                    $cal->id => [
                        'id'        => $cal->id,
                        'slug'      => $cal->slug,
                        'name'      => $cal->displayname,
                        'color'     => $color,
                        'color_fg'  => $colorFg,
                        'visible'   => $cal->visible,
                        'is_remote' => $cal->is_remote,
                    ],
                ];
            }),
        ];

        // time-based payload values
        $timeBased = in_array($view, ['day', 'week', 'four'], true);
        if ($timeBased) {
            // create the time gutter if we're in a time-based view
            $payload['slots'] = $viewBuilder->timeSlots(
                $range['start'],
                $tz,
                $timeFormat,
                $daytimeHoursEnabled ? $daytimeHoursRange : null
            );
            $payload['time_format'] = $timeFormat; // optional, if the blade cares

            // add the now indicator
            $payload['now'] = $viewBuilder->nowIndicator(
                $view,
                $range,
                $tz,
                15,
                1,
                $daytimeHoursEnabled ? $daytimeHoursRange : null
            );
        }

        // send the density array always, even though it doesn't matter for month
        $payload['density'] = [
            'step'        => $stepMinutes,  // 15|30|60
            'label_every' => $labelEvery,   // 1|2|4
            'anchor'      => $anchorDate,
        ];

        return view('calendar.index', $payload);
    }

    private function buildAllDayDisplay($allDayEvents)
    {
        $items = collect();
        $groups = $allDayEvents->groupBy(fn ($event) => (int) ($event['start_col'] ?? 1));

        foreach ($groups as $col => $events) {
            $sorted = $events->sortBy('start')->values();
            $total = $sorted->count();

            if ($total <= 3) {
                foreach ($sorted as $index => $event) {
                    $items->push(array_merge($event, [
                        'all_day_row' => $index + 1,
                        'all_day_more' => false,
                        'all_day_overflow' => false,
                    ]));
                }
                continue;
            }

            foreach ($sorted as $index => $event) {
                $items->push(array_merge($event, [
                    'all_day_row' => $index + 1,
                    'all_day_more' => false,
                    'all_day_overflow' => $index >= 2,
                ]));
            }

            $items->push([
                'id' => 'more-'.$col,
                'calendar_slug' => $sorted[0]['calendar_slug'] ?? null,
                'start_col' => $col,
                'all_day_row' => 3,
                'all_day_more' => true,
                'all_day_overflow' => false,
                'all_day_more_count' => $total - 2,
                'visible' => true,
            ]);
        }

        return $items->sortBy([
            ['start_col', 'asc'],
            ['all_day_row', 'asc'],
        ])->values();
    }

    /**
     * create sabre calendar + meta
     */
    public function store(Request $request, CreateCalendar $creator)
    {
        $data = $request->validate([
            'name'        => 'required|string|max:100',
            'description' => 'nullable|string|max:255',
            'timezone'    => 'required|string|max:64',
            'color'       => 'nullable|regex:/^#[0-9A-Fa-f]{6}$/',
            'redirect'    => 'nullable|string', // where to go after creating
        ]);

        $creator->create($request->user(), $data);
        $redirect = $data['redirect'] ?? route('calendar.index');

        return redirect($redirect)->with('toast', [
            'message' => __('Calendar created!'),
            'type'    => 'success',
        ]);
    }

    /**
     * show calendar details
     */
    public function show(Calendar $calendar)
    {
        $this->authorize('view', $calendar);

        $calendar->load([
            'meta',
            'instances' => fn ($q) => $q->where('principaluri', auth()->user()->uri),
        ]);

        /* grab the single instance for convenience in the view */
        $instance = $calendar->instances->first();
        $caldavUrl = $instance?->caldavUrl();   // null-safe

        /* events + meta, newest first */
        $events = $calendar->events()
            ->with('meta')
            ->orderByDesc('lastmodified')
            ->get();

        return view(
            'calendar.show',
            compact('calendar', 'instance', 'events', 'caldavUrl')
        );
    }

    /**
     * edit calendar page
     */
    public function edit(Calendar $calendar)
    {
        $this->authorize('update', $calendar);

        $calendar->load([
        'meta',
        'instances' => fn ($q) =>
            $q->where('principaluri', auth()->user()->uri),
        ]);

        $instance = $calendar->instances->first();   // may be null but shouldn’t

        return view('calendar.edit', compact('calendar', 'instance'));
    }

    /**
     * update sabre + meta records
     */
    public function update(Request $request, Calendar $calendar)
    {
        $this->authorize('update', $calendar);

        $data = $request->validate([
            'name'        => 'required|string|max:100',
            'description' => 'nullable|string|max:255',
            'timezone'    => 'required|string',
            'color'       => 'nullable|regex:/^#[0-9A-Fa-f]{6}$/',
        ]);

        // update the instance row
        $calendar->instances()
            ->where('principaluri', auth()->user()->uri)
            ->update([
                'displayname' => $data['name'],
                'description' => $data['description'] ?? '',
                'calendarcolor' => $data['color'] ?? null,
                'timezone'    => $data['timezone'],
            ]);

        // bump synctoken on master calendar row
        $calendar->increment('synctoken');

        // update calendar meta (our table)
        $color = calendar_color($data);
        $calendar->meta()->updateOrCreate([], [
            'color'    => $color,
            'color_fg' => contrast_text_color($color),
        ]);

        return redirect()
            ->route('calendar.show', $calendar)
            ->with('toast', __('Calendar saved successfully!'));
    }

    /**
     *
     * Delete calendar
     * @todo
     */
    public function destroy(Calendar $calendar)
    {
        $this->authorize('delete', $calendar);
        $calendar->delete();          // cascades to meta via FK
        return redirect()->route('calendar.index');
    }

    /**
     *
     * Private helpers
     */

}
