<?php

declare(strict_types=1);

namespace App\Infrastructure\Persistence\Urlaubsplaner;

use App\Domain\Urlaubsplaner\UrlaubsplanerRepository;
use PDO;
use Psr\Log\LoggerInterface;

class DatabaseUrlaubsplanerRepository implements UrlaubsplanerRepository
{
    protected LoggerInterface $logger;
    protected PDO $db;

    public function __construct(LoggerInterface $logger, PDO $database)
    {
        $this->logger = $logger;
        $this->db     = $database;
    }

    public function getAbteilungen(int $cid): array
    {
        $stmt = $this->db->prepare(
            "SELECT id, bezeichnung
             FROM usergruppe
             WHERE customerno = ?
             ORDER BY bezeichnung ASC"
        );
        $stmt->execute([$cid]);
        return $stmt->fetchAll();
    }

    public function getMonatsliste(int $cid, int $year, int $month): array
    {
        // Feiertage des Monats laden
        $stmt = $this->db->prepare(
            "SELECT tag FROM feiertage
             WHERE monat = ? AND jahr = ? AND customerno = ?
             ORDER BY tag ASC"
        );
        $stmt->execute([$month, $year, $cid]);
        $feiertage = array_column($stmt->fetchAll(), 'tag');

        // Anzahl Tage im Monat
        $anzahlTage = (int) date('t', mktime(0, 0, 0, $month, 1, $year));

        // Mitarbeiterliste mit Jahresanspruch
        $stmt = $this->db->prepare(
            "SELECT
                account.id,
                account.mitarbeiternr,
                account.username,
                account.name,
                account.vorname,
                usergruppe.bezeichnung AS team,
                account.jahresurlaub AS jahresanspruch,
                urlaubjahr.sonderurlaub,
                urlaubjahr.resturlaub,
                (urlaubjahr.resturlaub + account.jahresurlaub) AS gesamt
             FROM urlaubjahr
             LEFT JOIN account ON account.id = urlaubjahr.uid
             LEFT JOIN usergruppe ON account.gruppe = usergruppe.id
             WHERE urlaubjahr.jahr = ?
               AND account.isVerified = '1'
               AND account.isDeleted  = '0'
               AND account.customerId = ?
             ORDER BY account.id ASC"
        );
        $stmt->execute([$year, $cid]);
        $mitarbeiter = $stmt->fetchAll();

        $data = [];

        foreach ($mitarbeiter as $ma) {
            $uid = (int) $ma['id'];

            // Resturlaub aus Vormonaten berechnen
            $vormonatResturlaub = (float) $ma['gesamt'];
            $vormonatKrank      = 0.0;
            $vormonatSonder     = 0.0;

            if ($month > 1) {
                $stmt2 = $this->db->prepare(
                    "SELECT typ, COUNT(typ) AS anzahl
                     FROM urlaubtage
                     WHERE uid = ?
                       AND YEAR(datum) = ?
                       AND MONTH(datum) < ?
                     GROUP BY typ"
                );
                $stmt2->execute([$uid, $year, $month]);
                $vorTypen = $stmt2->fetchAll();

                $vorUrlaub  = 0.0;
                $vorHalbVM  = 0.0;
                $vorHalbNM  = 0.0;

                foreach ($vorTypen as $vt) {
                    switch ($vt['typ']) {
                        case 'U': $vorUrlaub  += (float) $vt['anzahl']; break;
                        case 'K': $vormonatKrank  += (float) $vt['anzahl']; break;
                        case 'S': $vormonatSonder += (float) $vt['anzahl']; break;
                        case 'H': $vorHalbVM  += (float) $vt['anzahl']; break;
                        case 'N': $vorHalbNM  += (float) $vt['anzahl']; break;
                    }
                }
                $vormonatResturlaub -= ($vorUrlaub + ($vorHalbVM * 0.5) + ($vorHalbNM * 0.5));
            }

            // Urlaubstage des aktuellen Monats laden
            $stmt3 = $this->db->prepare(
                "SELECT DAY(datum) AS tag, typ
                 FROM urlaubtage
                 WHERE uid = ?
                   AND YEAR(datum) = ?
                   AND MONTH(datum) = ?
                 ORDER BY tag ASC"
            );
            $stmt3->execute([$uid, $year, $month]);
            $urlaubTage = [];
            foreach ($stmt3->fetchAll() as $ut) {
                $urlaubTage[(int) $ut['tag']] = $ut['typ'];
            }

            // Tage-Array aufbauen
            $tage         = [];
            $summeUrlaub  = 0.0;
            $summeKrank   = 0.0;
            $summeSonder  = 0.0;
            $summeHalb    = 0.0;

            for ($n = 0; $n < $anzahlTage; $n++) {
                $ts  = mktime(0, 0, 0, $month, $n + 1, $year);
                $wt  = strtolower(date('D', $ts));
                $tag = ['wt' => $wt, 'urlaub' => '', 'typ' => ''];

                if ($wt === 'sat' || $wt === 'sun') {
                    $tag['typ'] = 'WE/F';
                } elseif (in_array($n + 1, $feiertage, true)) {
                    $tag['typ'] = 'WE/F';
                } else {
                    $tag['typ'] = 'A';
                }

                $typ = $urlaubTage[$n + 1] ?? null;
                if ($typ !== null) {
                    $tag['urlaub'] = $typ;
                    if ($typ === 'H' || $typ === 'N') {
                        $summeHalb += 0.5;
                    } elseif ($typ === 'U') {
                        $summeUrlaub++;
                    } elseif ($typ === 'K') {
                        $summeKrank++;
                    } elseif ($typ === 'S') {
                        $summeSonder += ($wt === 'sat') ? 0.5 : 1;
                    }
                }

                $tage[] = $tag;
            }

            $data[] = [
                'MITARBEITERNR' => $ma['mitarbeiternr'],
                'VORNAME'       => $ma['vorname'],
                'NAME'          => $ma['name'],
                'TEAM'          => $ma['team'] ?? '',
                'tage'          => $tage,
                'summeurlaub'   => $summeUrlaub,
                'summekrank'    => $summeKrank,
                'summehalb'     => $summeHalb,
                'summesonder'   => $summeSonder,
                'restvormonat'  => $vormonatResturlaub,
                'resturlaub'    => $vormonatResturlaub - $summeUrlaub - $summeHalb,
                'summekrankjahr'  => $vormonatKrank  + $summeKrank,
                'summesonderjahr' => $vormonatSonder + $summeSonder,
            ];
        }

        return $data;
    }

    public function createAbteilung(int $cid, string $bezeichnung): int
    {
        $stmt = $this->db->prepare(
            "INSERT INTO usergruppe SET bezeichnung = ?, customerno = ?"
        );
        $stmt->execute([$bezeichnung, $cid]);
        return (int) $this->db->lastInsertId();
    }

    public function updateAbteilung(int $id, string $bezeichnung): void
    {
        $stmt = $this->db->prepare(
            "UPDATE usergruppe SET bezeichnung = ? WHERE id = ?"
        );
        $stmt->execute([$bezeichnung, $id]);
    }

    public function deleteAbteilung(int $id): void
    {
        $check = $this->db->prepare(
            "SELECT id FROM account WHERE gruppe = ? AND isDeleted = '0' LIMIT 1"
        );
        $check->execute([$id]);
        if ($check->fetch()) {
            throw new \RuntimeException('Abteilung kann nicht gelöscht werden, es sind noch Mitarbeiter zugeordnet.');
        }
        $stmt = $this->db->prepare("DELETE FROM usergruppe WHERE id = ?");
        $stmt->execute([$id]);
    }

    public function saveAntrag(int $cid, int $uid, string $von, string $bis, string $typ, string $grund): void
    {
        // Doppeleintrag-Prüfung
        $stmt = $this->db->prepare(
            "SELECT id FROM urlaubtage
             WHERE uid = ? AND datum BETWEEN ? AND ?"
        );
        $stmt->execute([$uid, $von, $bis]);
        if ($stmt->fetch()) {
            throw new \RuntimeException("Für den Zeitraum von $von bis $bis sind schon Einträge vorhanden.");
        }

        // Krankmeldung: sofort genehmigt; alle anderen: wartend
        $antragStatus = ($typ === 'K') ? 'genehmigt' : 'wartend';

        $stmt = $this->db->prepare(
            "INSERT INTO urlaubsantrag SET
                uid         = ?,
                vondatum    = ?,
                bisdatum    = ?,
                typ         = ?,
                grund       = ?,
                datumantrag = CURDATE(),
                status      = ?"
        );
        $stmt->execute([$uid, $von, $bis, $typ, $grund, $antragStatus]);
        $antragId = (int) $this->db->lastInsertId();

        $anzahltage = 0.0;

        if ($typ === 'H' || $typ === 'N') {
            // Halbtag: nur ein Eintrag für $von
            $dayTyp = ($typ === 'H') ? 'A' : 'C';
            $stmt = $this->db->prepare(
                "INSERT INTO urlaubtage SET uid = ?, uaid = ?, datum = ?, typ = ?"
            );
            $stmt->execute([$uid, $antragId, $von, $dayTyp]);
            $anzahltage = 0.5;
        } else {
            // Ganze Tage: jeden Arbeitstag im Zeitraum eintragen
            // Krankmeldung (K) direkt als 'K' eintragen; andere als 'B' (beantragt)
            $dayTyp   = ($typ === 'K') ? 'K' : 'B';
            $faktoren = $this->getUrlaubsFaktoren($uid);
            $current  = new \DateTime($von);
            $end      = new \DateTime($bis);

            while ($current <= $end) {
                $dateStr = $current->format('Y-m-d');
                $dow     = (int) $current->format('N'); // 1=Mo … 7=So
                $faktor  = $faktoren[$dow] ?? 0.0;

                // Faktor 0 = an diesem Wochentag arbeitet der Mitarbeiter nicht
                if ($faktor > 0 && !$this->isFeiertag($dateStr, $cid)) {
                    $stmt = $this->db->prepare(
                        "INSERT INTO urlaubtage SET uid = ?, uaid = ?, datum = ?, typ = ?"
                    );
                    $stmt->execute([$uid, $antragId, $dateStr, $dayTyp]);
                    $anzahltage += $faktor;
                }
                $current->modify('+1 day');
            }
        }

        // Anzahl aktualisieren
        $stmt = $this->db->prepare(
            "UPDATE urlaubsantrag SET anzahltage = ? WHERE id = ? LIMIT 1"
        );
        $stmt->execute([$anzahltage, $antragId]);
    }

    public function getFeiertage(int $cid, int $year): array
    {
        $stmt = $this->db->prepare(
            "SELECT id, datum, name, faktor
             FROM feiertage
             WHERE jahr = ? AND customerno = ?
             ORDER BY monat ASC, tag ASC"
        );
        $stmt->execute([$year, $cid]);
        return $stmt->fetchAll();
    }

    public function createFeiertag(int $cid, string $datum, string $name, float $faktor): int
    {
        $stmt = $this->db->prepare(
            "INSERT INTO feiertage SET
                customerno = ?,
                datum      = ?,
                name       = ?,
                tag        = DAYOFMONTH(?),
                monat      = MONTH(?),
                jahr       = YEAR(?),
                faktor     = ?"
        );
        $stmt->execute([$cid, $datum, $name, $datum, $datum, $datum, $faktor]);
        return (int) $this->db->lastInsertId();
    }

    public function updateFeiertag(int $id, string $datum, string $name, float $faktor): void
    {
        $stmt = $this->db->prepare(
            "UPDATE feiertage SET
                datum  = ?,
                name   = ?,
                tag    = DAYOFMONTH(?),
                monat  = MONTH(?),
                jahr   = YEAR(?),
                faktor = ?
             WHERE id = ?"
        );
        $stmt->execute([$datum, $name, $datum, $datum, $datum, $faktor, $id]);
    }

    public function deleteFeiertag(int $id): void
    {
        $stmt = $this->db->prepare("DELETE FROM feiertage WHERE id = ?");
        $stmt->execute([$id]);
    }

    public function einlesenFeiertage(int $cid, int $year): int
    {
        // Bundesland des Kunden aus der Adresse holen
        $stmt = $this->db->prepare(
            "SELECT state FROM address
             WHERE contactId = (SELECT id FROM customer WHERE id = ? LIMIT 1)
               AND type = 'COMPANY'
             ORDER BY id ASC LIMIT 1"
        );
        $stmt->execute([$cid]);
        $row   = $stmt->fetch();
        $state = ($row && $row['state'] !== '') ? $row['state'] : 'NW';

        $url    = 'https://feiertage-api.de/api/?jahr=' . $year . '&nur_land=' . urlencode($state);
        $json   = @file_get_contents($url);
        if ($json === false) {
            throw new \RuntimeException('Feiertage-API nicht erreichbar.');
        }
        $feiertage = json_decode($json, true);
        if (!is_array($feiertage)) {
            throw new \RuntimeException('Ungültige Antwort von Feiertage-API.');
        }

        $cnt = 0;
        foreach ($feiertage as $name => $info) {
            $datum = $info['datum'] ?? null;
            if (!$datum) {
                continue;
            }
            $check = $this->db->prepare(
                "SELECT id FROM feiertage WHERE datum = ? AND customerno = ? LIMIT 1"
            );
            $check->execute([$datum, $cid]);
            if (!$check->fetch()) {
                $ins = $this->db->prepare(
                    "INSERT INTO feiertage SET
                        customerno = ?,
                        datum      = ?,
                        name       = ?,
                        tag        = DAYOFMONTH(?),
                        monat      = MONTH(?),
                        jahr       = YEAR(?),
                        faktor     = 1"
                );
                $ins->execute([$cid, $datum, $name, $datum, $datum, $datum]);
                $cnt++;
            }
        }
        return $cnt;
    }

    public function getAntraegeListe(int $cid, int $year): array
    {
        // year filter on vondatum (when vacation starts) — semantically: show vacations FOR that year
        // urlaubjahr joined in ON clause (proper LEFT JOIN) to avoid duplicates per user
        $base = "
            SELECT
                ua.id             AS ID,
                ua.uid            AS UID,
                a.mitarbeiternr   AS UIDTEXT,
                a.vorname         AS VORNAME,
                a.name            AS NAME,
                ua.typ            AS TYP,
                DATE_FORMAT(ua.vondatum,    '%d.%m.%Y') AS VONDATUM,
                DATE_FORMAT(ua.bisdatum,    '%d.%m.%Y') AS BISDATUM,
                ua.anzahltage     AS ANZAHLTAGE,
                ua.grund          AS GRUND,
                DATE_FORMAT(ua.datumantrag, '%d.%m.%Y') AS DATUMANTRAG,
                ua.status         AS STATUS,
                ua.begruendung    AS BEGRUENDUNG,
                COALESCE(uj.resturlaub + a.jahresurlaub, a.jahresurlaub) AS rest
            FROM urlaubsantrag ua
            LEFT JOIN account    a  ON a.id = ua.uid
            LEFT JOIN urlaubjahr uj ON uj.uid = ua.uid AND uj.jahr = ?
            WHERE YEAR(ua.vondatum) = ?
              AND a.customerId = ?
              AND a.isDeleted = '0'
        ";

        $stmt = $this->db->prepare($base . "AND ua.typ != 'K' AND ua.status = 'wartend' ORDER BY ua.vondatum ASC");
        $stmt->execute([$year, $year, $cid]);
        $offen = $stmt->fetchAll(\PDO::FETCH_ASSOC);

        $stmt = $this->db->prepare($base . "AND ua.typ != 'K' AND ua.status != 'wartend' ORDER BY ua.datumantrag DESC");
        $stmt->execute([$year, $year, $cid]);
        $sonst = $stmt->fetchAll(\PDO::FETCH_ASSOC);

        $stmt = $this->db->prepare($base . "AND ua.typ = 'K' ORDER BY ua.vondatum DESC");
        $stmt->execute([$year, $year, $cid]);
        $krank = $stmt->fetchAll(\PDO::FETCH_ASSOC);

        return ['offen' => $offen, 'sonst' => $sonst, 'krank' => $krank];
    }

    public function genehmigenAntrag(int $id, string $typ): void
    {
        $stmt = $this->db->prepare("UPDATE urlaubsantrag SET status = 'genehmigt' WHERE id = ? LIMIT 1");
        $stmt->execute([$id]);

        // urlaubtage: B→typ, A→H (Halbtag VM), C→N (Halbtag NM)
        $typMap = ['B' => $typ, 'A' => 'H', 'C' => 'N'];
        foreach ($typMap as $from => $to) {
            $stmt = $this->db->prepare(
                "UPDATE urlaubtage SET typ = ? WHERE uaid = ? AND typ = ?"
            );
            $stmt->execute([$to, $id, $from]);
        }
    }

    public function ablehnenAntrag(int $id, string $begruendung): void
    {
        $stmt = $this->db->prepare(
            "UPDATE urlaubsantrag SET status = 'abgelehnt', begruendung = ? WHERE id = ? LIMIT 1"
        );
        $stmt->execute([$begruendung, $id]);

        $stmt = $this->db->prepare("DELETE FROM urlaubtage WHERE uaid = ?");
        $stmt->execute([$id]);
    }

    public function deleteAntrag(int $id): void
    {
        $stmt = $this->db->prepare("DELETE FROM urlaubtage WHERE uaid = ?");
        $stmt->execute([$id]);

        $stmt = $this->db->prepare("DELETE FROM urlaubsantrag WHERE id = ? LIMIT 1");
        $stmt->execute([$id]);
    }

    public function updateKrankmeldung(int $cid, int $id, string $von, string $bis): void
    {
        $stmt = $this->db->prepare("SELECT uid FROM urlaubsantrag WHERE id = ? LIMIT 1");
        $stmt->execute([$id]);
        $row = $stmt->fetch();
        if (!$row) {
            throw new \RuntimeException('Antrag nicht gefunden.');
        }
        $uid = (int) $row['uid'];

        // Alte urlaubtage löschen
        $stmt = $this->db->prepare("DELETE FROM urlaubtage WHERE uaid = ?");
        $stmt->execute([$id]);

        // Neue urlaubtage eintragen
        $anzahltage = 0.0;
        $faktoren   = $this->getUrlaubsFaktoren($uid);
        $current    = new \DateTime($von);
        $end        = new \DateTime($bis);

        while ($current <= $end) {
            $dateStr = $current->format('Y-m-d');
            $dow     = (int) $current->format('N');
            $faktor  = $faktoren[$dow] ?? 0.0;

            if ($faktor > 0 && !$this->isFeiertag($dateStr, $cid)) {
                $stmt = $this->db->prepare(
                    "INSERT INTO urlaubtage SET uid = ?, uaid = ?, datum = ?, typ = 'K'"
                );
                $stmt->execute([$uid, $id, $dateStr]);
                $anzahltage += $faktor;
            }
            $current->modify('+1 day');
        }

        $stmt = $this->db->prepare(
            "UPDATE urlaubsantrag SET vondatum = ?, bisdatum = ?, anzahltage = ? WHERE id = ? LIMIT 1"
        );
        $stmt->execute([$von, $bis, $anzahltage, $id]);
    }

    public function getEigeneAntraege(int $uid, int $year): array
    {
        $base = "
            SELECT
                ua.id             AS ID,
                ua.uid            AS UID,
                a.mitarbeiternr   AS UIDTEXT,
                a.vorname         AS VORNAME,
                a.name            AS NAME,
                ua.typ            AS TYP,
                DATE_FORMAT(ua.vondatum,    '%d.%m.%Y') AS VONDATUM,
                DATE_FORMAT(ua.bisdatum,    '%d.%m.%Y') AS BISDATUM,
                ua.anzahltage     AS ANZAHLTAGE,
                ua.grund          AS GRUND,
                DATE_FORMAT(ua.datumantrag, '%d.%m.%Y') AS DATUMANTRAG,
                ua.status         AS STATUS,
                ua.begruendung    AS BEGRUENDUNG,
                COALESCE(uj.resturlaub + a.jahresurlaub, a.jahresurlaub) AS rest
            FROM urlaubsantrag ua
            LEFT JOIN account    a  ON a.id = ua.uid
            LEFT JOIN urlaubjahr uj ON uj.uid = ua.uid AND uj.jahr = ?
            WHERE YEAR(ua.vondatum) = ?
              AND ua.uid = ?
        ";

        $stmt = $this->db->prepare($base . "AND ua.typ != 'K' AND ua.status = 'wartend' ORDER BY ua.vondatum ASC");
        $stmt->execute([$year, $year, $uid]);
        $offen = $stmt->fetchAll(\PDO::FETCH_ASSOC);

        $stmt = $this->db->prepare($base . "AND ua.typ != 'K' AND ua.status != 'wartend' ORDER BY ua.datumantrag DESC");
        $stmt->execute([$year, $year, $uid]);
        $sonst = $stmt->fetchAll(\PDO::FETCH_ASSOC);

        $stmt = $this->db->prepare($base . "AND ua.typ = 'K' ORDER BY ua.vondatum DESC");
        $stmt->execute([$year, $year, $uid]);
        $krank = $stmt->fetchAll(\PDO::FETCH_ASSOC);

        return ['offen' => $offen, 'sonst' => $sonst, 'krank' => $krank];
    }

    public function getStatistiken(int $cid, int $year): array
    {
        // Jahresanspruch + Resturlaub aus Vorjahr pro Mitarbeiter
        $stmt = $this->db->prepare(
            "SELECT
                a.id              AS uid,
                a.mitarbeiternr,
                a.vorname,
                a.name,
                COALESCE(ug.bezeichnung, '') AS team,
                COALESCE(a.jahresurlaub, 0)  AS jahresanspruch,
                COALESCE(uj.resturlaub,  0)  AS resturlaub_vorjahr
             FROM account a
             LEFT JOIN urlaubjahr uj ON uj.uid = a.id AND uj.jahr = ?
             LEFT JOIN usergruppe ug ON ug.id  = a.gruppe
             WHERE a.customerId = ?
               AND a.isVerified = '1'
               AND a.isDeleted  = '0'
             ORDER BY a.name ASC, a.vorname ASC"
        );
        $stmt->execute([$year, $cid]);
        $mitarbeiter = $stmt->fetchAll(\PDO::FETCH_ASSOC);

        if (empty($mitarbeiter)) {
            return [];
        }

        // Alle Urlaubstage des Jahres für den Kunden in einem Query laden
        $uids = array_column($mitarbeiter, 'uid');
        $placeholders = implode(',', array_fill(0, count($uids), '?'));

        $stmt = $this->db->prepare(
            "SELECT ut.uid, ut.typ, DAYOFWEEK(ut.datum) AS dow
             FROM urlaubtage ut
             WHERE ut.uid IN ($placeholders)
               AND YEAR(ut.datum) = ?"
        );
        $stmt->execute([...$uids, $year]);
        $alleTage = $stmt->fetchAll(\PDO::FETCH_ASSOC);

        // Gruppieren nach UID
        $tagenMap = [];
        foreach ($alleTage as $t) {
            $tagenMap[(int) $t['uid']][] = $t;
        }

        // Wochentags-Faktoren aller Mitarbeiter vorab laden
        $faktorenMap = $this->getUrlaubsFaktorenMap($uids);

        $result = [];
        foreach ($mitarbeiter as $ma) {
            $uid      = (int) $ma['uid'];
            $tage     = $tagenMap[$uid] ?? [];
            $faktoren = $faktorenMap[$uid] ?? $this->faktorenStandard();

            $verbraucht      = 0.0;
            $verbraucht_halb = 0.0;
            $krank           = 0.0;
            $sonder          = 0.0;

            foreach ($tage as $t) {
                // DAYOFWEEK(): 1 = So … 7 = Sa  →  ISO: 1 = Mo … 7 = So
                $dow    = ((int) $t['dow'] + 5) % 7 + 1;
                $faktor = $faktoren[$dow] ?? 0.0;

                switch ($t['typ']) {
                    case 'U': $verbraucht      += $faktor; break;
                    // Halbe Tage zählen pauschal 0.5 – wie beim Anlegen des Antrags
                    case 'H':
                    case 'N': $verbraucht_halb += 0.5; break;
                    case 'K': $krank           += $faktor; break;
                    case 'S': $sonder          += $faktor; break;
                }
            }

            $jahresanspruch    = (float) $ma['jahresanspruch'];
            $resturlaub_vorjahr = (float) $ma['resturlaub_vorjahr'];
            $gesamt            = $jahresanspruch + $resturlaub_vorjahr;
            $resturlaub        = $gesamt - $verbraucht - $verbraucht_halb;

            $result[] = [
                'uid'               => $uid,
                'mitarbeiternr'     => $ma['mitarbeiternr'],
                'vorname'           => $ma['vorname'],
                'name'              => $ma['name'],
                'team'              => $ma['team'],
                'jahresanspruch'    => $jahresanspruch,
                'resturlaub_vorjahr' => $resturlaub_vorjahr,
                'gesamt'            => $gesamt,
                'verbraucht'        => $verbraucht,
                'verbraucht_halb'   => $verbraucht_halb,
                'krank_jahr'        => $krank,
                'sonder_jahr'       => $sonder,
                'resturlaub'        => $resturlaub,
            ];
        }

        return $result;
    }

    /**
     * Standard-Faktoren ohne hinterlegtes Arbeitszeitmodell:
     * 5-Tage-Woche Mo–Fr, keine Samstagsarbeit.
     *
     * @return array<int,float>
     */
    private function faktorenStandard(): array
    {
        return [1 => 1.0, 2 => 1.0, 3 => 1.0, 4 => 1.0, 5 => 1.0, 6 => 0.0, 7 => 0.0];
    }

    /**
     * Urlaubs-Faktoren je Wochentag aus dem Arbeitszeitmodell des Mitarbeiters.
     * Schlüssel = ISO-Wochentag (1 = Mo … 7 = So), Wert = verbrauchte Urlaubstage.
     * Faktor 0 bedeutet: an diesem Wochentag arbeitet der Mitarbeiter nicht,
     * es wird weder ein urlaubtage-Eintrag erzeugt noch Urlaub abgezogen.
     *
     * @return array<int,float>
     */
    private function getUrlaubsFaktoren(int $uid): array
    {
        return $this->getUrlaubsFaktorenMap([$uid])[$uid] ?? $this->faktorenStandard();
    }

    /**
     * Urlaubs-Faktoren für mehrere Mitarbeiter in einem Query (vermeidet N+1).
     * Mitarbeiter ohne Arbeitszeitmodell fehlen im Ergebnis – Aufrufer nutzen
     * dann faktorenStandard().
     *
     * @param  int[] $uids
     * @return array<int,array<int,float>> uid => [ISO-Wochentag => Faktor]
     */
    private function getUrlaubsFaktorenMap(array $uids): array
    {
        if (empty($uids)) {
            return [];
        }

        $placeholders = implode(',', array_fill(0, count($uids), '?'));
        $stmt = $this->db->prepare(
            "SELECT a.id AS uid,
                    m.aufteilung,
                    m.samstagsarbeit,
                    m.urlaubstagewoche,
                    m.faktor_urlaub_montag,
                    m.faktor_urlaub_dienstag,
                    m.faktor_urlaub_mittwoch,
                    m.faktor_urlaub_donnerstag,
                    m.faktor_urlaub_freitag,
                    m.faktor_urlaub_samstag
               FROM account a
               JOIN arbeitszeitmodell m ON m.id = a.arbeitszeitmodell
              WHERE a.id IN ($placeholders)"
        );
        $stmt->execute(array_map('intval', $uids));

        $map = [];
        foreach ($stmt->fetchAll(\PDO::FETCH_ASSOC) as $modell) {
            $map[(int) $modell['uid']] = $this->faktorenAusModell($modell);
        }

        return $map;
    }

    /**
     * Rechnet eine Zeile aus arbeitszeitmodell in Wochentags-Faktoren um.
     *
     * @param  array<string,mixed> $modell
     * @return array<int,float>
     */
    private function faktorenAusModell(array $modell): array
    {
        $faktoren       = $this->faktorenStandard();
        $samstagsarbeit = ($modell['samstagsarbeit'] === 'Ja');

        if ($modell['aufteilung'] === 'Tag') {
            // Aufteilung "Tag": Faktor je Wochentag direkt aus dem Modell
            $faktoren[1] = (float) $modell['faktor_urlaub_montag'];
            $faktoren[2] = (float) $modell['faktor_urlaub_dienstag'];
            $faktoren[3] = (float) $modell['faktor_urlaub_mittwoch'];
            $faktoren[4] = (float) $modell['faktor_urlaub_donnerstag'];
            $faktoren[5] = (float) $modell['faktor_urlaub_freitag'];
            $faktoren[6] = $samstagsarbeit ? (float) $modell['faktor_urlaub_samstag'] : 0.0;

            return $faktoren;
        }

        // Aufteilung "Woche": urlaubstagewoche gleichmäßig auf die Arbeitstage
        // verteilen (5 bzw. 6 bei Samstagsarbeit). Eine volle Urlaubswoche
        // verbraucht damit genau urlaubstagewoche Tage.
        // Die faktor_urlaub_*-Spalten werden hier bewusst nicht verwendet: der
        // Modell-Editor blendet sie bei Aufteilung "Woche" aus, sie sind also
        // nicht gepflegt.
        $arbeitstage = $samstagsarbeit ? 6.0 : 5.0;
        $proWoche    = (float) $modell['urlaubstagewoche'];

        // urlaubstagewoche = 0 heißt "nicht gepflegt" → Vollzeit annehmen,
        // sonst würde Urlaub gar nicht mehr abgezogen.
        $faktor = ($proWoche > 0) ? ($proWoche / $arbeitstage) : 1.0;

        $faktoren[1] = $faktor;
        $faktoren[2] = $faktor;
        $faktoren[3] = $faktor;
        $faktoren[4] = $faktor;
        $faktoren[5] = $faktor;
        $faktoren[6] = $samstagsarbeit ? $faktor : 0.0;

        return $faktoren;
    }

    private function isFeiertag(string $datum, int $cid): bool
    {
        $stmt = $this->db->prepare(
            "SELECT id FROM feiertage WHERE datum = ? AND customerno = ? LIMIT 1"
        );
        $stmt->execute([$datum, $cid]);
        return (bool) $stmt->fetch();
    }
}
