<?php

declare(strict_types=1);

require_once __DIR__ . '/Mailer.php';

class MemberAuthService
{
    private PDO $db;
    private array $config;
    private string $sessionKey = 'member_recipient_id';

    public function __construct(PDO $db, array $config)
    {
        $this->db = $db;
        $this->config = $config;
    }

    public function isEnabled(): bool
    {
        return isMemberModuleEnabled($this->config);
    }

    public function isLoggedIn(): bool
    {
        return isset($_SESSION[$this->sessionKey]) && (int) $_SESSION[$this->sessionKey] > 0;
    }

    public function currentRecipientId(): int
    {
        return (int) ($_SESSION[$this->sessionKey] ?? 0);
    }

    public function login(string $email, string $password): bool
    {
        try {
            $sql = 'SELECT id, email, password_hash, login_enabled FROM recipients WHERE email = ? LIMIT 1';
            $stmt = $this->db->prepare($sql);
            $stmt->execute([$email]);
            ErrorLogger::logSqlSuccess('member_auth: login lookup | SQL: ' . $sql . ' | params: ' . json_encode([$email], JSON_UNESCAPED_UNICODE));
            $recipient = $stmt->fetch(PDO::FETCH_ASSOC);
        } catch (Throwable $e) {
            ErrorLogger::logSqlError('member_auth: login lookup failed | email: ' . $email . ' | error: ' . $e->getMessage());
            return false;
        }

        if (!$recipient || empty($recipient['login_enabled']) || empty($recipient['password_hash'])) {
            return false;
        }

        if (!password_verify($password, (string) $recipient['password_hash'])) {
            return false;
        }

        session_regenerate_id(true);
        $_SESSION[$this->sessionKey] = (int) $recipient['id'];
        return true;
    }

    public function logout(): void
    {
        unset($_SESSION[$this->sessionKey]);
    }

    public function getRecipientById(int $recipientId): ?array
    {
        try {
            $sql = 'SELECT id, email, anrede, vorname, nachname, status, login_enabled, created_at FROM recipients WHERE id = ? LIMIT 1';
            $stmt = $this->db->prepare($sql);
            $stmt->execute([$recipientId]);
            ErrorLogger::logSqlSuccess('member_auth: recipient lookup | SQL: ' . $sql . ' | params: ' . json_encode([$recipientId], JSON_UNESCAPED_UNICODE));
            $recipient = $stmt->fetch(PDO::FETCH_ASSOC);
            return $recipient ?: null;
        } catch (Throwable $e) {
            ErrorLogger::logSqlError('member_auth: recipient lookup failed | recipient_id: ' . $recipientId . ' | error: ' . $e->getMessage());
            return null;
        }
    }

    public function getCurrentRecipient(): ?array
    {
        $recipientId = $this->currentRecipientId();
        return $recipientId > 0 ? $this->getRecipientById($recipientId) : null;
    }

    public function getEditableCustomFields(): array
    {
        try {
            $sql = 'SELECT field_key, label FROM custom_field_definitions ORDER BY created_at ASC';
            $rows = $this->db->query($sql)->fetchAll(PDO::FETCH_ASSOC) ?: [];
            ErrorLogger::logSqlSuccess('member_auth: custom fields loaded | SQL: ' . $sql . ' | rows: ' . count($rows));
            return $rows;
        } catch (Throwable $e) {
            ErrorLogger::logSqlError('member_auth: custom fields failed | error: ' . $e->getMessage());
            return [];
        }
    }

    public function getRecipientCustomFieldValues(int $recipientId): array
    {
        try {
            $sql = 'SELECT field_key, field_value FROM recipient_custom_fields WHERE recipient_id = ?';
            $stmt = $this->db->prepare($sql);
            $stmt->execute([$recipientId]);
            ErrorLogger::logSqlSuccess('member_auth: custom values loaded | SQL: ' . $sql . ' | params: ' . json_encode([$recipientId], JSON_UNESCAPED_UNICODE));
            return array_column($stmt->fetchAll(PDO::FETCH_ASSOC) ?: [], 'field_value', 'field_key');
        } catch (Throwable $e) {
            ErrorLogger::logSqlError('member_auth: custom values failed | recipient_id: ' . $recipientId . ' | error: ' . $e->getMessage());
            return [];
        }
    }

    public function updateProfile(int $recipientId, array $profile, array $customValues): bool
    {
        try {
            $sql = 'UPDATE recipients SET anrede = ?, vorname = ?, nachname = ? WHERE id = ?';
            $stmt = $this->db->prepare($sql);
            $stmt->execute([
                (string) ($profile['anrede'] ?? ''),
                (string) ($profile['vorname'] ?? ''),
                (string) ($profile['nachname'] ?? ''),
                $recipientId,
            ]);
            ErrorLogger::logSqlSuccess('member_auth: profile updated | SQL: ' . $sql . ' | params: ' . json_encode([$recipientId], JSON_UNESCAPED_UNICODE));

            $deleteSql = 'DELETE FROM recipient_custom_fields WHERE recipient_id = ?';
            $deleteStmt = $this->db->prepare($deleteSql);
            $deleteStmt->execute([$recipientId]);
            ErrorLogger::logSqlSuccess('member_auth: custom values cleared | SQL: ' . $deleteSql . ' | params: ' . json_encode([$recipientId], JSON_UNESCAPED_UNICODE));

            $fieldDefinitions = $this->getEditableCustomFields();
            $insertSql = 'INSERT INTO recipient_custom_fields (recipient_id, field_key, field_value) VALUES (?, ?, ?)';
            $insertStmt = $this->db->prepare($insertSql);
            foreach ($fieldDefinitions as $field) {
                $fieldKey = (string) ($field['field_key'] ?? '');
                $fieldValue = trim((string) ($customValues[$fieldKey] ?? ''));
                if ($fieldKey === '' || $fieldValue === '') {
                    continue;
                }
                $insertStmt->execute([$recipientId, $fieldKey, $fieldValue]);
            }
            ErrorLogger::logSqlSuccess('member_auth: custom values stored | SQL: ' . $insertSql . ' | params: ' . json_encode([$recipientId], JSON_UNESCAPED_UNICODE));

            return true;
        } catch (Throwable $e) {
            ErrorLogger::logSqlError('member_auth: profile update failed | recipient_id: ' . $recipientId . ' | error: ' . $e->getMessage());
            return false;
        }
    }

    public function setRecipientAccess(int $recipientId, bool $loginEnabled, ?string $plainPassword = null): bool
    {
        try {
            if ($plainPassword !== null && $plainPassword !== '') {
                $passwordHash = password_hash($plainPassword, PASSWORD_DEFAULT);
                $sql = 'UPDATE recipients SET login_enabled = ?, password_hash = ? WHERE id = ?';
                $stmt = $this->db->prepare($sql);
                $stmt->execute([$loginEnabled ? 1 : 0, $passwordHash, $recipientId]);
                ErrorLogger::logSqlSuccess('member_auth: access updated with password | SQL: ' . $sql . ' | params: ' . json_encode([$recipientId, $loginEnabled], JSON_UNESCAPED_UNICODE));
            } else {
                $sql = 'UPDATE recipients SET login_enabled = ? WHERE id = ?';
                $stmt = $this->db->prepare($sql);
                $stmt->execute([$loginEnabled ? 1 : 0, $recipientId]);
                ErrorLogger::logSqlSuccess('member_auth: access updated | SQL: ' . $sql . ' | params: ' . json_encode([$recipientId, $loginEnabled], JSON_UNESCAPED_UNICODE));
            }
            return true;
        } catch (Throwable $e) {
            ErrorLogger::logSqlError('member_auth: access update failed | recipient_id: ' . $recipientId . ' | error: ' . $e->getMessage());
            return false;
        }
    }

    public function sendAccessMail(array $recipient, string $plainPassword): bool
    {
        $email = trim((string) ($recipient['email'] ?? ''));
        if ($email === '' || $plainPassword === '') {
            return false;
        }

        $loginUrl = memberUrl('login.php', $this->config);
        $name = trim((string) ($recipient['vorname'] ?? '') . ' ' . (string) ($recipient['nachname'] ?? ''));
        $title = 'Zugang zum Mitgliederbereich';
        $body = $this->wrapMailBody(
            $title,
            '<p>Hallo ' . htmlspecialchars($name !== '' ? $name : $email, ENT_QUOTES, 'UTF-8') . ',</p>'
            . '<p>dein Zugang zum Mitgliederbereich wurde im System aktiviert.</p>'
            . '<p><strong>Login:</strong> ' . htmlspecialchars($email, ENT_QUOTES, 'UTF-8') . '<br><strong>Passwort:</strong> ' . htmlspecialchars($plainPassword, ENT_QUOTES, 'UTF-8') . '</p>'
            . '<p><a href="' . htmlspecialchars($loginUrl, ENT_QUOTES, 'UTF-8') . '" style="display:inline-block;padding:12px 18px;border-radius:8px;background:#0d6efd;color:#ffffff;text-decoration:none;">Zum Login</a></p>'
            . '<p>Du kannst das Passwort sp&auml;ter &uuml;ber die Passwort-vergessen-Funktion selbst zur&uuml;cksetzen.</p>'
        );

        $mailer = new Mailer();
        return $mailer->send($email, $title, $body);
    }

    public function requestPasswordReset(string $email): bool
    {
        $recipient = $this->findResetEligibleRecipient($email);
        if (!$recipient) {
            return true;
        }

        $token = bin2hex(random_bytes(16));
        $expiresAt = (new DateTimeImmutable('now'))
            ->modify('+' . (int) ($this->config['member']['password_reset_expiry_minutes'] ?? 60) . ' minutes')
            ->format('Y-m-d H:i:s');

        try {
            $this->deleteExpiredResetTokens();
            $sql = 'INSERT INTO member_password_resets (recipient_id, email, reset_token, expires_at, used, created_at) VALUES (?, ?, ?, ?, 0, NOW())';
            $stmt = $this->db->prepare($sql);
            $stmt->execute([(int) $recipient['id'], (string) $recipient['email'], $token, $expiresAt]);
            ErrorLogger::logSqlSuccess('member_auth: reset token created | SQL: ' . $sql . ' | params: ' . json_encode([$recipient['id'], $recipient['email']], JSON_UNESCAPED_UNICODE));
        } catch (Throwable $e) {
            ErrorLogger::logSqlError('member_auth: reset token create failed | email: ' . $email . ' | error: ' . $e->getMessage());
            return false;
        }

        return $this->sendResetMail($recipient, $token);
    }

    public function resetPassword(string $token, string $newPassword): bool
    {
        try {
            $sql = 'SELECT * FROM member_password_resets WHERE reset_token = ? AND used = 0 LIMIT 1';
            $stmt = $this->db->prepare($sql);
            $stmt->execute([$token]);
            ErrorLogger::logSqlSuccess('member_auth: reset token lookup | SQL: ' . $sql . ' | params: ' . json_encode([$token], JSON_UNESCAPED_UNICODE));
            $tokenRow = $stmt->fetch(PDO::FETCH_ASSOC);
        } catch (Throwable $e) {
            ErrorLogger::logSqlError('member_auth: reset token lookup failed | token: ' . $token . ' | error: ' . $e->getMessage());
            return false;
        }

        if (!$tokenRow) {
            return false;
        }

        if (strtotime((string) $tokenRow['expires_at']) < time()) {
            return false;
        }

        try {
            $passwordHash = password_hash($newPassword, PASSWORD_DEFAULT);
            $updateRecipientSql = 'UPDATE recipients SET password_hash = ?, login_enabled = 1 WHERE id = ?';
            $updateRecipient = $this->db->prepare($updateRecipientSql);
            $updateRecipient->execute([$passwordHash, (int) $tokenRow['recipient_id']]);
            ErrorLogger::logSqlSuccess('member_auth: password reset applied | SQL: ' . $updateRecipientSql . ' | params: ' . json_encode([(int) $tokenRow['recipient_id']], JSON_UNESCAPED_UNICODE));

            $updateTokenSql = 'UPDATE member_password_resets SET used = 1, used_at = NOW() WHERE id = ?';
            $updateToken = $this->db->prepare($updateTokenSql);
            $updateToken->execute([(int) $tokenRow['id']]);
            ErrorLogger::logSqlSuccess('member_auth: reset token marked used | SQL: ' . $updateTokenSql . ' | params: ' . json_encode([(int) $tokenRow['id']], JSON_UNESCAPED_UNICODE));

            return true;
        } catch (Throwable $e) {
            ErrorLogger::logSqlError('member_auth: password reset failed | token_id: ' . (int) ($tokenRow['id'] ?? 0) . ' | error: ' . $e->getMessage());
            return false;
        }
    }

    private function findResetEligibleRecipient(string $email): ?array
    {
        try {
            $sql = 'SELECT id, email, vorname, nachname FROM recipients WHERE email = ? AND login_enabled = 1 AND password_hash IS NOT NULL AND password_hash <> "" LIMIT 1';
            $stmt = $this->db->prepare($sql);
            $stmt->execute([$email]);
            ErrorLogger::logSqlSuccess('member_auth: reset eligible lookup | SQL: ' . $sql . ' | params: ' . json_encode([$email], JSON_UNESCAPED_UNICODE));
            $recipient = $stmt->fetch(PDO::FETCH_ASSOC);
            return $recipient ?: null;
        } catch (Throwable $e) {
            ErrorLogger::logSqlError('member_auth: reset eligible lookup failed | email: ' . $email . ' | error: ' . $e->getMessage());
            return null;
        }
    }

    private function deleteExpiredResetTokens(): void
    {
        try {
            $sql = 'DELETE FROM member_password_resets WHERE expires_at < NOW() OR used = 1';
            $this->db->exec($sql);
            ErrorLogger::logSqlSuccess('member_auth: reset token cleanup | SQL: ' . $sql);
        } catch (Throwable $e) {
            ErrorLogger::logSqlError('member_auth: reset token cleanup failed | error: ' . $e->getMessage());
        }
    }

    private function sendResetMail(array $recipient, string $token): bool
    {
        $email = (string) ($recipient['email'] ?? '');
        if ($email === '') {
            return false;
        }

        $name = trim((string) ($recipient['vorname'] ?? '') . ' ' . (string) ($recipient['nachname'] ?? ''));
        $resetUrl = memberUrl('reset_password.php?token=' . urlencode($token), $this->config);
        $title = 'Passwort fuer den Mitgliederbereich zuruecksetzen';
        $body = $this->wrapMailBody(
            $title,
            '<p>Hallo ' . htmlspecialchars($name !== '' ? $name : $email, ENT_QUOTES, 'UTF-8') . ',</p>'
            . '<p>du hast eine Zur&uuml;cksetzung deines Mitgliederbereich-Passworts angefordert.</p>'
            . '<p><a href="' . htmlspecialchars($resetUrl, ENT_QUOTES, 'UTF-8') . '" style="display:inline-block;padding:12px 18px;border-radius:8px;background:#0d6efd;color:#ffffff;text-decoration:none;">Neues Passwort setzen</a></p>'
            . '<p>Wenn du diese Anfrage nicht ausgel&ouml;st hast, kannst du diese E-Mail ignorieren.</p>'
        );

        $mailer = new Mailer();
        return $mailer->send($email, $title, $body);
    }

    private function wrapMailBody(string $title, string $content): string
    {
        return '<!DOCTYPE html><html lang="de"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>'
            . htmlspecialchars($title, ENT_QUOTES, 'UTF-8')
            . '</title></head><body style="margin:0;padding:24px;background:#f4f7fb;font-family:Arial,sans-serif;color:#1f2937;"><table role="presentation" width="100%" cellspacing="0" cellpadding="0"><tr><td align="center"><table role="presentation" width="100%" style="max-width:640px;background:#ffffff;border:1px solid #dbe4ee;border-radius:16px;overflow:hidden;"><tr><td style="padding:28px 28px 20px;"><h1 style="margin:0 0 18px;font-size:24px;line-height:1.25;color:#0f172a;">'
            . htmlspecialchars($title, ENT_QUOTES, 'UTF-8')
            . '</h1>'
            . $content
            . '<p style="margin:24px 0 0;font-size:13px;color:#64748b;">Diese Nachricht wurde automatisch vom Mitgliederbereich versendet.</p></td></tr></table></td></tr></table></body></html>';
    }
}