<?php

declare(strict_types=1);

use Illuminate\Database\QueryException;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\DB;

/**
 * CTO-13: RLS smoke-test.
 *
 * Проверяет ключевые сценарии Row Level Security из Прил. И «Часть Г.1»:
 * - Кейс 1: SET LOCAL app.current_tenant_id изолирует SELECT по tenant_id.
 * - Кейс 4: WITH CHECK блокирует INSERT в чужой тенант.
 *
 * Кейсы 2-3 (PgBouncer pooling, job retry) — отдельно в production-окружении
 * через Прил. И Г.1 (PgBouncer на native Windows-стеке нет).
 *
 * Кейс 5 (REVOKE на 6 saas-таблицах) — требует роли `crm_app_user` из
 * db/00_create_roles.sql + db/02_grants.sql, на dev superuser обходит REVOKE.
 *
 * Стратегия: для эмуляции crm_app_user на dev-машине создаётся NOLOGIN-роль
 * `testing_rls_user` без BYPASSRLS. SET LOCAL ROLE внутри транзакции —
 * сбрасывается ROLLBACK'ом DatabaseTransactions.
 */
uses(DatabaseTransactions::class);

beforeEach(function () {
    // Идемпотентное создание testing-роли (роль = глобальный объект, не Rolled Back).
    DB::unprepared(<<<'SQL'
DO $$
BEGIN
    IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'testing_rls_user') THEN
        CREATE ROLE testing_rls_user NOLOGIN;
        GRANT USAGE ON SCHEMA public TO testing_rls_user;
        GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO testing_rls_user;
        GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO testing_rls_user;
    END IF;
END
$$;
SQL);

    // 2 тенанта для проверки изоляции
    $this->tenant1Id = DB::table('tenants')->insertGetId([
        'subdomain' => 'rls-tenant-a-'.uniqid(),
        'organization_name' => 'RLS Tenant A',
        'contact_email' => 'a@rls-test.local',
        'api_key_limit' => 5,
    ]);
    $this->tenant2Id = DB::table('tenants')->insertGetId([
        'subdomain' => 'rls-tenant-b-'.uniqid(),
        'organization_name' => 'RLS Tenant B',
        'contact_email' => 'b@rls-test.local',
        'api_key_limit' => 5,
    ]);

    $this->project1Id = DB::table('projects')->insertGetId([
        'tenant_id' => $this->tenant1Id,
        'name' => 'Project A1',
    ]);
    $this->project2Id = DB::table('projects')->insertGetId([
        'tenant_id' => $this->tenant2Id,
        'name' => 'Project B1',
    ]);

    DB::table('deals')->insert([
        ['tenant_id' => $this->tenant1Id, 'project_id' => $this->project1Id, 'phone' => '+79000010001', 'received_at' => now()],
        ['tenant_id' => $this->tenant1Id, 'project_id' => $this->project1Id, 'phone' => '+79000010002', 'received_at' => now()],
        ['tenant_id' => $this->tenant2Id, 'project_id' => $this->project2Id, 'phone' => '+79000020001', 'received_at' => now()],
        ['tenant_id' => $this->tenant2Id, 'project_id' => $this->project2Id, 'phone' => '+79000020002', 'received_at' => now()],
    ]);
});

test('кейс 1: SET LOCAL app.current_tenant_id изолирует SELECT в deals', function () {
    DB::statement('SET LOCAL ROLE testing_rls_user');
    DB::statement("SET LOCAL app.current_tenant_id = '{$this->tenant1Id}'");

    $allDealsVisible = DB::table('deals')->count();
    $foreignDealsVisible = DB::table('deals')->where('tenant_id', $this->tenant2Id)->count();

    expect($allDealsVisible)->toBe(2);
    expect($foreignDealsVisible)->toBe(0);
});

test('кейс 1: переключение tenant_id показывает только свои deals', function () {
    DB::statement('SET LOCAL ROLE testing_rls_user');
    DB::statement("SET LOCAL app.current_tenant_id = '{$this->tenant2Id}'");

    $allDealsVisible = DB::table('deals')->count();
    $foreignDealsVisible = DB::table('deals')->where('tenant_id', $this->tenant1Id)->count();

    expect($allDealsVisible)->toBe(2);
    expect($foreignDealsVisible)->toBe(0);
});

test('кейс 1: RLS работает на projects и users (не только deals)', function () {
    DB::statement('SET LOCAL ROLE testing_rls_user');
    DB::statement("SET LOCAL app.current_tenant_id = '{$this->tenant1Id}'");

    $projectsVisible = DB::table('projects')->count();
    $foreignProjectsVisible = DB::table('projects')->where('tenant_id', $this->tenant2Id)->count();

    expect($projectsVisible)->toBe(1);
    expect($foreignProjectsVisible)->toBe(0);
});

test('кейс 4: WITH CHECK блокирует INSERT projects с чужим tenant_id', function () {
    DB::statement('SET LOCAL ROLE testing_rls_user');
    DB::statement("SET LOCAL app.current_tenant_id = '{$this->tenant1Id}'");

    expect(fn () => DB::table('projects')->insert([
        'tenant_id' => $this->tenant2Id,
        'name' => 'Hijack attempt',
    ]))->toThrow(QueryException::class);
});
