<?php

namespace App\Services\Event;

use App\Models\Event;
use Carbon\Carbon;
use DateTimeZone;
use Illuminate\Support\Str;
use Sabre\VObject\Component\VCalendar;
use Sabre\VObject\Reader;
use Sabre\VObject\Recur\EventIterator;

class EventRecurrence
{
    /**
     * Build a VCALENDAR string from core fields and optional recurrence.
     */
    public function buildCalendar(array $data): string
    {
        $vcalendar = new VCalendar();
        $vcalendar->add('PRODID', '-//Kithkin//Laravel CalDAV//EN');
        $vcalendar->add('VERSION', '2.0');
        $vcalendar->add('CALSCALE', 'GREGORIAN');
        $vevent = $vcalendar->add('VEVENT', []);

        $uid = $data['uid'];
        $startUtc = $data['start_utc'];
        $endUtc = $data['end_utc'];
        $tzid = $data['tzid'] ?? null;
        $allDay = (bool) ($data['all_day'] ?? false);
        $startDate = $data['start_date'] ?? null;
        $endDate = $data['end_date'] ?? null;

        $vevent->add('UID', $uid);
        $vevent->add('DTSTAMP', $startUtc->copy()->utc());

        if ($allDay && $startDate && $endDate) {
            $vevent->add('DTSTART', Carbon::parse($startDate), ['VALUE' => 'DATE']);
            $vevent->add('DTEND', Carbon::parse($endDate), ['VALUE' => 'DATE']);
        } elseif ($tzid) {
            $startLocal = $startUtc->copy()->tz($tzid);
            $endLocal = $endUtc->copy()->tz($tzid);
            $vevent->add('DTSTART', $startLocal, ['TZID' => $tzid]);
            $vevent->add('DTEND', $endLocal, ['TZID' => $tzid]);
        } else {
            $vevent->add('DTSTART', $startUtc->copy()->utc());
            $vevent->add('DTEND', $endUtc->copy()->utc());
        }

        if (!empty($data['summary'])) {
            $vevent->add('SUMMARY', $data['summary']);
        }

        if (!empty($data['description'])) {
            $vevent->add('DESCRIPTION', $data['description']);
        }

        if (!empty($data['location'])) {
            $vevent->add('LOCATION', $data['location']);
        }

        $attendees = $data['attendees'] ?? [];
        if (is_array($attendees)) {
            $organizerAdded = false;

            foreach ($attendees as $attendee) {
                if (!is_array($attendee)) {
                    continue;
                }

                $uri = $this->normalizeAttendeeUri(
                    $attendee['attendee_uri'] ?? ($attendee['email'] ?? null)
                );

                if (!$uri) {
                    continue;
                }

                $params = [];
                if (!empty($attendee['name'])) {
                    $params['CN'] = (string) $attendee['name'];
                }
                if (!empty($attendee['role'])) {
                    $params['ROLE'] = strtoupper((string) $attendee['role']);
                }
                if (!empty($attendee['partstat'])) {
                    $params['PARTSTAT'] = strtoupper((string) $attendee['partstat']);
                }
                if (!empty($attendee['cutype'])) {
                    $params['CUTYPE'] = strtoupper((string) $attendee['cutype']);
                }
                if (array_key_exists('rsvp', $attendee) && $attendee['rsvp'] !== null) {
                    $params['RSVP'] = (bool) $attendee['rsvp'] ? 'TRUE' : 'FALSE';
                }

                if (!empty($attendee['extra']) && is_array($attendee['extra'])) {
                    foreach ($attendee['extra'] as $key => $value) {
                        if (!is_scalar($value)) {
                            continue;
                        }

                        $paramName = strtoupper((string) $key);
                        if ($paramName === '' || isset($params[$paramName])) {
                            continue;
                        }

                        $params[$paramName] = (string) $value;
                    }
                }

                if ((bool) ($attendee['is_organizer'] ?? false) && !$organizerAdded) {
                    $organizerParams = $params;
                    unset($organizerParams['ROLE'], $organizerParams['PARTSTAT'], $organizerParams['RSVP']);
                    $vevent->add('ORGANIZER', $uri, $organizerParams);
                    $organizerAdded = true;
                    continue;
                }

                $vevent->add('ATTENDEE', $uri, $params);
            }
        }

        $rrule = $data['rrule'] ?? null;
        if ($rrule) {
            $vevent->add('RRULE', $rrule);
        }

        $exdates = $data['exdate'] ?? [];
        if (!empty($exdates)) {
            foreach ($exdates as $ex) {
                $dt = Carbon::parse($ex, $tzid ?: 'UTC');
                if ($allDay) {
                    $vevent->add('EXDATE', Carbon::parse($dt->toDateString()), ['VALUE' => 'DATE']);
                } elseif ($tzid) {
                    $vevent->add('EXDATE', $dt, ['TZID' => $tzid]);
                } else {
                    $vevent->add('EXDATE', $dt->utc());
                }
            }
        }

        $rdates = $data['rdate'] ?? [];
        if (!empty($rdates)) {
            foreach ($rdates as $r) {
                $dt = Carbon::parse($r, $tzid ?: 'UTC');
                if ($allDay) {
                    $vevent->add('RDATE', Carbon::parse($dt->toDateString()), ['VALUE' => 'DATE']);
                } elseif ($tzid) {
                    $vevent->add('RDATE', $dt, ['TZID' => $tzid]);
                } else {
                    $vevent->add('RDATE', $dt->utc());
                }
            }
        }

        return $vcalendar->serialize();
    }

    /**
     * Check if a stored event contains recurrence data.
     */
    public function isRecurring(Event $event): bool
    {
        $extra = $event->meta?->extra ?? [];
        if (!empty($extra['rrule'])) {
            return true;
        }

        return Str::contains($event->calendardata ?? '', ['RRULE', 'RDATE', 'EXDATE']);
    }

    /**
     * Expand recurring instances within the requested range.
     *
     * Returns an array of ['start' => Carbon, 'end' => Carbon, 'recurrence_id' => string|null]
     */
    public function expand(Event $event, Carbon $rangeStart, Carbon $rangeEnd): array
    {
        $vcalendar = $this->readCalendar($event->calendardata);
        if (!$vcalendar || empty($vcalendar->VEVENT)) {
            return [];
        }

        $vevent = $vcalendar->VEVENT;
        $uid = (string) $vevent->UID;

        $startTz = $vevent->DTSTART?->getDateTime()?->getTimezone()
            ?? new DateTimeZone('UTC');

        $iter = new EventIterator($vcalendar, $uid);
        $baseStart = $vevent->DTSTART?->getDateTime();
        $baseEnd = $vevent->DTEND?->getDateTime();
        $durationSeconds = 0;
        if ($baseStart && $baseEnd) {
            $durationSeconds = max(0, $baseEnd->getTimestamp() - $baseStart->getTimestamp());
        }

        $searchStart = $rangeStart->copy()->subSeconds($durationSeconds)->setTimezone($startTz);
        $iter->fastForward($searchStart->toDateTime());

        $items = [];
        while ($iter->valid()) {
            $start = Carbon::instance($iter->getDTStart());
            $end = Carbon::instance($iter->getDTEnd());
            $allDayTz = null;
            if ($event->meta?->all_day) {
                $allDayTz = $event->meta?->tzid
                    ?? ($event->meta?->extra['tzid'] ?? null);
            }

            if ($allDayTz) {
                $start = Carbon::parse($start->toDateString(), $allDayTz);
                $end = Carbon::parse($end->toDateString(), $allDayTz);
            }

            $startUtc = $start->copy()->utc();
            $endUtc = $end->copy()->utc();

            if ($startUtc->gt($rangeEnd)) {
                break;
            }

            if ($endUtc->lte($rangeStart)) {
                $iter->next();
                continue;
            }

            $items[] = [
                'start' => $startUtc,
                'end' => $endUtc,
                'recurrence_id' => $startUtc->format('Ymd\\THis\\Z'),
            ];

            $iter->next();
        }

        return $items;
    }

    /**
     * Resolve a single occurrence by its DTSTART.
     */
    public function resolveOccurrence(Event $event, Carbon $occurrenceStart): ?array
    {
        $rangeStart = $occurrenceStart->copy()->subDay();
        $rangeEnd = $occurrenceStart->copy()->addDay();

        foreach ($this->expand($event, $rangeStart, $rangeEnd) as $occ) {
            if ($occ['start']->equalTo($occurrenceStart)) {
                return $occ;
            }
        }

        return null;
    }

    public function extractRrule(Event $event): ?string
    {
        $vcalendar = $this->readCalendar($event->calendardata);
        if (!$vcalendar || empty($vcalendar->VEVENT)) {
            return null;
        }

        $vevent = $vcalendar->VEVENT;
        return isset($vevent->RRULE) ? (string) $vevent->RRULE : null;
    }

    private function readCalendar(?string $ical): ?VCalendar
    {
        if (!$ical) {
            return null;
        }

        try {
            return Reader::read($ical);
        } catch (\Throwable $e) {
            return null;
        }
    }

    private function normalizeAttendeeUri(mixed $value): ?string
    {
        $uri = trim((string) $value);
        if ($uri === '') {
            return null;
        }

        if (str_contains($uri, '@') && !str_contains($uri, ':')) {
            return 'mailto:' . Str::lower($uri);
        }

        if (str_starts_with(Str::lower($uri), 'mailto:')) {
            return 'mailto:' . Str::lower(substr($uri, 7));
        }

        return $uri;
    }
}
