<?php

declare(strict_types=1);

use App\Models\Tenant;
use App\Models\User;
use App\Services\Pd\PdErasureService;
use Carbon\CarbonImmutable;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\DB;
use Tests\Concerns\SharesSupplierPdo;

// /erase эндпоинт пишет pd_processing_log через pgsql_supplier; без SharesSupplierPdo
// строки коммитятся (откатывается только default pgsql) и ломают глобальный
// audit:verify-chains в других тестах. Также шарит committed admin/tenant/user строки.
uses(DatabaseTransactions::class, SharesSupplierPdo::class);

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

/** Создать stub saas_admin_users и вернуть его id. */
function pdStubAdminUser(string $email = 'pd-test-stub@system.local'): int
{
    $existing = DB::table('saas_admin_users')->where('email', $email)->value('id');
    if ($existing !== null) {
        return (int) $existing;
    }

    return (int) DB::table('saas_admin_users')->insertGetId([
        'email' => $email,
        'full_name' => 'PD Test Stub',
        'password_hash' => '$2y$04$system-stub-not-loginable',
        'role' => 'super_admin',
        'is_active' => false,
        'sso_provider' => 'local',
        'is_break_glass' => false,
    ]);
}

/** Создать тенант и вернуть его. */
function pdCreateTenant(): Tenant
{
    return Tenant::factory()->create([
        'subdomain' => 'pd-test-'.uniqid(),
        'organization_name' => 'PD Test Org',
        'contact_email' => 'pd-tenant@test.local',
        'status' => 'active',
    ]);
}

/** Вставить запись pd_subject_requests напрямую и вернуть id. */
function pdInsertRequest(array $attrs = []): int
{
    $defaults = [
        'received_at' => now(),
        'subject_email' => 'subject@example.com',
        'subject_phone' => null,
        'subject_full_name' => 'Test Subject',
        'request_type' => 'deletion',
        'description' => 'Test description',
        'status' => 'received',
        'tenant_id' => null,
        'processing_restricted' => false,
        // deadline_at заполняется триггером, но NOT NULL — вставим вручную
        'deadline_at' => now()->addDays(30),
    ];

    return (int) DB::connection('pgsql_supplier')
        ->table('pd_subject_requests')
        ->insertGetId(array_merge($defaults, $attrs));
}

// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------

it('index returns paginated list of pd_subject_requests', function (): void {
    $this->actingAs(User::factory()->create());

    // Вставим 2 записи
    pdInsertRequest(['request_type' => 'deletion']);
    pdInsertRequest(['request_type' => 'access', 'status' => 'completed']);

    $response = $this->getJson('/api/admin/pd-subject-requests');

    $response->assertOk();
    expect($response->json('total'))->toBeGreaterThanOrEqual(2);
    expect($response->json('data'))->toBeArray();
    $first = $response->json('data.0');
    expect($first)->toHaveKeys(['id', 'received_at', 'request_type', 'status', 'deadline_at']);
});

it('index filters by status', function (): void {
    $this->actingAs(User::factory()->create());

    pdInsertRequest(['status' => 'received']);
    pdInsertRequest(['status' => 'completed', 'request_type' => 'access']);

    $response = $this->getJson('/api/admin/pd-subject-requests?status=received');

    $response->assertOk();
    foreach ($response->json('data') as $row) {
        expect($row['status'])->toBe('received');
    }
});

it('store creates pd_subject_request with deadline_at ~+30 days from received_at', function (): void {
    $this->actingAs(User::factory()->create());

    $response = $this->postJson('/api/admin/pd-subject-requests', [
        'subject_email' => 'newsubject@example.com',
        'request_type' => 'deletion',
        'description' => 'Please delete my data.',
    ]);

    $response->assertCreated();
    $data = $response->json('data');
    expect($data['subject_email'])->toBe('newsubject@example.com');
    expect($data['request_type'])->toBe('deletion');
    expect($data['status'])->toBe('received');

    // deadline_at должен быть ~30 дней вперёд (с погрешностью ±2 дня на тест-лаги)
    $deadline = CarbonImmutable::parse($data['deadline_at']);
    $received = CarbonImmutable::parse($data['received_at']);
    // diffInDays: абсолютное значение (порядок параметров не важен с abs)
    $diff = abs($deadline->diffInDays($received));
    expect($diff)->toBeGreaterThanOrEqual(29)->toBeLessThanOrEqual(31);
});

it('store validates: at least email or phone required', function (): void {
    $this->actingAs(User::factory()->create());

    $this->postJson('/api/admin/pd-subject-requests', [
        'request_type' => 'deletion',
    ])->assertStatus(422);
});

it('store validates: request_type must be valid', function (): void {
    $this->actingAs(User::factory()->create());

    $this->postJson('/api/admin/pd-subject-requests', [
        'subject_email' => 'x@y.com',
        'request_type' => 'invalid_type',
    ])->assertStatus(422);
});

it('executeErasure anonymises user email first_name phone and writes pd_processing_log', function (): void {
    $this->actingAs(User::factory()->create());

    // Используем pgsql_supplier для всех вставок, чтобы FK-проверки работали
    // в рамках одного соединения (DatabaseTransactions оборачивает default pgsql,
    // но pgsql_supplier видит только committed данные default-соединения).
    $stubEmail = 'pd-user-stub-'.uniqid().'@system.local';
    $adminId = (int) DB::connection('pgsql_supplier')->table('saas_admin_users')->insertGetId([
        'email' => $stubEmail,
        'full_name' => 'User Test Stub',
        'password_hash' => '$2y$04$system-stub-not-loginable',
        'role' => 'super_admin',
        'is_active' => false,
        'sso_provider' => 'local',
        'is_break_glass' => false,
    ]);

    // Создаём тенант через pgsql_supplier (тот же физ. сервер/БД)
    $tenantId = (int) DB::connection('pgsql_supplier')->table('tenants')->insertGetId([
        'subdomain' => 'pd-user-test-'.uniqid(),
        'organization_name' => 'PD User Test',
        'contact_email' => 'pd-u@test.local',
        'status' => 'active',
        'balance_rub' => '0.00',
        'balance_leads' => 0,
        'is_trial' => false,
        'chargeback_unrecovered_rub' => '0.00',
        'created_at' => now(),
    ]);

    // Создаём user с email/phone субъекта
    $victimEmail = 'victim-'.uniqid().'@example.com';
    $victimPhone = '+79991234567';

    $userId = (int) DB::connection('pgsql_supplier')->table('users')->insertGetId([
        'tenant_id' => $tenantId,
        'email' => $victimEmail,
        'password_hash' => '$2y$04$test',
        'first_name' => 'Иван',
        'last_name' => 'Иванов',
        'phone' => $victimPhone,
        'is_active' => true,
        'created_at' => now(),
    ]);

    $requestId = pdInsertRequest([
        'subject_email' => $victimEmail,
        'subject_phone' => $victimPhone,
        'tenant_id' => $tenantId,
        'request_type' => 'deletion',
        'status' => 'received',
    ]);

    $response = $this->postJson("/api/admin/pd-subject-requests/{$requestId}/erase", [
        'admin_user_id' => $adminId,
    ]);

    $response->assertOk();
    expect($response->json('counts.users'))->toBe(1);

    // Проверяем анонимизацию user
    $user = DB::connection('pgsql_supplier')->table('users')->where('id', $userId)->first();
    expect($user->email)->toContain('erased-');
    expect($user->first_name)->toBe('Удалено');
    expect($user->phone)->toContain('+7000');

    // pd_processing_log должен содержать запись
    $log = DB::connection('pgsql_supplier')
        ->table('pd_processing_log')
        ->where('subject_id', $userId)
        ->where('subject_type', 'user')
        ->where('action', 'deleted')
        ->where('actor_admin_user_id', $adminId)
        ->first();
    expect($log)->not->toBeNull();
    expect($log->purpose)->toBe('152-FZ erasure');
});

it('executeErasure anonymises supplier_lead phone and raw_payload', function (): void {
    $this->actingAs(User::factory()->create());

    // Создаём stub admin через pgsql_supplier, чтобы FK pd_processing_log работал
    // независимо от DatabaseTransactions-транзакции default-соединения.
    $stubEmail = 'pd-lead-stub-'.uniqid().'@system.local';
    $adminId = (int) DB::connection('pgsql_supplier')->table('saas_admin_users')->insertGetId([
        'email' => $stubEmail,
        'full_name' => 'Lead Test Stub',
        'password_hash' => '$2y$04$system-stub-not-loginable',
        'role' => 'super_admin',
        'is_active' => false,
        'sso_provider' => 'local',
        'is_break_glass' => false,
    ]);

    $victimPhone = '+79887654321';

    $leadId = (int) DB::connection('pgsql_supplier')->table('supplier_leads')->insertGetId([
        'platform' => 'B1',
        'raw_payload' => json_encode(['phone' => $victimPhone, 'name' => 'Жертва']),
        'phone' => $victimPhone,
        'received_at' => now(),
        'source' => 'webhook',
    ]);

    $requestId = pdInsertRequest([
        'subject_phone' => $victimPhone,
        'request_type' => 'deletion',
        'status' => 'received',
    ]);

    $response = $this->postJson("/api/admin/pd-subject-requests/{$requestId}/erase", [
        'admin_user_id' => $adminId,
    ]);

    $response->assertOk();
    expect($response->json('counts.leads'))->toBeGreaterThanOrEqual(1);

    $lead = DB::connection('pgsql_supplier')->table('supplier_leads')->where('id', $leadId)->first();
    expect($lead->phone)->toBe('+7000XXXXXXX');
    $payload = json_decode($lead->raw_payload, true);
    expect($payload['erased'])->toBe(true);
});

it('executeErasure marks pd_subject_request as completed', function (): void {
    $this->actingAs(User::factory()->create());

    $adminId = pdStubAdminUser();
    $requestId = pdInsertRequest([
        'subject_email' => 'mark-completed-'.uniqid().'@example.com',
        'request_type' => 'deletion',
        'status' => 'received',
    ]);

    $this->postJson("/api/admin/pd-subject-requests/{$requestId}/erase", [
        'admin_user_id' => $adminId,
    ])->assertOk();

    $row = DB::connection('pgsql_supplier')
        ->table('pd_subject_requests')
        ->where('id', $requestId)
        ->first();

    expect($row->status)->toBe('completed');
    expect($row->completed_at)->not->toBeNull();
    expect($row->response_text)->toContain('users=');
});

it('executeErasure rejects non-deletion request_type with 422', function (): void {
    $this->actingAs(User::factory()->create());

    $requestId = pdInsertRequest([
        'subject_email' => 'access-request@example.com',
        'request_type' => 'access',
        'status' => 'received',
    ]);

    $this->postJson("/api/admin/pd-subject-requests/{$requestId}/erase")
        ->assertStatus(422);
});

it('executeErasure rejects already completed request with 422', function (): void {
    $this->actingAs(User::factory()->create());

    $requestId = pdInsertRequest([
        'subject_email' => 'already-done-'.uniqid().'@example.com',
        'request_type' => 'deletion',
        'status' => 'completed',
    ]);

    $this->postJson("/api/admin/pd-subject-requests/{$requestId}/erase")
        ->assertStatus(422);
});

it('saas-admin middleware allows request in testing env', function (): void {
    // EnsureSaasAdmin в testing-окружении пропускает всех без проверки.
    $response = $this->getJson('/api/admin/pd-subject-requests');
    $response->assertOk();
});

it('PdErasureService throws InvalidArgumentException when both email and phone are null', function (): void {
    $service = app(PdErasureService::class);

    expect(fn () => $service->eraseSubject(null, null, null, 1, null))
        ->toThrow(InvalidArgumentException::class);
});
