<?php

declare(strict_types=1);

namespace CalendarPlugin\Domain;

final class CalDavService
{
    public function __construct(
        private readonly EventService $events,
        private readonly IcsService $ics
    ) {
    }

    public function listResources(): array
    {
        $items = [];
        foreach ($this->events->listEvents() as $event) {
            $resource = $this->resourceForEvent($event);
            $items[] = [
                'resource' => $resource,
                'href' => '/caldav/calendars/public/' . $resource,
                'uid' => (string) ($event['uid'] ?? ''),
                'etag' => (string) ($event['etag'] ?? ''),
                'updated_at' => (string) ($event['updated_at'] ?? ''),
                'sync_version' => (int) ($event['sync_version'] ?? 1),
            ];
        }
        return $items;
    }

    public function getObject(string $resource): ?array
    {
        $event = $this->findEventByResource($resource);
        if (!$event) {
            return null;
        }
        $ics = $this->ics->buildCalendar(
            [$event],
            fn(int $eventId): array => $this->events->getDeletedOccurrenceKeys($eventId)
        );

        return [
            'resource' => $resource,
            'etag' => (string) ($event['etag'] ?? ''),
            'event' => $event,
            'ics' => $ics,
        ];
    }

    public function putObject(string $resource, string $icsPayload, ?string $ifMatch = null, ?string $ifNoneMatch = null, ?int $userId = null): array
    {
        $payload = $this->ics->parseEventFromIcs($icsPayload);
        if ($payload === null) {
            return ['error' => ['code' => 'invalid_ics', 'message' => 'invalid iCalendar payload', 'status' => 422]];
        }

        $existing = $this->events->getEventByResource($resource);
        if (!$existing) {
            $existing = $this->findEventByResource($resource);
        }
        if ($ifNoneMatch === '*' && $existing) {
            return ['error' => ['code' => 'precondition_failed', 'message' => 'resource already exists', 'status' => 412]];
        }
        if ($ifMatch !== null) {
            if (!$existing) {
                return ['error' => ['code' => 'precondition_failed', 'message' => 'resource does not exist', 'status' => 412]];
            }
            if ((string) ($existing['etag'] ?? '') !== trim($ifMatch)) {
                return ['error' => ['code' => 'precondition_failed', 'message' => 'etag mismatch', 'status' => 412]];
            }
        }

        $payload['caldav_resource'] = $resource;
        if ($userId !== null) {
            $payload['last_modified_by_user_id'] = $userId;
        }

        $deleted = (array) ($payload['deleted_occurrence_keys'] ?? []);
        unset($payload['deleted_occurrence_keys']);

        if ($existing) {
            $nextSyncVersion = ((int) ($existing['sync_version'] ?? 1)) + 1;
            $payload['sync_version'] = $nextSyncVersion;
            $payload['etag'] = $this->etagFor((string) ($payload['uid'] ?? $existing['uid'] ?? ''), $nextSyncVersion);
            $event = $this->events->updateEvent((int) $existing['id'], $payload);
            if (!$event) {
                return ['error' => ['code' => 'update_failed', 'message' => 'failed to update object', 'status' => 500]];
            }
            $this->events->syncDeletedOccurrenceKeys((int) $event['id'], $deleted, true);
            $event = $this->events->getEvent((int) $event['id']) ?? $event;

            return ['status' => 204, 'created' => false, 'event' => $event];
        }

        $payload['sync_version'] = 1;
        $payload['etag'] = $this->etagFor((string) ($payload['uid'] ?? ''), 1);
        $event = $this->events->createEvent($payload);
        $this->events->syncDeletedOccurrenceKeys((int) $event['id'], $deleted, true);
        $event = $this->events->getEvent((int) $event['id']) ?? $event;

        return ['status' => 201, 'created' => true, 'event' => $event];
    }

    public function deleteObject(string $resource): array
    {
        $existing = $this->findEventByResource($resource);
        if (!$existing) {
            return ['error' => ['code' => 'not_found', 'message' => 'resource not found', 'status' => 404]];
        }

        $ok = $this->events->deleteEvent((int) $existing['id']);
        if (!$ok) {
            return ['error' => ['code' => 'delete_failed', 'message' => 'failed to delete resource', 'status' => 500]];
        }

        return ['status' => 204, 'deleted' => true];
    }

    public function multiget(array $resources): array
    {
        $out = [];
        foreach ($resources as $resource) {
            $resource = basename((string) $resource);
            if ($resource === '') {
                continue;
            }
            $object = $this->getObject($resource);
            if ($object === null) {
                $out[] = ['resource' => $resource, 'status' => 404];
                continue;
            }
            $out[] = [
                'resource' => $resource,
                'status' => 200,
                'etag' => $object['etag'],
                'ics' => $object['ics'],
            ];
        }
        return $out;
    }

    public function listDeletedResources(int $limit = 500): array
    {
        $rows = $this->events->listCalDavTombstones($limit);
        return array_values(array_filter(array_map(static function (array $row): string {
            return trim((string) ($row['resource'] ?? ''));
        }, $rows)));
    }

    public function resourceForEvent(array $event): string
    {
        $resource = trim((string) ($event['caldav_resource'] ?? ''));
        if ($resource !== '') {
            return $resource;
        }
        return (string) ($event['uid'] ?? 'event-' . (string) ($event['id'] ?? 0)) . '.ics';
    }

    private function etagFor(string $uid, int $version): string
    {
        return '"' . substr(sha1($uid . ':' . $version . ':' . gmdate('c')), 0, 16) . '"';
    }

    private function findEventByResource(string $resource): ?array
    {
        foreach ($this->events->listEvents() as $event) {
            if ($this->resourceForEvent($event) === $resource) {
                return $event;
            }
        }
        return null;
    }
}
