<?php

declare(strict_types=1);

require __DIR__ . '/dev-db.php';
require __DIR__ . '/../app/ledger.php';
require __DIR__ . '/../app/saas-auth.php';
require __DIR__ . '/../app/notices.php';
require __DIR__ . '/../app/audit.php';

function m8_assert(string $label, bool $condition, array &$failures, int &$passes): void
{
    if ($condition) {
        $passes++;
        echo "PASS {$label}\n";
        return;
    }

    $failures[] = $label;
    echo "FAIL {$label}\n";
}

/**
 * Some app functions (e.g. saas_grant_participant_access) manage their own
 * PDO transaction, so this script cannot wrap everything in one outer
 * transaction. Cleanup runs in a finally block instead.
 */
function m8_cleanup(PDO $pdo, array $tenantIds): void
{
    foreach ($tenantIds as $tid) {
        $pdo->prepare('DELETE FROM audit_log WHERE tenant_id = ?')->execute([$tid]);
        $pdo->prepare('DELETE FROM notices WHERE tenant_id = ?')->execute([$tid]);
        $pdo->prepare('DELETE FROM ledger_entries WHERE tenant_id = ?')->execute([$tid]);
        $pdo->prepare('DELETE FROM participants WHERE tenant_id = ?')->execute([$tid]);
        $pdo->prepare('DELETE FROM tenant_settings WHERE tenant_id = ?')->execute([$tid]);
        $pdo->prepare('DELETE FROM tenants WHERE id = ?')->execute([$tid]);
    }
}

$pdo = dev_pdo();
$failures = [];
$passes = 0;
$suffix = bin2hex(random_bytes(4));
$tenantIds = [];

try {
    // Zwei isolierte Test-Mandanten mit je einem Teilnehmer, einer Buchung,
    // einem Hinweis und einem Audit-Log-Eintrag anlegen.
    $participantIds = [];
    foreach (['a', 'b'] as $key) {
        $stmt = $pdo->prepare('INSERT INTO tenants (slug, name, status) VALUES (?, ?, ?)');
        $stmt->execute(["isolation-{$key}-{$suffix}", "Isolation Tenant {$key}", 'active']);
        $tenantIds[$key] = (int)$pdo->lastInsertId();

        $pdo->prepare('INSERT INTO tenant_settings (tenant_id) VALUES (?)')->execute([$tenantIds[$key]]);

        $stmt = $pdo->prepare(
            'INSERT INTO participants (tenant_id, display_name, email, email_norm, active) VALUES (?, ?, ?, ?, 1)'
        );
        $email = "isolation-{$key}-{$suffix}@test.local";
        $stmt->execute([$tenantIds[$key], "Isolation {$key}", $email, $email]);
        $participantIds[$key] = (int)$pdo->lastInsertId();

        ledger_record_payment($pdo, $tenantIds[$key], $participantIds[$key], 1000, 'isolation_test');
        notices_create($pdo, $tenantIds[$key], "Hinweis {$key}", date('Y-m-d H:i:s', time() + 3600), null);
        app_audit_log($pdo, $tenantIds[$key], null, 'isolation_test.marker', 'test', null);
    }

    $tenantA = $tenantIds['a'];
    $tenantB = $tenantIds['b'];
    $participantA = $participantIds['a'];

    // 1. Teilnehmerlisten sind strikt getrennt.
    $summariesB = ledger_fetch_participant_summaries($pdo, $tenantB, ['active_only' => true]);
    $namesB = array_column($summariesB, 'display_name');
    m8_assert('tenant B sieht nur eigenen Teilnehmer', in_array('Isolation b', $namesB, true) && !in_array('Isolation a', $namesB, true), $failures, $passes);

    // 2. Direkter Zugriff auf einen fremden Teilnehmer ueber den falschen Mandanten liefert nichts.
    $crossSummary = ledger_fetch_participant_summary($pdo, $tenantB, $participantA);
    m8_assert('fremder Teilnehmer ueber falschen Mandanten nicht lesbar', $crossSummary === null, $failures, $passes);

    // 3. Letzte Buchungen sind mandantenscoped.
    $recentB = ledger_fetch_recent_entries($pdo, $tenantB, ['limit' => 50]);
    $participantIdsInRecentB = array_column($recentB, 'participant_id');
    m8_assert('letzte Buchungen zeigen keinen fremden Teilnehmer', !in_array($participantA, $participantIdsInRecentB, true), $failures, $passes);

    // 4. Aktive Hinweise sind mandantenscoped.
    $noticeB = notices_fetch_active($pdo, $tenantB);
    m8_assert('aktiver Hinweis ist der eigene', $noticeB !== null && $noticeB['message'] === 'Hinweis b', $failures, $passes);

    // 5. Schreibversuch auf einen fremden Teilnehmer ueber den falschen Mandanten schlaegt fehl.
    $crossWriteFailed = false;
    try {
        ledger_record_payment($pdo, $tenantB, $participantA, 500, 'isolation_test');
    } catch (Throwable $e) {
        $crossWriteFailed = true;
    }
    m8_assert('Buchung auf fremden Teilnehmer ueber falschen Mandanten schlaegt fehl', $crossWriteFailed, $failures, $passes);

    // 6. Zugangsvergabe auf einen fremden Teilnehmer ueber den falschen Mandanten schlaegt fehl.
    $grantResult = saas_grant_participant_access($pdo, $tenantB, $participantA, 'member');
    m8_assert('Zugangsvergabe auf fremden Teilnehmer schlaegt fehl', $grantResult['ok'] === false, $failures, $passes);

    // 7. Storno eines fremden Eintrags ueber den falschen Mandanten aendert nichts.
    // ledger_void_entry_by_legacy_id() matcht ueber (tenant_id, legacy_table,
    // legacy_id); dafuer braucht es einen Eintrag mit einer synthetischen
    // Legacy-Referenz, unabhaengig von echten kl_*-Zeilen.
    $pdo->prepare(
        "UPDATE ledger_entries SET legacy_table = 'isolation_test', legacy_id = ? WHERE tenant_id = ? AND participant_id = ?"
    )->execute([$participantA, $tenantA, $participantA]);
    $voidedWrongTenant = ledger_void_entry_by_legacy_id($pdo, $tenantB, 'isolation_test', $participantA);
    m8_assert('Storno ueber falschen Mandanten aendert nichts', $voidedWrongTenant === false, $failures, $passes);
    $voidedRightTenant = ledger_void_entry_by_legacy_id($pdo, $tenantA, 'isolation_test', $participantA);
    m8_assert('Storno ueber den richtigen Mandanten funktioniert', $voidedRightTenant === true, $failures, $passes);

    // 8. Audit-Log ist mandantenscoped.
    $auditB = app_fetch_audit_log($pdo, $tenantB, 50);
    $auditActionsB = array_column($auditB, 'action');
    m8_assert('Audit-Log von Mandant B enthaelt nur eigene Eintraege', in_array('isolation_test.marker', $auditActionsB, true) && count($auditB) === 1, $failures, $passes);

    // 9. Tenant-Settings sind getrennt (unterschiedliche IDs, kein Ueberlauf).
    $settingsA = saas_fetch_tenant_settings($pdo, $tenantA);
    $settingsB = saas_fetch_tenant_settings($pdo, $tenantB);
    m8_assert('tenant_settings sind pro Mandant getrennt', $settingsA['tenant_id'] !== $settingsB['tenant_id'], $failures, $passes);
} catch (Throwable $e) {
    if ($pdo->inTransaction()) {
        $pdo->rollBack();
    }
    fwrite(STDERR, "M8 tenant isolation check failed to run: {$e->getMessage()}\n");
    m8_cleanup($pdo, $tenantIds);
    exit(1);
}

m8_cleanup($pdo, $tenantIds);

if ($failures !== []) {
    echo "\nM8 tenant isolation check failed with " . count($failures) . " failure(s):\n";
    foreach ($failures as $failure) {
        echo "- {$failure}\n";
    }
    exit(1);
}

echo "\nM8 tenant isolation check passed with {$passes} assertions.\n";
