<?php
/**
 * Feed iCalendar (RFC 5545) — abonare continuă Google Calendar / Apple Cal / Outlook.
 *
 * Endpoint:  https://adventistpnt.ro/calendar.ics
 * Webcal:    webcal://adventistpnt.ro/calendar.ics
 *
 * Conține:
 *  - Program săptămânal recurent (Sâmbătă: Școala Sabatului 9:30, Serviciu Divin 11:00)
 *  - Toate evenimentele speciale viitoare din private/events.json (max 100)
 *
 * Timezone:   Europe/Bucharest (VTIMEZONE complet RFC 5545)
 * Cache HTTP: public, max-age=1800 (30 min)
 *
 * @author     Canny Agency SRL <office@canny.ro>
 * @copyright  2026 Canny Agency SRL
 * @link       https://www.canny.ro
 */

declare(strict_types=1);

require_once __DIR__ . '/admin/bootstrap_public.php';
require_once __DIR__ . '/admin/bootstrap/data.php';

/* ============================================================
   RATE LIMIT: 30 req/min per IP
   ============================================================ */
$rlId = 'cal_ics_' . preg_replace('/[^a-fA-F0-9:]/', '_', $_SERVER['REMOTE_ADDR'] ?? 'unknown');
if (!check_rate_limit($rlId, 30, 60)) {
    http_response_code(429);
    header('Content-Type: text/plain; charset=utf-8');
    header('Retry-After: 60');
    exit('Too Many Requests');
}

/* ============================================================
   HELPERS ICS
   ============================================================ */

/**
 * Escape valori proprietăți text (SUMMARY, DESCRIPTION, LOCATION).
 * RFC 5545 §3.3.11: escape ,  ;  \  și folding la 75 octeți.
 */
function ics_escape_text(string $text): string {
    $text = str_replace('\\', '\\\\', $text);
    $text = str_replace(';', '\\;', $text);
    $text = str_replace(',', '\\,', $text);
    $text = str_replace("\r\n", '\\n', $text);
    $text = str_replace("\n", '\\n', $text);
    $text = str_replace("\r", '\\n', $text);
    return $text;
}

/**
 * Fold o linie ICS la max 75 octeti (RFC 5545 §3.1).
 * Continuările încep cu CRLF + SPACE.
 */
function ics_fold(string $line): string {
    $output = '';
    // Lucrează cu bytes (octeți) — mb_strcut pentru UTF-8 safe
    while (strlen($line) > 75) {
        $chunk  = mb_strcut($line, 0, 75, 'UTF-8');
        $output .= $chunk . "\r\n ";
        $line    = mb_strcut($line, strlen($chunk), null, 'UTF-8');
    }
    $output .= $line;
    return $output;
}

/**
 * Adaugă o proprietate ICS cu folding automat.
 */
function ics_prop(string $name, string $value): string {
    return ics_fold($name . ':' . $value) . "\r\n";
}

/**
 * Convertește un string datetime în format ICS.
 * Dacă $allDay e true, returnează format DATE (YYYYMMDD).
 * Altfel DATE-TIME cu TZID=Europe/Bucharest.
 */
function ics_datetime(string $iso, bool $allDay = false): array {
    if ($allDay) {
        $ts  = strtotime($iso);
        return [
            'param' => 'VALUE=DATE',
            'value' => date('Ymd', $ts ?: time()),
        ];
    }
    $ts = strtotime($iso);
    return [
        'param' => 'TZID=Europe/Bucharest',
        'value' => date('Ymd\THis', $ts ?: time()),
    ];
}

/**
 * Generează un VEVENT complet din array de eveniment.
 * UID stabil bazat pe id sau hash determinist (nu se duplică la re-sync).
 */
function build_vevent(array $ev, string $dtstamp): string {
    $startRaw = trim($ev['start'] ?? '');
    $endRaw   = trim($ev['end']   ?? $startRaw);

    if ($startRaw === '') {
        return '';
    }

    $allDay  = strlen($startRaw) <= 10;
    $startDt = ics_datetime($startRaw, $allDay);

    // End: pentru all-day trebuie DTEND = ziua următoare (RFC 5545 §3.6.1)
    if ($allDay) {
        $endRaw2 = $endRaw ?: $startRaw;
        // Adaugă +1 zi la end pentru all-day
        $endTs   = strtotime($endRaw2) ?: strtotime($startRaw);
        $endDt   = ['param' => 'VALUE=DATE', 'value' => date('Ymd', $endTs + 86400)];
    } else {
        if ($endRaw === '' || $endRaw === $startRaw) {
            // Default: eveniment de 1 oră
            $endTs  = (strtotime($startRaw) ?: time()) + 3600;
            $endDt  = ['param' => 'TZID=Europe/Bucharest', 'value' => date('Ymd\THis', $endTs)];
        } else {
            $endDt  = ics_datetime($endRaw, false);
        }
    }

    // UID stabil — preferă id explicit, fallback hash determinist
    $evId  = $ev['id'] ?? '';
    $uid   = $evId !== ''
        ? 'ev-' . preg_replace('/[^a-zA-Z0-9_-]/', '', $evId) . '@adventistpnt.ro'
        : md5($startRaw . ($ev['title'] ?? '')) . '@adventistpnt.ro';

    $title    = ics_escape_text($ev['title']    ?? 'Eveniment');
    $location = ics_escape_text($ev['location'] ?? '');
    $descParts = [];
    if (!empty($ev['description'])) {
        $descParts[] = ics_escape_text($ev['description']);
    }
    if ($location !== '') {
        $descParts[] = 'Locație: ' . $location;
    }
    $desc = implode('\\n', $descParts);

    $out  = "BEGIN:VEVENT\r\n";
    $out .= ics_prop('UID', $uid);
    $out .= ics_prop('DTSTAMP', $dtstamp);

    if ($allDay) {
        $out .= ics_fold("DTSTART;{$startDt['param']}:{$startDt['value']}") . "\r\n";
        $out .= ics_fold("DTEND;{$endDt['param']}:{$endDt['value']}") . "\r\n";
    } else {
        $out .= ics_fold("DTSTART;{$startDt['param']}:{$startDt['value']}") . "\r\n";
        $out .= ics_fold("DTEND;{$endDt['param']}:{$endDt['value']}") . "\r\n";
    }

    $out .= ics_prop('SUMMARY', $title);

    if ($location !== '') {
        $out .= ics_prop('LOCATION', $location);
    }
    if ($desc !== '') {
        $out .= ics_fold('DESCRIPTION:' . $desc) . "\r\n";
    }

    // STATUS pentru evenimente anulate
    $status = strtoupper($ev['status'] ?? '');
    if ($status === 'CANCELLED' || $status === 'CANCELED') {
        $out .= "STATUS:CANCELLED\r\n";
    }

    // URL opțional
    if (!empty($ev['url'])) {
        $safeUrl = filter_var($ev['url'], FILTER_VALIDATE_URL) ? $ev['url'] : '';
        if ($safeUrl !== '') {
            $out .= ics_prop('URL', $safeUrl);
        }
    }

    $out .= "END:VEVENT\r\n";
    return $out;
}

/* ============================================================
   VTIMEZONE complet RFC 5545 — Europe/Bucharest
   EET (UTC+2, iarnă) / EEST (UTC+3, vară — ultimul duminică din mart/oct)
   ============================================================ */
function vtimezone_bucharest(): string {
    $tz  = "BEGIN:VTIMEZONE\r\n";
    $tz .= "TZID:Europe/Bucharest\r\n";
    $tz .= "X-LIC-LOCATION:Europe/Bucharest\r\n";
    // Tranziție la EEST — ultimul duminică din martie, 03:00 → 04:00 locală
    $tz .= "BEGIN:DAYLIGHT\r\n";
    $tz .= "TZOFFSETFROM:+0200\r\n";
    $tz .= "TZOFFSETTO:+0300\r\n";
    $tz .= "TZNAME:EEST\r\n";
    $tz .= "DTSTART:19700329T030000\r\n";
    $tz .= "RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU\r\n";
    $tz .= "END:DAYLIGHT\r\n";
    // Tranziție la EET — ultimul duminică din octombrie, 04:00 → 03:00 locală
    $tz .= "BEGIN:STANDARD\r\n";
    $tz .= "TZOFFSETFROM:+0300\r\n";
    $tz .= "TZOFFSETTO:+0200\r\n";
    $tz .= "TZNAME:EET\r\n";
    $tz .= "DTSTART:19701025T040000\r\n";
    $tz .= "RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU\r\n";
    $tz .= "END:STANDARD\r\n";
    $tz .= "END:VTIMEZONE\r\n";
    return $tz;
}

/* ============================================================
   VEVENTS SĂPTĂMÂNALE RECURENTE (program fix)
   DTSTART = primul sâmbătă viitor față de azi (sau azi dacă e sâmbătă)
   RRULE:FREQ=WEEKLY;BYDAY=SA → se repetă la infinit
   ============================================================ */
function build_weekly_events(string $dtstamp): string {
    // Calculează primul sâmbătă de la azi
    $now = new DateTimeImmutable('today', new DateTimeZone('Europe/Bucharest'));
    $dow = (int) $now->format('N'); // 1=Lun … 7=Dum, 6=Sâm
    $daysToSat = ($dow <= 6) ? (6 - $dow) : 0;
    $firstSat  = $now->modify("+{$daysToSat} days");

    $dateSat = $firstSat->format('Ymd');
    $loc     = ics_escape_text('Strada Mihai Viteazu 22, Piatra Neamț');
    $out     = '';

    // --- Școala Sabatului 09:30–11:00 ---
    $uidSS = '20060101T093000-scoala-sabatului@adventistpnt.ro';
    $out  .= "BEGIN:VEVENT\r\n";
    $out  .= ics_prop('UID', $uidSS);
    $out  .= ics_prop('DTSTAMP', $dtstamp);
    $out  .= "DTSTART;TZID=Europe/Bucharest:{$dateSat}T093000\r\n";
    $out  .= "DTEND;TZID=Europe/Bucharest:{$dateSat}T110000\r\n";
    $out  .= ics_prop('SUMMARY', 'Școala Sabatului');
    $out  .= ics_prop('LOCATION', $loc);
    $out  .= ics_prop('DESCRIPTION', 'Studiul Bibliei — program săptămânal sâmbătă dimineața\\nBiserica Adventistă de Ziua a Șaptea Piatra Neamț – Dărmănești');
    $out  .= "RRULE:FREQ=WEEKLY;BYDAY=SA\r\n";
    $out  .= "END:VEVENT\r\n";

    // --- Serviciu Divin 11:00–12:30 ---
    $uidSD = '20060101T110000-serviciu-divin@adventistpnt.ro';
    $out  .= "BEGIN:VEVENT\r\n";
    $out  .= ics_prop('UID', $uidSD);
    $out  .= ics_prop('DTSTAMP', $dtstamp);
    $out  .= "DTSTART;TZID=Europe/Bucharest:{$dateSat}T110000\r\n";
    $out  .= "DTEND;TZID=Europe/Bucharest:{$dateSat}T123000\r\n";
    $out  .= ics_prop('SUMMARY', 'Serviciu Divin');
    $out  .= ics_prop('LOCATION', $loc);
    $out  .= ics_prop('DESCRIPTION', 'Serviciu de adorație — program săptămânal sâmbătă\\nBiserica Adventistă de Ziua a Șaptea Piatra Neamț – Dărmănești');
    $out  .= "RRULE:FREQ=WEEKLY;BYDAY=SA\r\n";
    $out  .= "END:VEVENT\r\n";

    return $out;
}

/* ============================================================
   ÎNCARCĂ EVENIMENTELE SPECIALE (via helper — fără acces direct la JSON)
   ============================================================ */
$eventsData = load_events();
$allEvents  = is_array($eventsData['events'] ?? null) ? $eventsData['events'] : [];

// Filtrare: doar viitoare (end >= azi) și nu mai mult de 100
$today       = new DateTimeImmutable('today');
$futureEvents = array_filter($allEvents, function (array $ev) use ($today): bool {
    $endStr = $ev['end'] ?? $ev['start'] ?? '';
    if ($endStr === '') {
        return false;
    }
    try {
        $end = new DateTimeImmutable(substr($endStr, 0, 10));
        return $end >= $today;
    } catch (\Exception) {
        return false;
    }
});
usort($futureEvents, fn($a, $b) => strcmp($a['start'] ?? '', $b['start'] ?? ''));
$futureEvents = array_slice(array_values($futureEvents), 0, 100);

/* ============================================================
   DTSTAMP: filemtime pe events.json sau now
   ============================================================ */
$eventsFile = PRIVATE_ROOT . '/events.json';
$mtime      = file_exists($eventsFile) ? filemtime($eventsFile) : time();
$dtstamp    = gmdate('Ymd\THis\Z', $mtime ?: time());

/* ============================================================
   CONSTRUIEȘTE FEED-UL
   ============================================================ */
$ics  = "BEGIN:VCALENDAR\r\n";
$ics .= "VERSION:2.0\r\n";
$ics .= "PRODID:-//adventistpnt.ro//Events 1.0//RO\r\n";
$ics .= "CALSCALE:GREGORIAN\r\n";
$ics .= "METHOD:PUBLISH\r\n";
$ics .= ics_prop('X-WR-CALNAME', 'Biserica Adventistă Piatra Neamț');
$ics .= "X-WR-TIMEZONE:Europe/Bucharest\r\n";
$ics .= ics_prop('X-WR-CALDESC', 'Evenimente și program — Biserica Adventistă de Ziua a Șaptea Piatra Neamț – Dărmănești');
$ics .= vtimezone_bucharest();
$ics .= build_weekly_events($dtstamp);

foreach ($futureEvents as $ev) {
    $vevent = build_vevent($ev, $dtstamp);
    if ($vevent !== '') {
        $ics .= $vevent;
    }
}

$ics .= "END:VCALENDAR\r\n";

/* ============================================================
   RĂSPUNS HTTP
   ============================================================ */
header('Content-Type: text/calendar; charset=utf-8');
header('Cache-Control: public, max-age=1800');
header('Content-Length: ' . strlen($ics));
// Previne download forțat pe browsere — prefers streaming (abonare webcal)
header('Content-Disposition: inline; filename="adventistpnt-calendar.ics"');

echo $ics;
