<?php

declare(strict_types=1);

use App\Models\OutboundWebhookSubscription;
use App\Models\Project;
use App\Models\Tenant;
use App\Models\User;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Queue;
use Tests\Concerns\SharesSupplierPdo;

uses(DatabaseTransactions::class);
uses(SharesSupplierPdo::class);

/**
 * Full operational journaling pipeline integration test.
 *
 * Proves that a realistic mix of operations produces expected rows in the
 * audit/log tables:
 *   - tenant_operations_log  (via OperationsLogger)
 *   - saas_admin_audit_log   (via SaasAdminAuditLog::create)
 *   - incidents_log          (via incidents:watch-failures command)
 *
 * NB: webhook_log was dropped in schema v8.35 (legacy direct webhook removal —
 * supplier channel moved to CSV; SupplierWebhookController::logSupplierWebhook
 * now no-ops via Schema::hasTable guard). The former Step 6 asserting on that
 * table was removed accordingly.
 */
it('full operational flow produces rows in the audit tables', function (): void {
    Queue::fake();

    // ── Shared fixtures ──────────────────────────────────────────────────────
    $tenant = Tenant::factory()->withRequisites()->create();
    $user = User::factory()->create(['tenant_id' => $tenant->id]);

    DB::statement('SET app.current_tenant_id = '.$tenant->id);

    // Ensure a saas_admin_users row exists for saas_admin_audit_log FK.
    // Must be is_active=true so incidents:watch-failures can resolve the FK too.
    $adminId = DB::table('saas_admin_users')->where('email', 'opflow-stub@test.local')->value('id');
    if ($adminId === null) {
        $adminId = (int) DB::table('saas_admin_users')->insertGetId([
            'email' => 'opflow-stub@test.local',
            'full_name' => 'OpFlow Test Stub',
            'password_hash' => '$2y$04$system-stub-not-loginable',
            'role' => 'super_admin',
            'is_active' => true,
            'sso_provider' => 'local',
            'is_break_glass' => false,
        ]);
    }
    $adminId = (int) $adminId;

    // ── Step 1: Create a project → project.created in tenant_operations_log ──
    $this->actingAs($user)->postJson('/api/projects', [
        'name' => 'Опфлоу тест',
        'signal_type' => 'site',
        'signal_identifier' => 'opflow-test.ru',
        'daily_limit_target' => 30,
        'regions' => [],
        'delivery_days_mask' => 127,
    ])->assertStatus(201);

    $createdProject = Project::where('tenant_id', $tenant->id)
        ->where('signal_identifier', 'opflow-test.ru')
        ->firstOrFail();

    expect(
        DB::table('tenant_operations_log')
            ->where('tenant_id', $tenant->id)
            ->where('event', 'project.created')
            ->where('entity_id', $createdProject->id)
            ->exists()
    )->toBeTrue('project.created must be logged');

    // ── Step 2: Update the project → project.updated in tenant_operations_log ─
    $this->actingAs($user)->patchJson("/api/projects/{$createdProject->id}", [
        'daily_limit_target' => 60,
    ])->assertOk();

    $updatedRow = DB::table('tenant_operations_log')
        ->where('tenant_id', $tenant->id)
        ->where('event', 'project.updated')
        ->where('entity_id', $createdProject->id)
        ->first();

    expect($updatedRow)->not->toBeNull('project.updated must be logged');
    $after = json_decode($updatedRow->payload_after, true);
    expect((int) $after['daily_limit_target'])->toBe(60);

    // ── Step 3: Regenerate API key → api_key.regenerated in tenant_operations_log
    $this->actingAs($user)->postJson('/api/api-keys/regenerate')
        ->assertStatus(201);

    $apiKeyRow = DB::table('tenant_operations_log')
        ->where('tenant_id', $tenant->id)
        ->where('event', 'api_key.regenerated')
        ->latest('id')
        ->first();

    expect($apiKeyRow)->not->toBeNull('api_key.regenerated must be logged');
    $keyPayload = json_decode($apiKeyRow->payload_after, true);
    expect($keyPayload['key_prefix'] ?? null)->not->toBeNull();
    expect(strlen($keyPayload['key_prefix']))->toBeLessThan(20);
    expect(json_encode($keyPayload))->not->toContain('plain_key');

    // ── Step 4: Change webhook URL → webhook_settings.updated in tenant_operations_log
    $oldUrl = 'https://8.8.8.8/hook';
    OutboundWebhookSubscription::factory()->create([
        'tenant_id' => $tenant->id,
        'user_id' => $user->id,
        'target_url' => $oldUrl,
    ]);

    $newUrl = 'https://1.1.1.1/hook';
    $this->actingAs($user)->putJson('/api/tenants/me/webhook-settings', [
        'target_url' => $newUrl,
    ])->assertOk();

    $webhookSettingsRow = DB::table('tenant_operations_log')
        ->where('tenant_id', $tenant->id)
        ->where('event', 'webhook_settings.updated')
        ->latest('id')
        ->first();

    expect($webhookSettingsRow)->not->toBeNull('webhook_settings.updated must be logged');
    $wsAfter = json_decode($webhookSettingsRow->payload_after, true);
    $wsBefore = json_decode($webhookSettingsRow->payload_before, true);
    expect($wsAfter['target_url'] ?? null)->toBe($newUrl);
    expect($wsBefore['target_url'] ?? null)->toBe($oldUrl);

    // ── Step 5: Admin sets export mode → saas_admin_audit_log ────────────────
    // Reset guards so Sanctum pollution from previous actingAs calls doesn't
    // interfere with the admin endpoint (which uses a different auth context).
    app('auth')->forgetGuards();
    app('auth')->setDefaultDriver('web');

    // Seed the system_settings key the admin endpoint needs
    DB::table('system_settings')->updateOrInsert(
        ['key' => 'supplier_export_mode'],
        ['value' => 'batch', 'type' => 'string', 'updated_at' => now()],
    );

    $countBefore = DB::table('saas_admin_audit_log')->count();

    $this->actingAs(User::factory()->create())
        ->postJson('/api/admin/supplier-integration/export-mode', [
            'mode' => 'online',
            'admin_user_id' => $adminId,
        ])->assertOk();

    expect(DB::table('saas_admin_audit_log')->count())->toBe($countBefore + 1);

    $adminRow = DB::table('saas_admin_audit_log')->orderByDesc('id')->first();
    expect($adminRow->action)->toBe('supplier_integration.export_mode_set')
        ->and($adminRow->admin_user_id)->toBe($adminId);

    // ── Step 6: Failure storm → incidents_log ─────────────────────────────────
    // Insert 201 failed_webhook_jobs rows (above the 200-threshold)
    $now = now();
    $rows = [];
    for ($i = 0; $i < 201; $i++) {
        $rows[] = [
            'failed_at' => $now,
            'exception' => 'App\\Exceptions\\WebhookException: opflow-storm',
            'raw_payload' => '{}',
            'retry_count' => 0,
        ];
    }
    DB::table('failed_webhook_jobs')->insert($rows);

    $this->artisan('incidents:watch-failures')->assertSuccessful();

    $incident = DB::table('incidents_log')
        ->where('summary', 'like', '%201%')
        ->latest('id')
        ->first();

    expect($incident)->not->toBeNull('incidents:watch-failures must create an incident row');
    expect($incident->severity)->toBe('high');

    // ── Final: confirm the audit tables received rows ─────────────────────────
    expect(
        DB::table('tenant_operations_log')
            ->where('tenant_id', $tenant->id)
            ->count()
    )->toBeGreaterThanOrEqual(4, 'tenant_operations_log must have ≥4 rows for this tenant');

    expect(DB::table('saas_admin_audit_log')->count())->toBeGreaterThanOrEqual(1);
    expect(DB::table('incidents_log')->count())->toBeGreaterThanOrEqual(1);
});
