<?php

declare(strict_types=1);

use App\Exceptions\Supplier\SupplierAuthException;
use App\Jobs\Supplier\RefreshSupplierSessionJob;
use App\Jobs\Supplier\SyncSupplierProjectsJob;
use App\Mail\SupplierCriticalAlertMail;
use App\Models\Project;
use App\Models\SupplierProject;
use App\Models\SupplierSyncLog;
use App\Models\Tenant;
use Carbon\Carbon;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\Bus;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Mail;
use Tests\Concerns\SharesSupplierPdo;

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

beforeEach(function (): void {
    Cache::store('redis')->put('supplier:session', [
        'phpsessid' => 'sess',
        'csrf' => 'csrf',
        'refreshed_at' => now()->toIso8601String(),
    ], now()->addHours(6));

    config(['services.supplier.portal_url' => 'https://crm.bp-gr.ru']);
    config(['services.supplier.alert_email' => 'ops@liderra.test']);
});

afterEach(function (): void {
    Cache::store('redis')->forget('supplier:session');
    Carbon::setTestNow();
});

test('creates supplier_project at supplier when supplier_external_id is null', function (): void {
    $tenant = Tenant::factory()->create();
    $sp = SupplierProject::factory()->create([
        'platform' => 'B1',
        'signal_type' => 'site',
        'unique_key' => 'create-flow.example.com',
        'supplier_external_id' => null,
        'current_limit' => 0,
        'current_workdays' => [],
        'current_regions' => [],
    ]);
    Project::factory()->create([
        'tenant_id' => $tenant->id,
        'is_active' => true,
        'signal_type' => 'site',
        'signal_identifier' => 'create-flow.example.com',
        'supplier_b1_project_id' => $sp->id,
        'daily_limit_target' => 9,
        'delivery_days_mask' => 127,
        'region_mask' => 255,
        'region_mode' => 'include',
    ]);

    Http::fake([
        'crm.bp-gr.ru/admin/rt-project-save' => Http::response(['id' => 555], 200),
    ]);

    (new SyncSupplierProjectsJob)->handle();

    $sp->refresh();
    expect($sp->supplier_external_id)->toBe('555')
        ->and($sp->sync_status)->toBe('ok')
        ->and($sp->current_limit)->toBe(3);

    Http::assertSent(fn ($r) => str_ends_with($r->url(), '/admin/rt-project-save'));
});

test('updates when diff detected', function (): void {
    $tenant = Tenant::factory()->create();
    $sp = SupplierProject::factory()->create([
        'platform' => 'B1',
        'signal_type' => 'site',
        'unique_key' => 'update-flow.example.com',
        'supplier_external_id' => '12345',
        'current_limit' => 1,
        'current_workdays' => [1, 2, 3, 4, 5, 6, 7],
        'current_regions' => [],
    ]);
    Project::factory()->create([
        'tenant_id' => $tenant->id,
        'is_active' => true,
        'signal_type' => 'site',
        'signal_identifier' => 'update-flow.example.com',
        'supplier_b1_project_id' => $sp->id,
        'daily_limit_target' => 30,
        'delivery_days_mask' => 127,
        'region_mask' => 255,
        'region_mode' => 'include',
    ]);

    Http::fake([
        'crm.bp-gr.ru/admin/rt-project-update' => Http::response([], 200),
    ]);

    (new SyncSupplierProjectsJob)->handle();

    $sp->refresh();
    expect($sp->current_limit)->toBe(10)
        ->and($sp->sync_status)->toBe('ok');

    Http::assertSent(fn ($r) => str_ends_with($r->url(), '/admin/rt-project-update'));
});

test('skips when no diff between current and computed allocation', function (): void {
    $tenant = Tenant::factory()->create();
    $sp = SupplierProject::factory()->create([
        'platform' => 'B1',
        'signal_type' => 'site',
        'unique_key' => 'no-diff.example.com',
        'supplier_external_id' => '999',
        'current_limit' => 9,
        'current_workdays' => [1, 2, 3, 4, 5, 6, 7],
        'current_regions' => [],
        'sync_status' => 'ok',
    ]);
    Project::factory()->create([
        'tenant_id' => $tenant->id,
        'is_active' => true,
        'signal_type' => 'site',
        'signal_identifier' => 'no-diff.example.com',
        'supplier_b1_project_id' => $sp->id,
        'daily_limit_target' => 27,
        'delivery_days_mask' => 127,
        'region_mask' => 255,
        'region_mode' => 'include',
    ]);

    Http::fake();
    (new SyncSupplierProjectsJob)->handle();

    Http::assertNothingSent();
});

test('isolates failure: one bad supplier_project does not stop others', function (): void {
    $tenant = Tenant::factory()->create();

    $bad = SupplierProject::factory()->create([
        'platform' => 'B1',
        'signal_type' => 'site',
        'unique_key' => 'bad.example.com',
        'supplier_external_id' => null,
        'current_limit' => 0,
        'current_workdays' => [],
        'current_regions' => [],
    ]);
    $good = SupplierProject::factory()->create([
        'platform' => 'B2',
        'signal_type' => 'site',
        'unique_key' => 'good.example.com',
        'supplier_external_id' => null,
        'current_limit' => 0,
        'current_workdays' => [],
        'current_regions' => [],
    ]);

    Project::factory()->create([
        'tenant_id' => $tenant->id,
        'is_active' => true,
        'signal_type' => 'site',
        'signal_identifier' => 'bad.example.com',
        'supplier_b1_project_id' => $bad->id,
        'daily_limit_target' => 9,
        'delivery_days_mask' => 127,
        'region_mask' => 255,
        'region_mode' => 'include',
    ]);
    Project::factory()->create([
        'tenant_id' => $tenant->id,
        'is_active' => true,
        'signal_type' => 'site',
        'signal_identifier' => 'good.example.com',
        'supplier_b2_project_id' => $good->id,
        'daily_limit_target' => 9,
        'delivery_days_mask' => 127,
        'region_mask' => 255,
        'region_mode' => 'include',
    ]);

    Http::fakeSequence('crm.bp-gr.ru/admin/rt-project-save')
        ->push('bad request', 422)
        ->push(['id' => 777], 200);

    (new SyncSupplierProjectsJob)->handle();

    expect(
        SupplierSyncLog::on('pgsql_supplier')
            ->where('supplier_project_id', $bad->id)
            ->whereNotNull('error_message')
            ->exists()
    )->toBeTrue();

    expect($good->fresh()->supplier_external_id)->toBe('777');
});

test('aborts after 50 consecutive transient failures and sends alert', function (): void {
    Mail::fake();
    $tenant = Tenant::factory()->create();

    for ($i = 1; $i <= 60; $i++) {
        $sp = SupplierProject::factory()->create([
            'platform' => 'B1',
            'signal_type' => 'site',
            'unique_key' => "host{$i}.example.com",
            'supplier_external_id' => null,
            'current_limit' => 0,
            'current_workdays' => [],
            'current_regions' => [],
        ]);
        Project::factory()->create([
            'tenant_id' => $tenant->id,
            'is_active' => true,
            'signal_type' => 'site',
            'signal_identifier' => "host{$i}.example.com",
            'supplier_b1_project_id' => $sp->id,
            'daily_limit_target' => 9,
            'delivery_days_mask' => 127,
            'region_mask' => 255,
            'region_mode' => 'include',
        ]);
    }

    Http::fake(['crm.bp-gr.ru/*' => Http::response('upstream', 503)]);

    (new SyncSupplierProjectsJob)->handle();

    Mail::assertQueued(SupplierCriticalAlertMail::class, function (SupplierCriticalAlertMail $mail): bool {
        return $mail->alertType === 'mass_transient';
    });
});

test('writes supplier_sync_log row for each successful action', function (): void {
    $tenant = Tenant::factory()->create();
    $sp = SupplierProject::factory()->create([
        'platform' => 'B1',
        'signal_type' => 'site',
        'unique_key' => 'audit-log.example.com',
        'supplier_external_id' => null,
        'current_limit' => 0,
        'current_workdays' => [],
        'current_regions' => [],
    ]);
    Project::factory()->create([
        'tenant_id' => $tenant->id,
        'is_active' => true,
        'signal_type' => 'site',
        'signal_identifier' => 'audit-log.example.com',
        'supplier_b1_project_id' => $sp->id,
        'daily_limit_target' => 9,
        'delivery_days_mask' => 127,
        'region_mask' => 255,
        'region_mode' => 'include',
    ]);

    Http::fake([
        'crm.bp-gr.ru/admin/rt-project-save' => Http::response(['id' => 555], 200),
    ]);

    (new SyncSupplierProjectsJob)->handle();

    $log = SupplierSyncLog::on('pgsql_supplier')
        ->where('supplier_project_id', $sp->id)
        ->first();

    expect($log)->not->toBeNull()
        ->and($log->action)->toBe('create')
        ->and($log->http_status)->toBe(200)
        ->and($log->error_message)->toBeNull();
});

test('respects time budget by stopping at 20:55 МСК', function (): void {
    Carbon::setTestNow(Carbon::parse('2026-05-12 20:56:00', 'Europe/Moscow'));

    $tenant = Tenant::factory()->create();
    $sp = SupplierProject::factory()->create([
        'platform' => 'B1',
        'signal_type' => 'site',
        'unique_key' => 'time-budget.example.com',
        'supplier_external_id' => null,
        'current_limit' => 0,
        'current_workdays' => [],
        'current_regions' => [],
    ]);
    Project::factory()->create([
        'tenant_id' => $tenant->id,
        'is_active' => true,
        'signal_type' => 'site',
        'signal_identifier' => 'time-budget.example.com',
        'supplier_b1_project_id' => $sp->id,
        'daily_limit_target' => 9,
        'delivery_days_mask' => 127,
        'region_mask' => 255,
        'region_mode' => 'include',
    ]);

    Http::fake();
    (new SyncSupplierProjectsJob)->handle();

    Http::assertNothingSent();
});

test('sticky auth error throws and sends critical alert email', function (): void {
    Mail::fake();
    Bus::fake([RefreshSupplierSessionJob::class]);

    $tenant = Tenant::factory()->create();
    $sp = SupplierProject::factory()->create([
        'platform' => 'B1',
        'signal_type' => 'site',
        'unique_key' => 'auth-fail.example.com',
        'supplier_external_id' => null,
        'current_limit' => 0,
        'current_workdays' => [],
        'current_regions' => [],
    ]);
    Project::factory()->create([
        'tenant_id' => $tenant->id,
        'is_active' => true,
        'signal_type' => 'site',
        'signal_identifier' => 'auth-fail.example.com',
        'supplier_b1_project_id' => $sp->id,
        'daily_limit_target' => 9,
        'delivery_days_mask' => 127,
        'region_mask' => 255,
        'region_mode' => 'include',
    ]);

    Http::fake([
        'crm.bp-gr.ru/*' => Http::response('Unauthorized', 401),
    ]);

    expect(fn () => (new SyncSupplierProjectsJob)->handle())
        ->toThrow(SupplierAuthException::class);

    Mail::assertQueued(SupplierCriticalAlertMail::class, function (SupplierCriticalAlertMail $mail): bool {
        return $mail->alertType === 'sticky_auth';
    });
});
