<?php

declare(strict_types=1);

require_once __DIR__ . '/bootstrap.php';
require_once __DIR__ . '/database.php';

function saas_email_norm(string $email): string
{
    return strtolower(trim($email));
}

function saas_slugify(string $value): string
{
    $slug = strtolower(trim($value));
    $slug = preg_replace('/[^a-z0-9]+/', '-', $slug) ?? '';
    $slug = trim($slug, '-');

    return $slug;
}

function saas_html(mixed $value): string
{
    return htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8');
}

function saas_should_show_auth_links(): bool
{
    return app_is_dev() || app_env('APP_SHOW_AUTH_LINKS', '0') === '1';
}

function saas_token_hash(string $token): string
{
    return hash('sha256', $token);
}

function saas_normalize_host(?string $host): string
{
    $host = strtolower(trim((string)$host));
    if ($host === '') {
        return '';
    }

    if (str_contains($host, '://')) {
        $parsedHost = parse_url($host, PHP_URL_HOST);
        $host = is_string($parsedHost) ? $parsedHost : $host;
    }

    $host = preg_replace('/:\d+$/', '', $host) ?? $host;

    return trim($host, " \t\n\r\0\x0B.");
}

function saas_primary_app_host(): string
{
    return saas_normalize_host(app_env('APP_PRIMARY_HOST', ''));
}

function saas_is_primary_app_host(?string $host = null): bool
{
    $primaryHost = saas_primary_app_host();
    if ($primaryHost === '') {
        return false;
    }

    $host = $host ?? ($_SERVER['HTTP_HOST'] ?? '');

    return saas_normalize_host($host) === $primaryHost;
}

function saas_resolve_tenant_domain(PDO $pdo, ?string $host = null): ?array
{
    $host = saas_normalize_host($host ?? ($_SERVER['HTTP_HOST'] ?? ''));
    if ($host === '' || saas_is_primary_app_host($host)) {
        return null;
    }

    $stmt = $pdo->prepare(
        'SELECT
            td.id AS tenant_domain_id,
            td.domain,
            td.domain_norm,
            td.domain_type,
            t.id AS tenant_id,
            t.slug AS tenant_slug,
            t.name AS tenant_name,
            t.status AS tenant_status
         FROM tenant_domains td
         JOIN tenants t ON t.id = td.tenant_id
         WHERE td.domain_norm = ?
           AND td.status = ?
           AND t.status = ?
         LIMIT 1'
    );
    $stmt->execute([$host, 'active', 'active']);
    $row = $stmt->fetch();

    return $row !== false ? $row : null;
}

function saas_parse_money_cents(mixed $value): ?int
{
    $normalized = trim((string)$value);
    $normalized = str_replace(["\xc2\xa0", ' '], '', $normalized);

    if ($normalized === '') {
        return null;
    }

    if (str_contains($normalized, ',') && str_contains($normalized, '.')) {
        $normalized = str_replace('.', '', $normalized);
    }

    $normalized = str_replace(',', '.', $normalized);
    if (!preg_match('/^-?[0-9]+(?:\.[0-9]{1,2})?$/', $normalized)) {
        return null;
    }

    $negative = str_starts_with($normalized, '-');
    if ($negative) {
        $normalized = substr($normalized, 1);
    }

    [$euros, $cents] = array_pad(explode('.', $normalized, 2), 2, '0');
    $result = ((int)$euros * 100) + (int)str_pad(substr($cents, 0, 2), 2, '0');

    return $negative ? -$result : $result;
}

function saas_format_money_cents(mixed $cents): string
{
    return number_format(((int)$cents) / 100, 2, ',', '');
}

function saas_current_user(?PDO $pdo = null): ?array
{
    static $cached = false;
    static $user = null;

    app_start_session();

    if ($cached) {
        return $user;
    }

    $cached = true;

    $userId = isset($_SESSION['saas_user_id']) ? (int)$_SESSION['saas_user_id'] : 0;
    $tenantId = isset($_SESSION['saas_tenant_id']) ? (int)$_SESSION['saas_tenant_id'] : 0;
    if ($userId <= 0 || $tenantId <= 0) {
        return null;
    }

    try {
        $pdo = $pdo ?? app_db_pdo();
        $stmt = $pdo->prepare(
            'SELECT
                u.id AS user_id,
                u.email,
                u.email_norm,
                u.display_name,
                u.email_verified_at,
                u.status AS user_status,
                t.id AS tenant_id,
                t.slug AS tenant_slug,
                t.name AS tenant_name,
                t.status AS tenant_status,
                tm.role,
                tm.status AS membership_status
             FROM users u
             JOIN tenant_memberships tm ON tm.user_id = u.id
             JOIN tenants t ON t.id = tm.tenant_id
             WHERE u.id = ?
               AND t.id = ?
               AND u.status = ?
               AND tm.status = ?
               AND t.status = ?
             LIMIT 1'
        );
        $stmt->execute([$userId, $tenantId, 'active', 'active', 'active']);
        $row = $stmt->fetch();
    } catch (Throwable $e) {
        return null;
    }

    if ($row === false) {
        unset($_SESSION['saas_user_id'], $_SESSION['saas_tenant_id'], $_SESSION['saas_role']);
        return null;
    }

    $user = $row;

    return $user;
}

function saas_session_login(array $identity): void
{
    app_start_session();
    session_regenerate_id(true);

    $_SESSION['saas_user_id'] = (int)$identity['user_id'];
    $_SESSION['saas_tenant_id'] = (int)$identity['tenant_id'];
    $_SESSION['saas_role'] = (string)$identity['role'];
    saas_clear_pending_tenant_selection();
}

function saas_logout(): void
{
    app_start_session();
    unset(
        $_SESSION['saas_user_id'],
        $_SESSION['saas_tenant_id'],
        $_SESSION['saas_role'],
        $_SESSION['saas_pending_user_id'],
        $_SESSION['saas_pending_started_at']
    );
}

function saas_require_login(): array
{
    $user = saas_current_user();
    if ($user !== null) {
        return $user;
    }

    header('Location: login.php');
    exit;
}

function saas_user_has_role(array|string $roles, ?array $user = null): bool
{
    $user = $user ?? saas_current_user();
    if ($user === null) {
        return false;
    }

    $allowed = is_array($roles) ? $roles : [$roles];

    return in_array((string)($user['role'] ?? ''), $allowed, true);
}

function saas_require_role(array|string $roles): array
{
    $user = saas_require_login();
    if (saas_user_has_role($roles, $user)) {
        return $user;
    }

    http_response_code(403);
    die('Keine Berechtigung.');
}

function saas_can_manage_tenant_settings(?array $user = null): bool
{
    return saas_user_has_role(['owner', 'admin'], $user);
}

function saas_fetch_tenant_settings(PDO $pdo, int $tenantId): ?array
{
    $stmt = $pdo->prepare(
        'SELECT
            t.id AS tenant_id,
            t.slug,
            t.name,
            t.status,
            t.timezone,
            t.locale,
            t.currency_code,
            ts.mark_price_cents,
            ts.self_entry_enabled,
            ts.paypal_enabled,
            ts.paypal_url_template,
            ts.sheet_window_days,
            ts.negative_warning_cents
         FROM tenants t
         LEFT JOIN tenant_settings ts ON ts.tenant_id = t.id
         WHERE t.id = ?
         LIMIT 1'
    );
    $stmt->execute([$tenantId]);
    $settings = $stmt->fetch();

    if ($settings === false) {
        return null;
    }

    if ($settings['mark_price_cents'] === null) {
        $pdo->prepare('INSERT INTO tenant_settings (tenant_id) VALUES (?)')->execute([$tenantId]);

        return saas_fetch_tenant_settings($pdo, $tenantId);
    }

    return [
        'tenant_id' => (int)$settings['tenant_id'],
        'slug' => (string)$settings['slug'],
        'name' => (string)$settings['name'],
        'status' => (string)$settings['status'],
        'timezone' => (string)$settings['timezone'],
        'locale' => (string)$settings['locale'],
        'currency_code' => (string)$settings['currency_code'],
        'mark_price_cents' => (int)$settings['mark_price_cents'],
        'self_entry_enabled' => (int)$settings['self_entry_enabled'],
        'paypal_enabled' => (int)$settings['paypal_enabled'],
        'paypal_url_template' => (string)$settings['paypal_url_template'],
        'sheet_window_days' => (int)$settings['sheet_window_days'],
        'negative_warning_cents' => (int)$settings['negative_warning_cents'],
    ];
}

function saas_update_tenant_settings(PDO $pdo, int $tenantId, array $input): array
{
    $tenantName = trim((string)($input['tenant_name'] ?? ''));
    $timezone = trim((string)($input['timezone'] ?? 'Europe/Berlin'));
    $locale = trim((string)($input['locale'] ?? 'de-DE'));
    $currencyCode = strtoupper(trim((string)($input['currency_code'] ?? 'EUR')));
    $markPriceCents = saas_parse_money_cents($input['mark_price'] ?? '');
    $sheetWindowDays = filter_var($input['sheet_window_days'] ?? null, FILTER_VALIDATE_INT);
    $negativeWarningCents = saas_parse_money_cents($input['negative_warning'] ?? '');
    $paypalUrlTemplate = trim((string)($input['paypal_url_template'] ?? ''));

    $errors = [];
    if (strlen($tenantName) < 3 || strlen($tenantName) > 255) {
        $errors[] = 'Der Kundenname muss zwischen 3 und 255 Zeichen lang sein.';
    }
    if (!in_array($timezone, DateTimeZone::listIdentifiers(), true)) {
        $errors[] = 'Die Zeitzone ist ungültig.';
    }
    if (!preg_match('/^[a-z]{2}-[A-Z]{2}$/', $locale)) {
        $errors[] = 'Die Locale muss dem Muster de-DE entsprechen.';
    }
    if (!preg_match('/^[A-Z]{3}$/', $currencyCode)) {
        $errors[] = 'Der Währungscode muss aus drei Großbuchstaben bestehen.';
    }
    if ($markPriceCents === null || $markPriceCents < 1 || $markPriceCents > 10000) {
        $errors[] = 'Der Preis pro Strich muss zwischen 0,01 und 100,00 liegen.';
    }
    if ($sheetWindowDays === false || $sheetWindowDays < 1 || $sheetWindowDays > 365) {
        $errors[] = 'Das Listenfenster muss zwischen 1 und 365 Tagen liegen.';
    }
    if ($negativeWarningCents === null || $negativeWarningCents < 0 || $negativeWarningCents > 100000) {
        $errors[] = 'Die Schulden-Warnschwelle muss zwischen 0,00 und 1000,00 liegen.';
    }
    if (strlen($paypalUrlTemplate) > 1000) {
        $errors[] = 'Der PayPal-Link ist zu lang.';
    }

    if ($errors !== []) {
        return ['ok' => false, 'errors' => $errors];
    }

    $selfEntryEnabled = !empty($input['self_entry_enabled']) ? 1 : 0;
    $paypalEnabled = !empty($input['paypal_enabled']) ? 1 : 0;

    try {
        $pdo->beginTransaction();

        $stmt = $pdo->prepare(
            'UPDATE tenants
             SET name = ?, timezone = ?, locale = ?, currency_code = ?
             WHERE id = ?'
        );
        $stmt->execute([$tenantName, $timezone, $locale, $currencyCode, $tenantId]);

        $stmt = $pdo->prepare(
            'INSERT INTO tenant_settings
               (tenant_id, mark_price_cents, self_entry_enabled, paypal_enabled,
                paypal_url_template, sheet_window_days, negative_warning_cents)
             VALUES (?, ?, ?, ?, ?, ?, ?)
             ON DUPLICATE KEY UPDATE
               mark_price_cents = VALUES(mark_price_cents),
               self_entry_enabled = VALUES(self_entry_enabled),
               paypal_enabled = VALUES(paypal_enabled),
               paypal_url_template = VALUES(paypal_url_template),
               sheet_window_days = VALUES(sheet_window_days),
               negative_warning_cents = VALUES(negative_warning_cents)'
        );
        $stmt->execute([
            $tenantId,
            $markPriceCents,
            $selfEntryEnabled,
            $paypalEnabled,
            $paypalUrlTemplate,
            (int)$sheetWindowDays,
            -abs($negativeWarningCents),
        ]);

        $pdo->commit();
    } catch (Throwable $e) {
        if ($pdo->inTransaction()) {
            $pdo->rollBack();
        }

        return [
            'ok' => false,
            'errors' => ['Die Einstellungen konnten nicht gespeichert werden.'],
        ];
    }

    return [
        'ok' => true,
        'errors' => [],
        'settings' => saas_fetch_tenant_settings($pdo, $tenantId),
    ];
}

function saas_find_auth_identity(PDO $pdo, string $email, string $tenantSlug = ''): ?array
{
    $emailNorm = saas_email_norm($email);
    if (!filter_var($emailNorm, FILTER_VALIDATE_EMAIL)) {
        return null;
    }

    $params = [$emailNorm, 'active', 'active', 'active'];
    $tenantFilter = '';
    if (trim($tenantSlug) !== '') {
        $tenantFilter = ' AND t.slug = ?';
        $params[] = saas_slugify($tenantSlug);
    }

    $stmt = $pdo->prepare(
        'SELECT
            u.id AS user_id,
            u.email,
            u.email_norm,
            u.display_name,
            u.status AS user_status,
            u.email_verified_at,
            t.id AS tenant_id,
            t.slug AS tenant_slug,
            t.name AS tenant_name,
            tm.role
         FROM users u
         JOIN tenant_memberships tm ON tm.user_id = u.id
         JOIN tenants t ON t.id = tm.tenant_id
         WHERE u.email_norm = ?
           AND u.status = ?
           AND tm.status = ?
           AND t.status = ?' . $tenantFilter . '
         ORDER BY
           CASE tm.role
             WHEN \'owner\' THEN 1
             WHEN \'admin\' THEN 2
             ELSE 3
           END,
           t.name
         LIMIT 1'
    );
    $stmt->execute($params);
    $identity = $stmt->fetch();

    return $identity !== false ? $identity : null;
}

function saas_list_user_memberships(PDO $pdo, int $userId): array
{
    $stmt = $pdo->prepare(
        'SELECT
            u.id AS user_id,
            u.email,
            u.email_norm,
            u.display_name,
            u.status AS user_status,
            u.email_verified_at,
            t.id AS tenant_id,
            t.slug AS tenant_slug,
            t.name AS tenant_name,
            t.status AS tenant_status,
            tm.role,
            tm.status AS membership_status
         FROM users u
         JOIN tenant_memberships tm ON tm.user_id = u.id
         JOIN tenants t ON t.id = tm.tenant_id
         WHERE u.id = ?
           AND u.status = ?
           AND tm.status = ?
           AND t.status = ?
         ORDER BY
           CASE tm.role
             WHEN \'owner\' THEN 1
             WHEN \'admin\' THEN 2
             ELSE 3
           END,
           t.name'
    );
    $stmt->execute([$userId, 'active', 'active', 'active']);

    return $stmt->fetchAll();
}

function saas_identity_for_user_tenant(PDO $pdo, int $userId, int $tenantId): ?array
{
    $stmt = $pdo->prepare(
        'SELECT
            u.id AS user_id,
            u.email,
            u.email_norm,
            u.display_name,
            u.status AS user_status,
            u.email_verified_at,
            t.id AS tenant_id,
            t.slug AS tenant_slug,
            t.name AS tenant_name,
            t.status AS tenant_status,
            tm.role,
            tm.status AS membership_status
         FROM users u
         JOIN tenant_memberships tm ON tm.user_id = u.id
         JOIN tenants t ON t.id = tm.tenant_id
         WHERE u.id = ?
           AND t.id = ?
           AND u.status = ?
           AND tm.status = ?
           AND t.status = ?
         LIMIT 1'
    );
    $stmt->execute([$userId, $tenantId, 'active', 'active', 'active']);
    $identity = $stmt->fetch();

    return $identity !== false ? $identity : null;
}

function saas_start_pending_tenant_selection(int $userId): void
{
    app_start_session();
    session_regenerate_id(true);

    $_SESSION['saas_pending_user_id'] = $userId;
    $_SESSION['saas_pending_started_at'] = time();
    unset($_SESSION['saas_user_id'], $_SESSION['saas_tenant_id'], $_SESSION['saas_role']);
}

function saas_pending_tenant_user_id(): ?int
{
    app_start_session();

    $userId = isset($_SESSION['saas_pending_user_id']) ? (int)$_SESSION['saas_pending_user_id'] : 0;
    $startedAt = isset($_SESSION['saas_pending_started_at']) ? (int)$_SESSION['saas_pending_started_at'] : 0;
    if ($userId <= 0 || $startedAt <= 0 || $startedAt < time() - 900) {
        saas_clear_pending_tenant_selection();
        return null;
    }

    return $userId;
}

function saas_clear_pending_tenant_selection(): void
{
    if (PHP_SAPI !== 'cli') {
        app_start_session();
    }

    unset($_SESSION['saas_pending_user_id'], $_SESSION['saas_pending_started_at']);
}

function saas_create_auth_token(PDO $pdo, int $userId, string $tokenType, ?int $tenantId, int $ttlMinutes): array
{
    $token = bin2hex(random_bytes(32));

    $pdo->prepare(
        'UPDATE user_auth_tokens
         SET consumed_at = NOW()
         WHERE user_id = ?
           AND token_type = ?
           AND consumed_at IS NULL'
    )->execute([$userId, $tokenType]);

    $stmt = $pdo->prepare(
        'INSERT INTO user_auth_tokens (user_id, tenant_id, token_type, token_hash, expires_at)
         VALUES (?, ?, ?, ?, DATE_ADD(NOW(), INTERVAL ? MINUTE))'
    );
    $stmt->execute([$userId, $tenantId, $tokenType, saas_token_hash($token), $ttlMinutes]);

    return [
        'token' => $token,
        'expires_at' => date('Y-m-d H:i:s', time() + ($ttlMinutes * 60)),
    ];
}

function saas_fetch_valid_auth_token(PDO $pdo, string $token, string $tokenType): ?array
{
    if (!preg_match('/^[a-f0-9]{64}$/', $token)) {
        return null;
    }

    $stmt = $pdo->prepare(
        'SELECT
            at.id,
            at.user_id,
            at.tenant_id,
            at.token_type,
            at.expires_at,
            u.email,
            u.email_norm,
            u.display_name,
            u.status AS user_status,
            u.email_verified_at
         FROM user_auth_tokens at
         JOIN users u ON u.id = at.user_id
         WHERE at.token_hash = ?
           AND at.token_type = ?
           AND at.consumed_at IS NULL
           AND at.expires_at >= NOW()
           AND u.status = ?
         LIMIT 1'
    );
    $stmt->execute([saas_token_hash($token), $tokenType, 'active']);
    $row = $stmt->fetch();

    return $row !== false ? $row : null;
}

function saas_request_password_reset(PDO $pdo, string $email, string $tenantSlug = ''): array
{
    $emailNorm = saas_email_norm($email);
    if (!filter_var($emailNorm, FILTER_VALIDATE_EMAIL)) {
        return [
            'ok' => false,
            'errors' => ['Bitte eine gültige E-Mail-Adresse eingeben.'],
        ];
    }

    $identity = saas_find_auth_identity($pdo, $emailNorm, $tenantSlug);
    if ($identity === null) {
        return [
            'ok' => true,
            'message' => 'Wenn ein passendes Konto existiert, wurde ein Link vorbereitet.',
            'token' => null,
            'errors' => [],
        ];
    }

    $token = saas_create_auth_token(
        $pdo,
        (int)$identity['user_id'],
        'password_reset',
        (int)$identity['tenant_id'],
        60
    );

    return [
        'ok' => true,
        'message' => 'Wenn ein passendes Konto existiert, wurde ein Link vorbereitet.',
        'token' => $token['token'],
        'expires_at' => $token['expires_at'],
        'errors' => [],
    ];
}

function saas_reset_password_with_token(PDO $pdo, string $token, string $password, string $passwordConfirm): array
{
    $errors = [];
    if (strlen($password) < 8) {
        $errors[] = 'Das Passwort muss mindestens 8 Zeichen lang sein.';
    }
    if ($password !== $passwordConfirm) {
        $errors[] = 'Die Passwort-Wiederholung stimmt nicht.';
    }

    $tokenRow = saas_fetch_valid_auth_token($pdo, $token, 'password_reset');
    if ($tokenRow === null) {
        $errors[] = 'Der Link ist ungültig oder abgelaufen.';
    }

    if ($errors !== []) {
        return ['ok' => false, 'errors' => $errors];
    }

    try {
        $pdo->beginTransaction();
        $pdo->prepare('UPDATE users SET password_hash = ? WHERE id = ?')
            ->execute([password_hash($password, PASSWORD_DEFAULT), (int)$tokenRow['user_id']]);
        $pdo->prepare('UPDATE user_auth_tokens SET consumed_at = NOW() WHERE id = ?')
            ->execute([(int)$tokenRow['id']]);
        $pdo->commit();
    } catch (Throwable $e) {
        if ($pdo->inTransaction()) {
            $pdo->rollBack();
        }

        return [
            'ok' => false,
            'errors' => ['Das Passwort konnte nicht gespeichert werden.'],
        ];
    }

    return ['ok' => true, 'errors' => []];
}

function saas_request_email_verification(PDO $pdo, int $userId, ?int $tenantId = null): array
{
    $stmt = $pdo->prepare(
        'SELECT id, email_verified_at, status
         FROM users
         WHERE id = ?
         LIMIT 1'
    );
    $stmt->execute([$userId]);
    $user = $stmt->fetch();

    if ($user === false || (string)$user['status'] !== 'active') {
        return [
            'ok' => false,
            'errors' => ['Dieses Benutzerkonto ist nicht aktiv.'],
        ];
    }

    if ($user['email_verified_at'] !== null) {
        return [
            'ok' => true,
            'already_verified' => true,
            'token' => null,
            'errors' => [],
        ];
    }

    $token = saas_create_auth_token($pdo, $userId, 'email_verification', $tenantId, 1440);

    return [
        'ok' => true,
        'already_verified' => false,
        'token' => $token['token'],
        'expires_at' => $token['expires_at'],
        'errors' => [],
    ];
}

function saas_verify_email_token(PDO $pdo, string $token): array
{
    $tokenRow = saas_fetch_valid_auth_token($pdo, $token, 'email_verification');
    if ($tokenRow === null) {
        return [
            'ok' => false,
            'errors' => ['Der Link ist ungültig oder abgelaufen.'],
        ];
    }

    try {
        $pdo->beginTransaction();
        $pdo->prepare('UPDATE users SET email_verified_at = COALESCE(email_verified_at, NOW()) WHERE id = ?')
            ->execute([(int)$tokenRow['user_id']]);
        $pdo->prepare('UPDATE user_auth_tokens SET consumed_at = NOW() WHERE id = ?')
            ->execute([(int)$tokenRow['id']]);
        $pdo->commit();
    } catch (Throwable $e) {
        if ($pdo->inTransaction()) {
            $pdo->rollBack();
        }

        return [
            'ok' => false,
            'errors' => ['Die E-Mail-Adresse konnte nicht bestätigt werden.'],
        ];
    }

    return [
        'ok' => true,
        'email' => (string)$tokenRow['email'],
        'errors' => [],
    ];
}

function saas_authenticate(PDO $pdo, string $email, string $password, string $tenantSlug = ''): array
{
    $emailNorm = saas_email_norm($email);
    if (!filter_var($emailNorm, FILTER_VALIDATE_EMAIL) || $password === '') {
        return [
            'ok' => false,
            'errors' => ['E-Mail oder Passwort ist ungültig.'],
        ];
    }

    $stmt = $pdo->prepare(
        'SELECT id, email, email_norm, display_name, password_hash, status
         FROM users
         WHERE email_norm = ?
         LIMIT 1'
    );
    $stmt->execute([$emailNorm]);
    $user = $stmt->fetch();

    if ($user === false
        || (string)$user['status'] !== 'active'
        || empty($user['password_hash'])
        || !password_verify($password, (string)$user['password_hash'])
    ) {
        return [
            'ok' => false,
            'errors' => ['E-Mail oder Passwort ist ungültig.'],
        ];
    }

    $params = [(int)$user['id'], 'active', 'active'];
    $tenantFilter = '';
    if (trim($tenantSlug) !== '') {
        $tenantFilter = ' AND t.slug = ?';
        $params[] = saas_slugify($tenantSlug);
    }

    $stmt = $pdo->prepare(
        'SELECT t.id AS tenant_id
         FROM tenant_memberships tm
         JOIN tenants t ON t.id = tm.tenant_id
         WHERE tm.user_id = ?
           AND tm.status = ?
           AND t.status = ?' . $tenantFilter . '
         ORDER BY
           CASE tm.role
             WHEN \'owner\' THEN 1
             WHEN \'admin\' THEN 2
             ELSE 3
           END,
           t.name'
    );
    $stmt->execute($params);
    $tenantIds = array_map('intval', $stmt->fetchAll(PDO::FETCH_COLUMN));

    if ($tenantIds === []) {
        return [
            'ok' => false,
            'errors' => ['Für dieses Konto ist kein aktiver Mandant verfügbar.'],
        ];
    }

    if (count($tenantIds) > 1 && trim($tenantSlug) === '') {
        return [
            'ok' => true,
            'needs_tenant_selection' => true,
            'user_id' => (int)$user['id'],
            'memberships' => saas_list_user_memberships($pdo, (int)$user['id']),
            'errors' => [],
        ];
    }

    $identity = saas_identity_for_user_tenant($pdo, (int)$user['id'], $tenantIds[0]);
    if ($identity === null) {
        return [
            'ok' => false,
            'errors' => ['Für dieses Konto ist kein aktiver Mandant verfügbar.'],
        ];
    }

    $pdo->prepare('UPDATE users SET last_login_at = NOW() WHERE id = ?')->execute([(int)$user['id']]);

    return [
        'ok' => true,
        'identity' => $identity,
        'errors' => [],
    ];
}

function saas_register_tenant_owner(PDO $pdo, array $input): array
{
    $tenantName = trim((string)($input['tenant_name'] ?? ''));
    $tenantSlug = saas_slugify((string)($input['tenant_slug'] ?? ''));
    if ($tenantSlug === '') {
        $tenantSlug = saas_slugify($tenantName);
    }

    $displayName = trim((string)($input['display_name'] ?? ''));
    $email = trim((string)($input['email'] ?? ''));
    $emailNorm = saas_email_norm($email);
    $password = (string)($input['password'] ?? '');
    $passwordConfirm = (string)($input['password_confirm'] ?? '');

    $errors = [];
    if (strlen($tenantName) < 3 || strlen($tenantName) > 255) {
        $errors[] = 'Der Kundenname muss zwischen 3 und 255 Zeichen lang sein.';
    }
    if (!preg_match('/^[a-z0-9][a-z0-9-]{1,98}[a-z0-9]$/', $tenantSlug)) {
        $errors[] = 'Das Kundenkürzel darf nur Kleinbuchstaben, Zahlen und Bindestriche enthalten.';
    }
    if (strlen($displayName) < 2 || strlen($displayName) > 255) {
        $errors[] = 'Der Name muss zwischen 2 und 255 Zeichen lang sein.';
    }
    if (!filter_var($emailNorm, FILTER_VALIDATE_EMAIL)) {
        $errors[] = 'Bitte eine gültige E-Mail-Adresse eingeben.';
    }
    if (strlen($password) < 8) {
        $errors[] = 'Das Passwort muss mindestens 8 Zeichen lang sein.';
    }
    if ($password !== $passwordConfirm) {
        $errors[] = 'Die Passwort-Wiederholung stimmt nicht.';
    }

    if ($errors !== []) {
        return ['ok' => false, 'errors' => $errors];
    }

    $stmt = $pdo->prepare('SELECT id, password_hash, status FROM users WHERE email_norm = ? LIMIT 1');
    $stmt->execute([$emailNorm]);
    $existingUser = $stmt->fetch();
    if ($existingUser !== false && (string)$existingUser['status'] !== 'active') {
        return [
            'ok' => false,
            'errors' => ['Dieses Benutzerkonto ist nicht aktiv.'],
        ];
    }
    if ($existingUser !== false
        && !empty($existingUser['password_hash'])
        && !password_verify($password, (string)$existingUser['password_hash'])
    ) {
        return [
            'ok' => false,
            'errors' => ['Diese E-Mail ist bereits vergeben. Bitte mit dem bestehenden Passwort anmelden.'],
        ];
    }

    $stmt = $pdo->prepare('SELECT COUNT(*) FROM tenants WHERE slug = ?');
    $stmt->execute([$tenantSlug]);
    if ((int)$stmt->fetchColumn() > 0) {
        return [
            'ok' => false,
            'errors' => ['Dieses Kundenkürzel ist bereits vergeben.'],
        ];
    }

    $passwordHash = password_hash($password, PASSWORD_DEFAULT);

    try {
        $pdo->beginTransaction();

        $stmt = $pdo->prepare(
            'INSERT INTO tenants (slug, name, status, timezone, locale, currency_code)
             VALUES (?, ?, ?, ?, ?, ?)'
        );
        $stmt->execute([$tenantSlug, $tenantName, 'active', 'Europe/Berlin', 'de-DE', 'EUR']);
        $tenantId = (int)$pdo->lastInsertId();

        $stmt = $pdo->prepare(
            'INSERT INTO tenant_settings
               (tenant_id, mark_price_cents, self_entry_enabled, paypal_enabled, paypal_url_template)
             VALUES (?, ?, ?, ?, ?)'
        );
        $stmt->execute([$tenantId, 20, 1, 0, '']);

        if ($existingUser === false) {
            $stmt = $pdo->prepare(
                'INSERT INTO users (email, email_norm, display_name, password_hash, status)
                 VALUES (?, ?, ?, ?, ?)'
            );
            $stmt->execute([$email, $emailNorm, $displayName, $passwordHash, 'active']);
            $userId = (int)$pdo->lastInsertId();
        } else {
            $userId = (int)$existingUser['id'];
            $stmt = $pdo->prepare(
                'UPDATE users
                 SET email = ?, display_name = ?, password_hash = ?, status = ?
                 WHERE id = ?'
            );
            $stmt->execute([$email, $displayName, $passwordHash, 'active', $userId]);
        }

        $stmt = $pdo->prepare(
            'INSERT INTO tenant_memberships (tenant_id, user_id, role, status, joined_at)
             VALUES (?, ?, ?, ?, NOW())'
        );
        $stmt->execute([$tenantId, $userId, 'owner', 'active']);

        $stmt = $pdo->prepare(
            'INSERT INTO participants
               (tenant_id, user_id, display_name, email, email_norm, active)
             VALUES (?, ?, ?, ?, ?, ?)'
        );
        $stmt->execute([$tenantId, $userId, $displayName, $email, $emailNorm, 1]);

        $verificationToken = saas_create_auth_token($pdo, $userId, 'email_verification', $tenantId, 1440);

        $pdo->commit();
    } catch (Throwable $e) {
        if ($pdo->inTransaction()) {
            $pdo->rollBack();
        }

        return [
            'ok' => false,
            'errors' => ['Die Registrierung konnte nicht gespeichert werden.'],
        ];
    }

    return [
        'ok' => true,
        'identity' => [
            'user_id' => $userId,
            'email' => $email,
            'email_norm' => $emailNorm,
            'display_name' => $displayName,
            'tenant_id' => $tenantId,
            'tenant_slug' => $tenantSlug,
            'tenant_name' => $tenantName,
            'role' => 'owner',
        ],
        'email_verification_token' => $verificationToken['token'],
        'errors' => [],
    ];
}
