<?php

require_once __DIR__ . '/functions_cache.php';

function function_wecker_hole_alle(): array
{
    $c = function_cache_get('wecker_all');
    if ($c !== null) {
        return $c;
    }
    $sql = "
        SELECT *
        FROM wecker
        ORDER BY weckzeit
    ";
    $rows = function_cyldb_select($GLOBALS['pdo'], $sql);
    function_cache_set('wecker_all', $rows);
    return $rows;
}

function function_wecker_hole_aktive(): array
{
    $c = function_cache_get('wecker_aktive');
    if ($c !== null) {
        return $c;
    }
    $sql = "
        SELECT *
        FROM wecker
        WHERE aktiv = :aktiv
        ORDER BY weckzeit
    ";
    $rows = function_cyldb_select($GLOBALS['pdo'], $sql, [
        'aktiv' => 1
    ]);
    function_cache_set('wecker_aktive', $rows);
    return $rows;
}

function function_wecker_hole(int $id): ?array
{
    $sql = "
        SELECT *
        FROM wecker
        WHERE id = :id
    ";

    $rows = function_cyldb_select($GLOBALS['pdo'], $sql, [
        'id' => $id
    ]);

    return $rows[0] ?? null;
}

function function_wecker_hole_wochentage(int $weckerId): array
{
    $sql = "
        SELECT wochentag
        FROM wecker_wochentage
        WHERE wecker_id = :id
        ORDER BY wochentag
    ";

    $rows = function_cyldb_select($GLOBALS['pdo'], $sql, [
        'id' => $weckerId
    ]);

    return array_column($rows, 'wochentag');
}

/**
 * Alle Wochentage aller Wecker in einer Abfrage (für Index-Ladezeit).
 * @return array<int, array<int>> wecker_id => [wochentag, ...]
 */
function function_wecker_hole_wochentage_alle(): array
{
    $sql = "SELECT wecker_id, wochentag FROM wecker_wochentage ORDER BY wecker_id, wochentag";
    $rows = function_cyldb_select($GLOBALS['pdo'], $sql);
    $map = [];
    foreach ($rows as $r) {
        $id = (int) ($r['wecker_id'] ?? 0);
        $tag = (int) ($r['wochentag'] ?? 0);
        if ($id > 0) {
            if (!isset($map[$id])) {
                $map[$id] = [];
            }
            $map[$id][] = $tag;
        }
    }
    return $map;
}

function function_wecker_hole_audio(int $weckerId): array
{
    $sql = "
        SELECT id, datei, titel, position, sortierung
        FROM wecker_audio
        WHERE wecker_id = :id
        ORDER BY position, sortierung, id
    ";
    return function_cyldb_select($GLOBALS['pdo'], $sql, ['id' => $weckerId]);
}

function function_wecker_setze_audio(int $weckerId, array $items): void
{
    function_cyldb_exec(
        $GLOBALS['pdo'],
        'DELETE FROM wecker_audio WHERE wecker_id = :id',
        ['id' => $weckerId]
    );

    $sql = "
        INSERT INTO wecker_audio (wecker_id, datei, titel, position, sortierung)
        VALUES (:wecker_id, :datei, :titel, :position, :sortierung)
    ";
    $sort = ['gleichzeitig' => 0, 'danach' => 0];
    foreach ($items as $item) {
        $pos = $item['position'] ?? 'danach';
        if (!in_array($pos, ['gleichzeitig', 'danach'], true)) {
            $pos = 'danach';
        }
        $sort[$pos]++;
        function_cyldb_exec($GLOBALS['pdo'], $sql, [
            'wecker_id'  => $weckerId,
            'datei'      => $item['datei'] ?? '',
            'titel'      => !empty($item['titel']) ? $item['titel'] : null,
            'position'   => $pos,
            'sortierung' => $item['sortierung'] ?? $sort[$pos]
        ]);
    }
}

/**
 * Gibt bei "radio:<https://...>" die Stream-URL zurück.
 */
function function_wecker_radio_stream_url(string $datei): ?string
{
    if (strpos($datei, 'radio:') !== 0) {
        return null;
    }
    $streamUrl = trim((string) substr($datei, 6));
    if ($streamUrl === '') {
        return null;
    }
    if (filter_var($streamUrl, FILTER_VALIDATE_URL) === false) {
        return null;
    }
    $parts = parse_url($streamUrl);
    $scheme = strtolower((string) ($parts['scheme'] ?? ''));
    if (!in_array($scheme, ['http', 'https'], true)) {
        return null;
    }
    return $streamUrl;
}

/**
 * Wecker-Audio in abspielbare URLs auflösen. Pro Position (gleichzeitig/danach) eine Liste von URLs.
 * datei kann ein Dateiname (einzelnes Lied), "playlist:ID" oder "radio:https://..." sein.
 * Playlists werden in die zugehörigen Songs aufgelöst.
 *
 * @return array{gleichzeitig: string[], danach: string[]}
 */
function function_wecker_audio_urls(int $weckerId): array
{
    $out = ['gleichzeitig' => [], 'danach' => []];
    $items = function_wecker_hole_audio($weckerId);
    $pdo = $GLOBALS['pdo'] ?? null;
    if (!$pdo) {
        return $out;
    }
    foreach ($items as $item) {
        $datei = isset($item['datei']) ? trim((string) $item['datei']) : '';
        $pos = isset($item['position']) && $item['position'] === 'gleichzeitig' ? 'gleichzeitig' : 'danach';
        if ($datei === '') {
            continue;
        }
        $urls = [];
        if (strpos($datei, 'playlist:') === 0) {
            $playlistId = (int) substr($datei, 9);
            if ($playlistId > 0) {
                try {
                    $stmt = $pdo->prepare(
                        'SELECT m.dateiname FROM musik m INNER JOIN playlist_tracks pt ON pt.musik_id = m.id WHERE pt.playlist_id = :pid ORDER BY pt.id'
                    );
                    $stmt->execute(['pid' => $playlistId]);
                    while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
                        if (!empty($row['dateiname'])) {
                            $urls[] = '/audio_file.php?f=' . rawurlencode($row['dateiname']);
                        }
                    }
                } catch (Throwable $e) {
                    // Tabelle fehlt oder Fehler – keine URLs
                }
            }
        } elseif (strpos($datei, 'radio:') === 0) {
            $streamUrl = function_wecker_radio_stream_url($datei);
            if ($streamUrl !== null) {
                $urls[] = $streamUrl;
            }
        } else {
            $urls[] = '/audio_file.php?f=' . rawurlencode(basename($datei));
        }
        foreach ($urls as $u) {
            $out[$pos][] = $u;
        }
    }
    return $out;
}

function function_wecker_hole_komplett(int $id): ?array
{
    $wecker = function_wecker_hole($id);

    if (!$wecker) {
        return null;
    }

    if ($wecker['wiederholung'] === 'woechentlich') {
        $wecker['wochentage'] = function_wecker_hole_wochentage($id);
    } else {
        $wecker['wochentage'] = [];
    }

    $wecker['audio'] = function_wecker_hole_audio($id);

    return $wecker;
}

function function_wecker_setze_wochentage(int $weckerId, array $tage): void
{
    function_cyldb_exec(
        $GLOBALS['pdo'],
        'DELETE FROM wecker_wochentage WHERE wecker_id = :id',
        ['id' => $weckerId]
    );

    $sql = "
        INSERT INTO wecker_wochentage (wecker_id, wochentag)
        VALUES (:id, :tag)
    ";

    foreach ($tage as $tag) {
        function_cyldb_exec($GLOBALS['pdo'], $sql, [
            'id'  => $weckerId,
            'tag' => $tag
        ]);
    }
}

function function_wecker_anlegen(array $daten): int
{
    $n = function_wecker_anlegen_impl($daten);
    if ($n > 0) {
        function_cache_invalidate('wecker_all');
        function_cache_invalidate('wecker_aktive');
    }
    return $n;
}

function function_wecker_anlegen_impl(array $daten): int
{
    $sql = "
        INSERT INTO wecker (
        weckzeit,
        weckdatum,
        wiederholung,
        sprachtext,
        aktiv,
        lautstaerke_tts,
        lautstaerke_musik,
        musik_ducking_prozent
    ) VALUES (
        :zeit,
        :datum,
        :wiederholung,
        :text,
        :aktiv,
        :lautstaerke_tts,
        :lautstaerke_musik,
        :musik_ducking_prozent
    )
    ";

    return function_cyldb_exec($GLOBALS['pdo'], $sql, [
        'zeit'                   => $daten['weckzeit'],
        'datum'                  => $daten['weckdatum'],
        'wiederholung'           => $daten['wiederholung'],
        'text'                   => $daten['sprachtext'],
        'aktiv'                  => $daten['aktiv'] ?? 1,
        'lautstaerke_tts'        => isset($daten['lautstaerke_tts']) ? max(0, min(100, (int) $daten['lautstaerke_tts'])) : null,
        'lautstaerke_musik'      => isset($daten['lautstaerke_musik']) ? max(0, min(100, (int) $daten['lautstaerke_musik'])) : null,
        'musik_ducking_prozent'  => isset($daten['musik_ducking_prozent']) ? max(0, min(100, (int) $daten['musik_ducking_prozent'])) : null,
    ]);
}

function function_wecker_aktualisieren(int $id, array $daten): int
{
    $sql = "
        UPDATE wecker SET
            weckzeit = :zeit,
            weckdatum = :datum,
            wiederholung = :wiederholung,
            sprachtext = :text,
            aktiv = :aktiv,
            lautstaerke_tts = :lautstaerke_tts,
            lautstaerke_musik = :lautstaerke_musik,
            musik_ducking_prozent = :musik_ducking_prozent
        WHERE id = :id
    ";
    $n = function_cyldb_exec($GLOBALS['pdo'], $sql, [
        'id'                     => $id,
        'zeit'                   => $daten['weckzeit'],
        'datum'                  => $daten['weckdatum'] ?: null,
        'wiederholung'            => $daten['wiederholung'],
        'text'                   => $daten['sprachtext'],
        'aktiv'                  => isset($daten['aktiv']) ? (int)(bool)$daten['aktiv'] : 1,
        'lautstaerke_tts'        => array_key_exists('lautstaerke_tts', $daten) ? max(0, min(100, (int) $daten['lautstaerke_tts'])) : null,
        'lautstaerke_musik'      => array_key_exists('lautstaerke_musik', $daten) ? max(0, min(100, (int) $daten['lautstaerke_musik'])) : null,
        'musik_ducking_prozent'  => array_key_exists('musik_ducking_prozent', $daten) ? max(0, min(100, (int) $daten['musik_ducking_prozent'])) : null,
    ]);

    // Wochentage immer aktualisieren (unabhängig von $n),
    // da MySQL 0 affected rows liefert wenn sich nur die Wochentage ändern
    if (($daten['wiederholung'] ?? '') === 'woechentlich' && isset($daten['wochentage']) && is_array($daten['wochentage'])) {
        function_wecker_setze_wochentage($id, $daten['wochentage']);
    } else {
        function_wecker_setze_wochentage($id, []);
    }

    if ($n > 0) {
        function_cache_invalidate('wecker_all');
        function_cache_invalidate('wecker_aktive');
    }
    return $n;
}

function function_wecker_setze_aktiv(int $id, bool $aktiv): int
{
    $n = function_cyldb_exec(
        $GLOBALS['pdo'],
        'UPDATE wecker SET aktiv = :aktiv WHERE id = :id',
        [
            'aktiv' => $aktiv ? 1 : 0,
            'id'    => $id
        ]
    );
    if ($n > 0) {
        function_cache_invalidate('wecker_all');
        function_cache_invalidate('wecker_aktive');
    }
    return $n;
}

function function_wecker_loeschen(int $id): int
{
    $n = function_cyldb_exec(
        $GLOBALS['pdo'],
        'DELETE FROM wecker WHERE id = :id',
        ['id' => $id]
    );
    if ($n > 0) {
        function_cache_invalidate('wecker_all');
        function_cache_invalidate('wecker_aktive');
    }
    return $n;
}

function function_einstellungen_hole_alle(): array
{
    $c = function_cache_get('einstellungen_all');
    if ($c !== null) {
        return $c;
    }
    $sql = "SELECT schluessel, wert FROM einstellungen";
    $rows = function_cyldb_select($GLOBALS['pdo'], $sql);
    function_cache_set('einstellungen_all', $rows);
    return $rows;
}

function function_einstellung_hole(string $schluessel): ?string
{
    $map = function_cache_get('einstellungen_map');
    if ($map === null) {
        $rows = function_einstellungen_hole_alle();
        $map = [];
        foreach ($rows as $r) {
            $map[$r['schluessel']] = $r['wert'];
        }
        function_cache_set('einstellungen_map', $map);
    }
    $wert = $map[$schluessel] ?? null;
    if ($wert !== null && $schluessel === 'tts_openai_api_key' && strpos($wert, 'enc1:') === 0 && function_exists('function_decrypt_secret')) {
        $wert = function_decrypt_secret($wert);
    }
    return $wert;
}

function function_einstellung_setzen(string $schluessel, string $wert): int
{
    $sql = "
        INSERT INTO einstellungen (schluessel, wert) VALUES (:k, :v)
        ON DUPLICATE KEY UPDATE wert = VALUES(wert)
    ";
    $n = function_cyldb_exec($GLOBALS['pdo'], $sql, ['k' => $schluessel, 'v' => $wert]);
    if ($n >= 0) {
        function_cache_invalidate('einstellungen_all');
        function_cache_invalidate('einstellungen_map');
    }
    return $n;
}

function function_tts_hole_aktive_platzhalter(): array
{
    $sql = "
        SELECT platzhalter, beschreibung
        FROM tts_platzhalter
        WHERE aktiv = 1
        ORDER BY sortierung, platzhalter
    ";

    return function_cyldb_select($GLOBALS['pdo'], $sql);
}

function function_tts_custom_hole_alle(): array
{
    $sql = "
        SELECT id, platzhalter, ersetzungstext, beschreibung, aktiv, sortierung
        FROM tts_custom
        ORDER BY sortierung, platzhalter
    ";
    return function_cyldb_select($GLOBALS['pdo'], $sql);
}

function function_tts_custom_hole_aktive(): array
{
    $sql = "
        SELECT platzhalter, ersetzungstext
        FROM tts_custom
        WHERE aktiv = 1
        ORDER BY sortierung, platzhalter
    ";
    return function_cyldb_select($GLOBALS['pdo'], $sql);
}

function function_tts_custom_hole(int $id): ?array
{
    $rows = function_cyldb_select(
        $GLOBALS['pdo'],
        'SELECT id, platzhalter, ersetzungstext, beschreibung, aktiv, sortierung FROM tts_custom WHERE id = :id',
        ['id' => $id]
    );
    return $rows[0] ?? null;
}

function function_tts_custom_anlegen(array $daten): int
{
    $sql = "
        INSERT INTO tts_custom (platzhalter, ersetzungstext, beschreibung, aktiv, sortierung)
        VALUES (:platzhalter, :ersetzungstext, :beschreibung, :aktiv, :sortierung)
    ";
    function_cyldb_exec($GLOBALS['pdo'], $sql, [
        'platzhalter'    => $daten['platzhalter'],
        'ersetzungstext' => $daten['ersetzungstext'],
        'beschreibung'   => $daten['beschreibung'] ?? null,
        'aktiv'         => isset($daten['aktiv']) ? (int)(bool)$daten['aktiv'] : 1,
        'sortierung'    => (int)($daten['sortierung'] ?? 0)
    ]);
    return (int)$GLOBALS['pdo']->lastInsertId();
}

function function_tts_custom_aktualisieren(int $id, array $daten): int
{
    $sql = "
        UPDATE tts_custom SET
            platzhalter = :platzhalter,
            ersetzungstext = :ersetzungstext,
            beschreibung = :beschreibung,
            aktiv = :aktiv,
            sortierung = :sortierung
        WHERE id = :id
    ";
    return function_cyldb_exec($GLOBALS['pdo'], $sql, [
        'id'             => $id,
        'platzhalter'    => $daten['platzhalter'],
        'ersetzungstext' => $daten['ersetzungstext'],
        'beschreibung'   => $daten['beschreibung'] ?? null,
        'aktiv'          => isset($daten['aktiv']) ? (int)(bool)$daten['aktiv'] : 1,
        'sortierung'     => (int)($daten['sortierung'] ?? 0)
    ]);
}

function function_tts_custom_loeschen(int $id): int
{
    return function_cyldb_exec(
        $GLOBALS['pdo'],
        'DELETE FROM tts_custom WHERE id = :id',
        ['id' => $id]
    );
}

/**
 * Lautstärke-Einstellungen für den Wecker-Service (TTS, Musik, Ducking).
 * @param array|null $wecker Optional: Wecker-Zeile (mit lautstaerke_tts, lautstaerke_musik, musik_ducking_prozent). Sonst Defaults.
 * Rückgabe: ['tts' => 0-100, 'musik' => 0-100, 'musik_ducking_prozent' => 0-100]
 * Während TTS abspielen: Musik auf (musik * musik_ducking_prozent / 100) setzen, danach wieder auf musik.
 */
function function_wecker_lautstaerke_holen(?array $wecker = null): array
{
    $tts = 100;
    $musik = 80;
    $duck = 20;
    if ($wecker !== null) {
        if (isset($wecker['lautstaerke_tts']) && $wecker['lautstaerke_tts'] !== null && $wecker['lautstaerke_tts'] !== '') {
            $tts = max(0, min(100, (int) $wecker['lautstaerke_tts']));
        }
        if (isset($wecker['lautstaerke_musik']) && $wecker['lautstaerke_musik'] !== null && $wecker['lautstaerke_musik'] !== '') {
            $musik = max(0, min(100, (int) $wecker['lautstaerke_musik']));
        }
        if (isset($wecker['musik_ducking_prozent']) && $wecker['musik_ducking_prozent'] !== null && $wecker['musik_ducking_prozent'] !== '') {
            $duck = max(0, min(100, (int) $wecker['musik_ducking_prozent']));
        }
    }
    return [
        'tts'                   => (int) $tts,
        'musik'                 => (int) $musik,
        'musik_ducking_prozent' => (int) $duck,
    ];
}

/**
 * Berechnet die nächste Klingelzeit für einen Wecker ab einem gegebenen Zeitpunkt.
 * @param array $wecker Zeile aus wecker (weckzeit, wiederholung, weckdatum)
 * @param array $wochentage Liste Wochentag 1–7 (Mo–So) für wöchentlich
 * @param DateTimeInterface $now Ab wann gesucht wird
 * @param string $zeitzone z.B. Europe/Berlin
 * @return DateTimeImmutable|null Nächste Klingelzeit oder null
 */
function function_wecker_naechste_klingelzeit(array $wecker, array $wochentage, DateTimeInterface $now, string $zeitzone = 'Europe/Berlin'): ?DateTimeImmutable
{
    $wt = trim((string) ($wecker['weckzeit'] ?? ''));
    if ($wt === '') {
        return null;
    }
    $parts = preg_split('/[:\s]+/', $wt, 3);
    $h = isset($parts[0]) ? (int) $parts[0] : 0;
    $m = isset($parts[1]) ? (int) $parts[1] : 0;
    $h = max(0, min(23, $h));
    $m = max(0, min(59, $m));

    $tz = new DateTimeZone($zeitzone);
    $now = DateTimeImmutable::createFromInterface($now)->setTimezone($tz);

    $wiederholung = (string) ($wecker['wiederholung'] ?? 'taeglich');

    if ($wiederholung === 'einmal') {
        $datum = trim((string) ($wecker['weckdatum'] ?? ''));
        if ($datum === '') {
            return null;
        }
        $ring = DateTimeImmutable::createFromFormat('Y-m-d H:i', $datum . ' ' . str_pad((string) $h, 2, '0', STR_PAD_LEFT) . ':' . str_pad((string) $m, 2, '0', STR_PAD_LEFT), $tz);
        if ($ring === false || $ring <= $now) {
            return null;
        }
        return $ring;
    }

    if ($wiederholung === 'woechentlich') {
        $wochentage = array_map('intval', $wochentage);
        if (empty($wochentage)) {
            return null;
        }
        $currentDow = (int) $now->format('N'); // 1 = Mon, 7 = Sun
        $candidates = [];
        foreach ($wochentage as $dow) {
            $diff = $dow - $currentDow;
            if ($diff < 0) {
                $diff += 7;
            }
            $day = $diff === 0 ? $now : $now->modify('+' . $diff . ' days');
            $cand = $day->setTime($h, $m, 0);
            if ($cand > $now) {
                $candidates[] = $cand;
            } else {
                $candidates[] = $day->modify('+7 days')->setTime($h, $m, 0);
            }
        }
        if (empty($candidates)) {
            return null;
        }
        return min($candidates);
    }

    // taeglich
    $today = $now->setTime($h, $m, 0);
    if ($today > $now) {
        return $today;
    }
    return $now->modify('+1 day')->setTime($h, $m, 0);
}

/**
 * Formatiert eine Weckzeit (z.B. "07:00" oder "07:00:00") als "7 Uhr 00".
 */
function function_weckzeit_formatieren(string $weckzeit): string
{
    $weckzeit = trim($weckzeit);
    if ($weckzeit === '') {
        return '';
    }
    $parts = preg_split('/[:\s]+/', $weckzeit, 3);
    $h = isset($parts[0]) ? (int) $parts[0] : 0;
    $m = isset($parts[1]) ? (int) $parts[1] : 0;
    $h = max(0, min(23, $h));
    $m = max(0, min(59, $m));
    return $h . ' Uhr ' . str_pad((string) $m, 2, '0', STR_PAD_LEFT);
}

/**
 * Ersetzt TTS-Platzhalter im Text (für Wecker-Service beim Abspielen).
 * {{name}}, {{uhrzeit}}, {{weckzeit}}, {{datum}}, {{temperatur}}, {{wetter}}, {{wetter_tag}}, plus tts_custom.
 * @param string|null $weckzeit Optional: Weckzeit im Format "HH:MM" oder "HH:MM:SS" für {{weckzeit}}
 */
function function_tts_text_ersetzen(string $text, ?DateTimeInterface $zeit = null, ?string $weckzeit = null): string
{
    if ($zeit === null) {
        $zeit = new DateTimeImmutable('now', new DateTimeZone(function_einstellung_hole('zeitzone') ?: 'Europe/Berlin'));
    }

    $temperatur = '';
    $wetter = '';
    $wetterTag = '';

    $ort = trim((string) (function_einstellung_hole('wetter_ort') ?: ''));
    if ($ort !== '') {
        if (!function_exists('function_wetter_fetch')) {
            require_once __DIR__ . '/functions_wetter.php';
        }
        $wetterData = function_wetter_fetch($ort);
        if (!empty($wetterData['ok']) && !empty($wetterData['current'])) {
            $cur = $wetterData['current'];
            $temperatur = (string) ($cur['temp'] ?? '');
            $wetter = (string) ($cur['beschreibung'] ?? '');
            if (!empty($wetterData['daily'][0])) {
                $tag = $wetterData['daily'][0];
                $min = $tag['temp_min'] ?? null;
                $max = $tag['temp_max'] ?? null;
                $beschr = $tag['beschreibung'] ?? '';
                if ($min !== null && $max !== null) {
                    $wetterTag = trim($beschr . ', ' . $min . ' bis ' . $max . ' Grad');
                } else {
                    $wetterTag = $beschr;
                }
            }
        }
    }

    $ersetzungen = [
        '{{name}}'         => function_einstellung_hole('tts_name') ?: '',
        '{{uhrzeit}}'      => (int)$zeit->format('G') . ' Uhr ' . $zeit->format('i'),
        '{{weckzeit}}'     => $weckzeit !== null && $weckzeit !== '' ? function_weckzeit_formatieren($weckzeit) : '',
        '{{datum}}'        => $zeit->format('d.m.Y'),
        '{{temperatur}}'   => $temperatur,
        '{{wetter}}'       => $wetter,
        '{{wetter_tag}}'   => $wetterTag,
    ];

    foreach (function_tts_custom_hole_aktive() as $row) {
        $ersetzungen[$row['platzhalter']] = $row['ersetzungstext'];
    }

    $out = $text;
    foreach ($ersetzungen as $platzhalter => $wert) {
        $out = str_replace($platzhalter, $wert, $out);
    }

    return $out;
}

