<?php

namespace App\Services\Event;

use App\Models\Event;
use App\Models\User;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use Sabre\VObject\Component\VEvent;
use Sabre\VObject\Reader;

class EventAttendeeSynchronizer
{
    public function syncFromCalendarData(Event $event, ?string $calendarData = null): void
    {
        $attendees = $this->extractFromCalendarData($calendarData ?? $event->calendardata);
        $this->syncRows($event, $attendees);
    }

    public function syncRows(Event $event, array $attendees): void
    {
        $rows = $this->normalizeRows($attendees);

        $event->attendees()->delete();
        if (empty($rows)) {
            return;
        }

        foreach ($rows as &$row) {
            $email = $row['email'] ?? null;
            if (!$email) {
                $row['attendee_user_id'] = null;
                continue;
            }

            $row['attendee_user_id'] = User::query()
                ->whereRaw('lower(email) = ?', [Str::lower($email)])
                ->value('id');
        }
        unset($row);

        $event->attendees()->createMany($rows);
    }

    public function extractFromCalendarData(?string $calendarData): array
    {
        if (!$calendarData) {
            return [];
        }

        try {
            $vcalendar = Reader::read($calendarData);
        } catch (\Throwable $e) {
            return [];
        }

        $vevent = $vcalendar->select('VEVENT')[0] ?? null;
        if (!$vevent instanceof VEvent) {
            return [];
        }

        return $this->extractFromVevent($vevent);
    }

    public function extractFromVevent(VEvent $vevent): array
    {
        $items = [];

        foreach ($vevent->select('ATTENDEE') as $attendee) {
            $params = $this->extractParams($attendee);
            $uri = $this->normalizeAttendeeUri((string) $attendee->getValue());
            $email = $this->extractEmail($uri, $params);

            $items[] = [
                'attendee_uri' => $uri,
                'email' => $email,
                'name' => $params['CN'] ?? null,
                'role' => isset($params['ROLE']) ? strtoupper($params['ROLE']) : null,
                'partstat' => isset($params['PARTSTAT']) ? strtoupper($params['PARTSTAT']) : null,
                'cutype' => isset($params['CUTYPE']) ? strtoupper($params['CUTYPE']) : null,
                'rsvp' => $this->normalizeBooleanParam($params['RSVP'] ?? null),
                'is_organizer' => false,
                'extra' => $this->extraParams($params, [
                    'CN',
                    'ROLE',
                    'PARTSTAT',
                    'CUTYPE',
                    'RSVP',
                    'EMAIL',
                ]),
            ];
        }

        if (isset($vevent->ORGANIZER)) {
            $organizer = $vevent->ORGANIZER;
            $params = $this->extractParams($organizer);
            $uri = $this->normalizeAttendeeUri((string) $organizer->getValue());
            $email = $this->extractEmail($uri, $params);

            $items[] = [
                'attendee_uri' => $uri,
                'email' => $email,
                'name' => $params['CN'] ?? null,
                'role' => 'CHAIR',
                'partstat' => null,
                'cutype' => isset($params['CUTYPE']) ? strtoupper($params['CUTYPE']) : null,
                'rsvp' => null,
                'is_organizer' => true,
                'extra' => $this->extraParams($params, ['CN', 'CUTYPE', 'EMAIL']),
            ];
        }

        return $items;
    }

    private function normalizeRows(array $attendees): array
    {
        $rows = [];
        $seen = [];

        foreach ($attendees as $item) {
            $email = $this->normalizeEmail(Arr::get($item, 'email'));
            $uri = $this->normalizeAttendeeUri(Arr::get($item, 'attendee_uri'));
            if (!$uri && $email) {
                $uri = 'mailto:' . $email;
            }

            if (!$uri && !$email) {
                continue;
            }

            $key = Str::lower($uri ?: $email);
            if (isset($seen[$key])) {
                continue;
            }
            $seen[$key] = true;

            $role = Arr::get($item, 'role');
            $partstat = Arr::get($item, 'partstat');
            $cutype = Arr::get($item, 'cutype');

            $rows[] = [
                'attendee_uri' => $uri,
                'email' => $email,
                'name' => $this->normalizeString(Arr::get($item, 'name')),
                'role' => $role ? strtoupper((string) $role) : null,
                'partstat' => $partstat ? strtoupper((string) $partstat) : null,
                'cutype' => $cutype ? strtoupper((string) $cutype) : null,
                'rsvp' => Arr::exists($item, 'rsvp') ? (bool) Arr::get($item, 'rsvp') : null,
                'is_organizer' => (bool) Arr::get($item, 'is_organizer', false),
                'extra' => is_array(Arr::get($item, 'extra')) ? Arr::get($item, 'extra') : null,
            ];
        }

        return $rows;
    }

    private function extractParams($property): array
    {
        $params = [];
        foreach ($property->parameters() as $parameter) {
            $params[strtoupper((string) $parameter->name)] = trim((string) $parameter->getValue());
        }

        return $params;
    }

    private function extractEmail(?string $uri, array $params): ?string
    {
        $fromParam = $this->normalizeEmail($params['EMAIL'] ?? null);
        if ($fromParam) {
            return $fromParam;
        }

        if (!$uri) {
            return null;
        }

        if (str_starts_with(Str::lower($uri), 'mailto:')) {
            return $this->normalizeEmail(substr($uri, 7));
        }

        return str_contains($uri, '@') ? $this->normalizeEmail($uri) : null;
    }

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

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

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

        return $value;
    }

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

        return Str::lower($email);
    }

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

    private function normalizeBooleanParam(?string $value): ?bool
    {
        if ($value === null || $value === '') {
            return null;
        }

        return in_array(strtoupper($value), ['1', 'TRUE', 'YES'], true);
    }

    private function extraParams(array $params, array $known): ?array
    {
        $knownMap = array_fill_keys($known, true);
        $extra = array_diff_key($params, $knownMap);

        return empty($extra) ? null : $extra;
    }
}
