<?php

use PHPMailer\PHPMailer\Exception;
use PHPMailer\PHPMailer\PHPMailer;

require_once __DIR__ . '/../../inc/PHPMailer/src/Exception.php';
require_once __DIR__ . '/../../inc/PHPMailer/src/PHPMailer.php';
require_once __DIR__ . '/../../inc/PHPMailer/src/SMTP.php';

if (!function_exists('timeErrorNotificationsEnsureTables')) {
    function timeErrorNotificationsEnsureTables(PDO $pdo): void
    {
        $legacyColumnStmt = $pdo->prepare(
            "SELECT COUNT(*)
             FROM information_schema.COLUMNS
             WHERE TABLE_SCHEMA = DATABASE()
               AND TABLE_NAME = 'time_error_notifications'
               AND COLUMN_NAME = 'error_date'"
        );
        $legacyColumnStmt->execute();
        if ((int)$legacyColumnStmt->fetchColumn() > 0) {
            $pdo->exec("DROP TABLE IF EXISTS time_error_notifications");
        }

        $pdo->exec(
            "CREATE TABLE IF NOT EXISTS time_error_notification_state (
                employee_id INT NOT NULL,
                cycle_started_on DATE NOT NULL,
                first_error_date DATE NOT NULL,
                last_notification_stage VARCHAR(50) DEFAULT NULL,
                last_notification_sent_at DATETIME DEFAULT NULL,
                employee_day_1_sent_at DATETIME DEFAULT NULL,
                employee_day_3_sent_at DATETIME DEFAULT NULL,
                admin_day_7_sent_at DATETIME DEFAULT NULL,
                updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
                PRIMARY KEY (employee_id)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4"
        );

        $pdo->exec(
            "CREATE TABLE IF NOT EXISTS time_error_notifications (
                id INT NOT NULL AUTO_INCREMENT,
                employee_id INT NOT NULL,
                cycle_started_on DATE NOT NULL,
                notification_stage VARCHAR(50) NOT NULL,
                recipient_email VARCHAR(255) NOT NULL,
                sent_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
                created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
                PRIMARY KEY (id),
                UNIQUE KEY uniq_time_error_notification (employee_id, cycle_started_on, notification_stage, recipient_email)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4"
        );
    }
}

if (!function_exists('timeErrorNotificationsSendMail')) {
    function timeErrorNotificationsSendMail(PDO $pdo, string $recipient, string $subject, string $body): bool
    {
        $stmt = $pdo->prepare("SELECT * FROM config LIMIT 1");
        $stmt->execute();
        $config = $stmt->fetch(PDO::FETCH_ASSOC);

        if (!$config) {
            return false;
        }

        $mail = new PHPMailer(true);

        try {
            $mail->SMTPDebug = 0;
            $mail->isSMTP();
            $mail->Host = (string)$config['mailserver'];
            $mail->SMTPAuth = true;
            $mail->Username = (string)$config['mailUsername'];
            $mail->Password = (string)$config['mailPassword'];

            if (!empty($config['mailSMTPSecure'])) {
                $mail->SMTPSecure = (string)$config['mailSMTPSecure'];
            }

            $mail->Port = (int)$config['mailPort'];
            $mail->CharSet = 'UTF-8';
            $mail->setFrom((string)$config['mailFrom'], (string)$config['mailFromName']);
            $mail->addAddress($recipient);
            $mail->isHTML(true);
            $mail->Subject = $subject;
            $mail->Body = $body;
            $mail->AltBody = trim(html_entity_decode(strip_tags(str_replace(["<br>", "<br/>", "<br />"], "\n", $body)), ENT_QUOTES, 'UTF-8'));

            $mail->send();
            return true;
        } catch (Exception $e) {
            return false;
        }
    }
}

if (!function_exists('timeErrorNotificationsFetchInvalidEntries')) {
    function timeErrorNotificationsFetchInvalidEntries(PDO $pdo): array
    {
        $stmt = $pdo->prepare(
            "SELECT
                u.id AS employee_id,
                u.email,
                u.vorname,
                u.nachname,
                DATE(t.timestamp_datetime) AS error_date,
                GROUP_CONCAT(t.timestamp_type ORDER BY t.timestamp_datetime) AS day_sequence
             FROM users u
             INNER JOIN timestamps t ON t.employee_id = u.id
             WHERE u.zeiterfassung = '1'
               AND DATE(t.timestamp_datetime) < CURDATE()
             GROUP BY u.id, u.email, u.vorname, u.nachname, DATE(t.timestamp_datetime)
             ORDER BY error_date ASC, u.nachname ASC, u.vorname ASC"
        );
        $stmt->execute();

        $entries = [];
        foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
            if (!isValidSequence((string)$row['day_sequence'])) {
                $entries[] = $row;
            }
        }

        return $entries;
    }
}

if (!function_exists('timeErrorNotificationsGroupEntriesByEmployee')) {
    function timeErrorNotificationsGroupEntriesByEmployee(array $entries): array
    {
        $grouped = [];

        foreach ($entries as $entry) {
            $employeeId = (int)$entry['employee_id'];
            if (!isset($grouped[$employeeId])) {
                $grouped[$employeeId] = [
                    'employee_id' => $employeeId,
                    'email' => (string)$entry['email'],
                    'vorname' => (string)$entry['vorname'],
                    'nachname' => (string)$entry['nachname'],
                    'first_error_date' => (string)$entry['error_date'],
                    'error_dates' => [],
                ];
            }

            $grouped[$employeeId]['error_dates'][] = (string)$entry['error_date'];
            if ((string)$entry['error_date'] < $grouped[$employeeId]['first_error_date']) {
                $grouped[$employeeId]['first_error_date'] = (string)$entry['error_date'];
            }
        }

        foreach ($grouped as &$employee) {
            $employee['error_dates'] = array_values(array_unique($employee['error_dates']));
            sort($employee['error_dates']);
        }
        unset($employee);

        return $grouped;
    }
}

if (!function_exists('timeErrorNotificationsFetchAdminRecipients')) {
    function timeErrorNotificationsFetchAdminRecipients(PDO $pdo): array
    {
        $stmt = $pdo->prepare(
            "SELECT DISTINCT
                u.id,
                u.email,
                u.vorname,
                u.nachname
             FROM users u
             LEFT JOIN users_admin ua ON ua.userid = u.id
             WHERE (u.admin = 1 OR ua.userid IS NOT NULL)
               AND u.email IS NOT NULL
               AND u.email <> ''
             ORDER BY u.nachname ASC, u.vorname ASC"
        );
        $stmt->execute();

        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }
}

if (!function_exists('timeErrorNotificationsFetchStateByEmployee')) {
    function timeErrorNotificationsFetchStateByEmployee(PDO $pdo): array
    {
        timeErrorNotificationsEnsureTables($pdo);

        $stmt = $pdo->prepare("SELECT * FROM time_error_notification_state");
        $stmt->execute();

        $states = [];
        foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
            $states[(int)$row['employee_id']] = $row;
        }

        return $states;
    }
}

if (!function_exists('timeErrorNotificationsUpsertState')) {
    function timeErrorNotificationsUpsertState(PDO $pdo, array $employee, ?array $existingState): array
    {
        timeErrorNotificationsEnsureTables($pdo);

        $cycleStartedOn = $existingState['cycle_started_on'] ?? $employee['first_error_date'];
        $stmt = $pdo->prepare(
            "INSERT INTO time_error_notification_state (
                employee_id,
                cycle_started_on,
                first_error_date,
                last_notification_stage,
                last_notification_sent_at,
                employee_day_1_sent_at,
                employee_day_3_sent_at,
                admin_day_7_sent_at
            ) VALUES (
                :employee_id,
                :cycle_started_on,
                :first_error_date,
                :last_notification_stage,
                :last_notification_sent_at,
                :employee_day_1_sent_at,
                :employee_day_3_sent_at,
                :admin_day_7_sent_at
            )
            ON DUPLICATE KEY UPDATE
                cycle_started_on = VALUES(cycle_started_on),
                first_error_date = VALUES(first_error_date),
                last_notification_stage = VALUES(last_notification_stage),
                last_notification_sent_at = VALUES(last_notification_sent_at),
                employee_day_1_sent_at = VALUES(employee_day_1_sent_at),
                employee_day_3_sent_at = VALUES(employee_day_3_sent_at),
                admin_day_7_sent_at = VALUES(admin_day_7_sent_at)"
        );
        $stmt->execute([
            'employee_id' => $employee['employee_id'],
            'cycle_started_on' => $cycleStartedOn,
            'first_error_date' => $employee['first_error_date'],
            'last_notification_stage' => $existingState['last_notification_stage'] ?? null,
            'last_notification_sent_at' => $existingState['last_notification_sent_at'] ?? null,
            'employee_day_1_sent_at' => $existingState['employee_day_1_sent_at'] ?? null,
            'employee_day_3_sent_at' => $existingState['employee_day_3_sent_at'] ?? null,
            'admin_day_7_sent_at' => $existingState['admin_day_7_sent_at'] ?? null,
        ]);

        return [
            'employee_id' => $employee['employee_id'],
            'cycle_started_on' => $cycleStartedOn,
            'first_error_date' => $employee['first_error_date'],
            'last_notification_stage' => $existingState['last_notification_stage'] ?? null,
            'last_notification_sent_at' => $existingState['last_notification_sent_at'] ?? null,
            'employee_day_1_sent_at' => $existingState['employee_day_1_sent_at'] ?? null,
            'employee_day_3_sent_at' => $existingState['employee_day_3_sent_at'] ?? null,
            'admin_day_7_sent_at' => $existingState['admin_day_7_sent_at'] ?? null,
        ];
    }
}

if (!function_exists('timeErrorNotificationsClearResolvedStates')) {
    function timeErrorNotificationsClearResolvedStates(PDO $pdo, array $openEmployeeIds): int
    {
        timeErrorNotificationsEnsureTables($pdo);

        if (empty($openEmployeeIds)) {
            $stmt = $pdo->prepare("DELETE FROM time_error_notification_state");
            $stmt->execute();
            return $stmt->rowCount();
        }

        $placeholders = implode(',', array_fill(0, count($openEmployeeIds), '?'));
        $stmt = $pdo->prepare("DELETE FROM time_error_notification_state WHERE employee_id NOT IN ($placeholders)");
        $stmt->execute(array_values($openEmployeeIds));

        return $stmt->rowCount();
    }
}

if (!function_exists('timeErrorNotificationsMarkStageSent')) {
    function timeErrorNotificationsMarkStageSent(PDO $pdo, array $state, string $stage, string $recipientEmail): array
    {
        timeErrorNotificationsEnsureTables($pdo);

        $stmt = $pdo->prepare(
            "INSERT INTO time_error_notifications (employee_id, cycle_started_on, notification_stage, recipient_email)
             VALUES (:employee_id, :cycle_started_on, :notification_stage, :recipient_email)
             ON DUPLICATE KEY UPDATE sent_at = sent_at"
        );
        $stmt->execute([
            'employee_id' => $state['employee_id'],
            'cycle_started_on' => $state['cycle_started_on'],
            'notification_stage' => $stage,
            'recipient_email' => $recipientEmail,
        ]);

        $now = (new DateTimeImmutable('now'))->format('Y-m-d H:i:s');
        $state['last_notification_stage'] = $stage;
        $state['last_notification_sent_at'] = $now;

        if ($stage === 'employee_day_1') {
            $state['employee_day_1_sent_at'] = $now;
        } elseif ($stage === 'employee_day_3') {
            $state['employee_day_3_sent_at'] = $now;
        } elseif ($stage === 'admin_day_7') {
            $state['admin_day_7_sent_at'] = $now;
        }

        $stmt = $pdo->prepare(
            "UPDATE time_error_notification_state
             SET last_notification_stage = :last_notification_stage,
                 last_notification_sent_at = :last_notification_sent_at,
                 employee_day_1_sent_at = :employee_day_1_sent_at,
                 employee_day_3_sent_at = :employee_day_3_sent_at,
                 admin_day_7_sent_at = :admin_day_7_sent_at
             WHERE employee_id = :employee_id"
        );
        $stmt->execute([
            'last_notification_stage' => $state['last_notification_stage'],
            'last_notification_sent_at' => $state['last_notification_sent_at'],
            'employee_day_1_sent_at' => $state['employee_day_1_sent_at'],
            'employee_day_3_sent_at' => $state['employee_day_3_sent_at'],
            'admin_day_7_sent_at' => $state['admin_day_7_sent_at'],
            'employee_id' => $state['employee_id'],
        ]);

        return $state;
    }
}

if (!function_exists('timeErrorNotificationsDaysSinceDate')) {
    function timeErrorNotificationsDaysSinceDate(string $date, ?DateTimeImmutable $today = null): int
    {
        $today = $today ?: new DateTimeImmutable('today');
        $reference = new DateTimeImmutable(substr($date, 0, 10));

        return (int)$reference->diff($today)->days;
    }
}

if (!function_exists('timeErrorNotificationsFormatDateList')) {
    function timeErrorNotificationsFormatDateList(array $errorDates): string
    {
        $labels = array_map(static function (string $date): string {
            return date('d.m.Y', strtotime($date));
        }, $errorDates);

        return implode(', ', $labels);
    }
}

if (!function_exists('timeErrorNotificationsBuildEmployeeMail')) {
    function timeErrorNotificationsGetTrackingUrl(): string
    {
        if (!empty($_SERVER['HTTP_HOST'])) {
            $protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https://' : 'http://';
            return $protocol . $_SERVER['HTTP_HOST'] . '/zeiterfassung/';
        }

        return 'https://www.praxis-creutzburg.de/zeiterfassung/';
    }
}

if (!function_exists('timeErrorNotificationsBuildEmployeeMail')) {
    function timeErrorNotificationsBuildEmployeeMail(array $employee, string $stage): array
    {
        $name = trim($employee['vorname'] . ' ' . $employee['nachname']);
        $dateList = timeErrorNotificationsFormatDateList($employee['error_dates']);
        $trackingUrl = timeErrorNotificationsGetTrackingUrl();
        $subject = ($stage === 'employee_day_3')
            ? 'Erneute Erinnerung zu offenen Zeitfehlern'
            : 'Erinnerung zu offenen Zeitfehlern';

        $body = '<p>Hallo ' . htmlspecialchars($name, ENT_QUOTES, 'UTF-8') . ',</p>'
            . '<p>in deiner Zeiterfassung gibt es weiterhin offene Buchungsfehler.</p>'
            . '<p><strong>Betroffene Tage:</strong> ' . htmlspecialchars($dateList, ENT_QUOTES, 'UTF-8') . '</p>'
            . '<p>Bitte korrigiere die Eintraege in der Zeiterfassung. Solange die Fehler offen bleiben, wird der Vorgang weiter verfolgt.</p>'
            . '<p><a href="' . htmlspecialchars($trackingUrl, ENT_QUOTES, 'UTF-8') . '">Zur Zeiterfassung</a></p>';

        return [
            'subject' => $subject,
            'body' => $body,
        ];
    }
}

if (!function_exists('timeErrorNotificationsBuildAdminMail')) {
    function timeErrorNotificationsBuildAdminMail(array $employee): array
    {
        $employeeName = trim($employee['vorname'] . ' ' . $employee['nachname']);
        $dateList = timeErrorNotificationsFormatDateList($employee['error_dates']);
        $trackingUrl = timeErrorNotificationsGetTrackingUrl();
        $subject = 'Eskalation: Offene Zeitfehler von ' . $employeeName;
        $body = '<p>Bei einem Mitarbeiter bestehen weiterhin offene Zeitfehler.</p>'
            . '<p><strong>Mitarbeiter:</strong> ' . htmlspecialchars($employeeName, ENT_QUOTES, 'UTF-8') . '<br>'
            . '<strong>E-Mail:</strong> ' . htmlspecialchars($employee['email'], ENT_QUOTES, 'UTF-8') . '<br>'
            . '<strong>Betroffene Tage:</strong> ' . htmlspecialchars($dateList, ENT_QUOTES, 'UTF-8') . '</p>'
            . '<p>Bitte pruefen Sie die Zeiterfassung und stimmen Sie die Korrektur mit dem Mitarbeiter ab.</p>'
            . '<p><a href="' . htmlspecialchars($trackingUrl, ENT_QUOTES, 'UTF-8') . '">Zur Zeiterfassung</a></p>';

        return [
            'subject' => $subject,
            'body' => $body,
        ];
    }
}

if (!function_exists('timeErrorNotificationsProcess')) {
    function timeErrorNotificationsProcess(PDO $pdo, ?DateTimeImmutable $today = null): array
    {
        $today = $today ?: new DateTimeImmutable('today');
        timeErrorNotificationsEnsureTables($pdo);

        $results = [
            'processed_errors' => 0,
            'affected_employees' => 0,
            'employee_day_1_sent' => 0,
            'employee_day_3_sent' => 0,
            'admin_day_7_sent' => 0,
            'resolved_states_cleared' => 0,
            'skipped_missing_employee_email' => 0,
            'skipped_missing_admin_email' => 0,
            'failed' => [],
        ];

        $entries = timeErrorNotificationsFetchInvalidEntries($pdo);
        $employees = timeErrorNotificationsGroupEntriesByEmployee($entries);
        $adminRecipients = timeErrorNotificationsFetchAdminRecipients($pdo);
        $states = timeErrorNotificationsFetchStateByEmployee($pdo);

        $results['processed_errors'] = count($entries);
        $results['affected_employees'] = count($employees);
        $results['resolved_states_cleared'] = timeErrorNotificationsClearResolvedStates($pdo, array_keys($employees));

        foreach ($employees as $employeeId => $employee) {
            $state = timeErrorNotificationsUpsertState($pdo, $employee, $states[$employeeId] ?? null);
            $daysSinceCycleStart = timeErrorNotificationsDaysSinceDate($state['cycle_started_on'], $today);
            $employeeEmail = trim($employee['email']);

            if ($state['employee_day_1_sent_at'] === null && $daysSinceCycleStart >= 1) {
                if ($employeeEmail === '') {
                    $results['skipped_missing_employee_email']++;
                    continue;
                }

                $mail = timeErrorNotificationsBuildEmployeeMail($employee, 'employee_day_1');
                if (timeErrorNotificationsSendMail($pdo, $employeeEmail, $mail['subject'], $mail['body'])) {
                    $state = timeErrorNotificationsMarkStageSent($pdo, $state, 'employee_day_1', $employeeEmail);
                    $results['employee_day_1_sent']++;
                } else {
                    $results['failed'][] = [
                        'stage' => 'employee_day_1',
                        'employee_id' => $employeeId,
                        'recipient_email' => $employeeEmail,
                    ];
                }
                continue;
            }

            if ($state['employee_day_3_sent_at'] === null && $daysSinceCycleStart >= 3) {
                if ($employeeEmail === '') {
                    $results['skipped_missing_employee_email']++;
                    continue;
                }

                $mail = timeErrorNotificationsBuildEmployeeMail($employee, 'employee_day_3');
                if (timeErrorNotificationsSendMail($pdo, $employeeEmail, $mail['subject'], $mail['body'])) {
                    $state = timeErrorNotificationsMarkStageSent($pdo, $state, 'employee_day_3', $employeeEmail);
                    $results['employee_day_3_sent']++;
                } else {
                    $results['failed'][] = [
                        'stage' => 'employee_day_3',
                        'employee_id' => $employeeId,
                        'recipient_email' => $employeeEmail,
                    ];
                }
                continue;
            }

            if ($state['admin_day_7_sent_at'] !== null || empty($state['last_notification_sent_at'])) {
                continue;
            }

            $daysSinceLastNotification = timeErrorNotificationsDaysSinceDate($state['last_notification_sent_at'], $today);
            if ($daysSinceLastNotification < 7) {
                continue;
            }

            if (empty($adminRecipients)) {
                $results['skipped_missing_admin_email']++;
                continue;
            }

            foreach ($adminRecipients as $adminRecipient) {
                $adminEmail = trim((string)$adminRecipient['email']);
                if ($adminEmail === '') {
                    $results['skipped_missing_admin_email']++;
                    continue;
                }

                $mail = timeErrorNotificationsBuildAdminMail($employee);
                if (timeErrorNotificationsSendMail($pdo, $adminEmail, $mail['subject'], $mail['body'])) {
                    $state = timeErrorNotificationsMarkStageSent($pdo, $state, 'admin_day_7', $adminEmail);
                    $results['admin_day_7_sent']++;
                } else {
                    $results['failed'][] = [
                        'stage' => 'admin_day_7',
                        'employee_id' => $employeeId,
                        'recipient_email' => $adminEmail,
                    ];
                }
            }
        }

        return $results;
    }
}
