<?php

declare(strict_types=1);

final class CalendarFeedService
{
    public static function generateToken(int $userId): string
    {
        $token = bin2hex(random_bytes(32));
        DB::pdo()
            ->prepare(
                "UPDATE users SET agenda_feed_token_hash=:hash,agenda_feed_created_at=datetime('now'),updated_at=datetime('now') WHERE id=:id AND active=1",
            )
            ->execute([':hash' => hash('sha256', $token), ':id' => $userId]);
        return $token;
    }
    public static function revoke(int $userId): void
    {
        DB::pdo()
            ->prepare(
                "UPDATE users SET agenda_feed_token_hash=NULL,agenda_feed_created_at=NULL,updated_at=datetime('now') WHERE id=?",
            )
            ->execute([$userId]);
    }
    public static function userForToken(string $token): ?array
    {
        if (!preg_match('/^[a-f0-9]{64}$/', $token)) {
            return null;
        }
        $s = DB::pdo()->prepare('SELECT id,role FROM users WHERE agenda_feed_token_hash=? AND active=1 LIMIT 1');
        $s->execute([hash('sha256', $token)]);
        $user = $s->fetch(PDO::FETCH_ASSOC) ?: null;
        if (!$user || !PermissionService::can('agenda', 'view', (string) $user['role'])) {
            return null;
        }
        return $user;
    }
    public static function absoluteUrl(string $path): string
    {
        $configured = rtrim((string) getenv('APP_URL'), '/');
        if ($configured !== '' && preg_match('~^https?://~i', $configured)) {
            return $configured . $path;
        }
        $proto = (string) ($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '');
        if (!in_array($proto, ['http', 'https'], true)) {
            $proto = !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' ? 'https' : 'http';
        }
        $host = (string) ($_SERVER['HTTP_X_FORWARDED_HOST'] ?? ($_SERVER['HTTP_HOST'] ?? 'localhost'));
        $host = trim(explode(',', $host)[0]);
        if (!preg_match('/^[a-z0-9.:-]+$/i', $host)) {
            $host = 'localhost';
        }
        return $proto . '://' . $host . $path;
    }
    public static function render(array $items, string $name): string
    {
        $lines = [
            'BEGIN:VCALENDAR',
            'VERSION:2.0',
            'PRODID:-//Globinours//Agenda refuge//FR',
            'CALSCALE:GREGORIAN',
            'METHOD:PUBLISH',
            'X-WR-CALNAME:' . self::escape($name),
            'X-WR-TIMEZONE:Europe/Paris',
        ];
        $stamp = gmdate('Ymd\THis\Z');
        foreach ($items as $item) {
            $source = (string) ($item['source_type'] ?? 'agenda');
            $sourceId = (int) ($item['source_id'] ?? $item['id']);
            $instance = (string) $item['instance_starts_at'];
            $uid = $source . '-' . $sourceId . '-' . substr(hash('sha256', $instance), 0, 12) . '@globinours';
            $lines[] = 'BEGIN:VEVENT';
            $lines[] = 'UID:' . $uid;
            $lines[] = 'DTSTAMP:' . $stamp;
            if ((int) $item['all_day'] === 1) {
                $start = new DateTimeImmutable(substr($instance, 0, 10));
                $lines[] = 'DTSTART;VALUE=DATE:' . $start->format('Ymd');
                $lines[] = 'DTEND;VALUE=DATE:' . $start->modify('+1 day')->format('Ymd');
            } else {
                $start = new DateTimeImmutable($instance, new DateTimeZone('Europe/Paris'));
                $end = !empty($item['instance_ends_at'])
                    ? new DateTimeImmutable($item['instance_ends_at'], new DateTimeZone('Europe/Paris'))
                    : $start->modify('+1 hour');
                $lines[] = 'DTSTART;TZID=Europe/Paris:' . $start->format('Ymd\THis');
                $lines[] = 'DTEND;TZID=Europe/Paris:' . $end->format('Ymd\THis');
            }
            $lines[] = 'SUMMARY:' . self::escape((string) $item['title']);
            $description = trim((string) ($item['description'] ?? ''));
            if (!empty($item['animal_name'])) {
                $description = trim($description . "\n" . t('animal.animal') . ' : ' . $item['animal_name']);
            }
            if ($description !== '') {
                $lines[] = 'DESCRIPTION:' . self::escape($description);
            }
            if (!empty($item['location'])) {
                $lines[] = 'LOCATION:' . self::escape((string) $item['location']);
            }
            if (!empty($item['animal_id'])) {
                $lines[] = 'URL:' . self::escape(self::absoluteUrl('/animal?id=' . (int) $item['animal_id']));
            }
            $lines[] = 'STATUS:' . (($item['status'] ?? 'planned') === 'completed' ? 'COMPLETED' : 'CONFIRMED');
            $lines[] = 'END:VEVENT';
        }
        $lines[] = 'END:VCALENDAR';
        return implode("\r\n", array_map([self::class, 'fold'], $lines)) . "\r\n";
    }
    private static function escape(string $value): string
    {
        return str_replace(['\\', "\r\n", "\n", "\r", ';', ','], ['\\\\', '\\n', '\\n', '\\n', '\\;', '\\,'], $value);
    }
    private static function fold(string $line): string
    {
        $out = '';
        while (strlen($line) > 73) {
            $cut = 73;
            while ($cut > 0 && (ord($line[$cut]) & 0xc0) === 0x80) {
                $cut--;
            }
            $out .= substr($line, 0, $cut) . "\r\n ";
            $line = substr($line, $cut);
        }
        return $out . $line;
    }
}
