<?php

declare(strict_types=1);

use App\Models\SupplierLead;
use App\Services\LeadRegionResolver;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Http\Client\ConnectionException;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Http;
use Tests\Concerns\SharesSupplierPdo;

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

beforeEach(function (): void {
    config([
        'services.dadata.enabled' => true,
        'services.dadata.api_key' => 'k',
        'services.dadata.secret' => 's',
        'services.dadata.daily_cap_rub' => 10000,
    ]);
});

function resolverSeedImport(): int
{
    return (int) DB::table('phone_ranges_imports')->insertGetId([
        'source_url' => 'test', 'checksum_sha256' => str_repeat('b', 64),
        'status' => 'completed', 'imported_at' => now(),
    ]);
}

function resolverSeedRange(int $subject, string $region = 'Москва', int $def = 916, string $operator = 'Ростелеком'): void
{
    DB::table('phone_ranges')->insert([
        'def_code' => $def, 'from_num' => 0, 'to_num' => 9999999,
        'operator' => $operator, 'region' => $region, 'subject_code' => $subject,
        'imported_at' => now(), 'import_id' => resolverSeedImport(),
    ]);
}

function resolverLead(string $phone = '79161234567', string $tag = ''): SupplierLead
{
    return new SupplierLead([
        'phone' => $phone,
        'raw_payload' => ['tag' => $tag],
        'received_at' => now(),
    ]);
}

function fakeDadata(array $row): void
{
    Http::fake(['cleaner.dadata.ru/*' => Http::response([$row], 200)]);
}

it('dadata qc 0 returns dadata source', function (): void {
    fakeDadata(['qc' => 0, 'region' => 'Москва', 'provider' => 'МТС', 'type' => 'Мобильный']);

    $r = app(LeadRegionResolver::class)->resolve(resolverLead());

    expect($r->source)->toBe('dadata')
        ->and($r->subjectCode)->toBe(82)
        ->and($r->phoneOperator)->toBe('МТС')
        ->and($r->qc)->toBe(0)
        ->and($r->cacheHit)->toBeFalse();
});

it('dadata qc 0 ambiguous region falls to rossvyaz but keeps dadata provider', function (): void {
    fakeDadata(['qc' => 0, 'region' => 'Санкт-Петербург и область', 'provider' => 'МегаФон']);
    resolverSeedRange(subject: 83, region: 'Санкт-Петербург');

    $r = app(LeadRegionResolver::class)->resolve(resolverLead());

    expect($r->source)->toBe('rossvyaz')
        ->and($r->subjectCode)->toBe(83)
        ->and($r->phoneOperator)->toBe('МегаФон') // оператор от DaData (MNP), §3.4.1
        ->and($r->rossvyazMatched)->toBeTrue();
});

it('dadata qc 3 returns dadata with multiple flag', function (): void {
    fakeDadata(['qc' => 3, 'region' => 'Москва', 'provider' => 'МТС']);

    $r = app(LeadRegionResolver::class)->resolve(resolverLead());

    expect($r->source)->toBe('dadata')->and($r->subjectCode)->toBe(82)->and($r->qc)->toBe(3);
});

it('dadata qc 1 falls back to rossvyaz', function (): void {
    fakeDadata(['qc' => 1, 'region' => 'Москва', 'provider' => 'Билайн']);
    resolverSeedRange(subject: 82);

    $r = app(LeadRegionResolver::class)->resolve(resolverLead());

    expect($r->source)->toBe('rossvyaz')->and($r->subjectCode)->toBe(82);
});

it('dadata qc 2 falls back to tag skipping rossvyaz', function (): void {
    fakeDadata(['qc' => 2]);
    resolverSeedRange(subject: 83); // если бы Россвязь дёрнули — был бы 83

    $r = app(LeadRegionResolver::class)->resolve(resolverLead(tag: 'Москва'));

    expect($r->source)->toBe('tag')->and($r->subjectCode)->toBe(82)->and($r->rossvyazMatched)->toBeFalse();
});

it('dadata qc 7 falls back to tag skipping rossvyaz', function (): void {
    fakeDadata(['qc' => 7]);
    resolverSeedRange(subject: 83);

    $r = app(LeadRegionResolver::class)->resolve(resolverLead(tag: 'Москва'));

    expect($r->source)->toBe('tag')->and($r->subjectCode)->toBe(82);
});

it('dadata timeout falls back to rossvyaz', function (): void {
    Http::fake(fn () => throw new ConnectionException('timeout'));
    resolverSeedRange(subject: 82);

    $r = app(LeadRegionResolver::class)->resolve(resolverLead());

    expect($r->source)->toBe('rossvyaz')->and($r->subjectCode)->toBe(82);
});

it('dadata network error 5xx falls back to rossvyaz', function (): void {
    Http::fake(['cleaner.dadata.ru/*' => Http::response('err', 500)]);
    resolverSeedRange(subject: 82);

    $r = app(LeadRegionResolver::class)->resolve(resolverLead());

    expect($r->source)->toBe('rossvyaz')->and($r->subjectCode)->toBe(82);
});

it('budget cap exceeded skips dadata directly to rossvyaz', function (): void {
    config(['services.dadata.daily_cap_rub' => 0]); // canSpend() → false
    Http::fake(['cleaner.dadata.ru/*' => Http::response([['qc' => 0, 'region' => 'Москва']], 200)]);
    resolverSeedRange(subject: 82);

    $r = app(LeadRegionResolver::class)->resolve(resolverLead());

    expect($r->source)->toBe('rossvyaz')->and($r->subjectCode)->toBe(82);
    Http::assertNothingSent();
});

it('cache hit skips dadata and rossvyaz on the second call', function (): void {
    fakeDadata(['qc' => 0, 'region' => 'Москва', 'provider' => 'МТС']);
    $resolver = app(LeadRegionResolver::class);

    $first = $resolver->resolve(resolverLead());
    $second = $resolver->resolve(resolverLead());

    expect($first->cacheHit)->toBeFalse()
        ->and($second->cacheHit)->toBeTrue()
        ->and($second->subjectCode)->toBe(82);
    Http::assertSentCount(1);
});

it('invalid phone skips dadata returns tag', function (): void {
    Http::fake(['cleaner.dadata.ru/*' => Http::response([['qc' => 0]], 200)]);

    $r = app(LeadRegionResolver::class)->resolve(resolverLead(phone: '123', tag: 'Москва'));

    expect($r->source)->toBe('tag')->and($r->subjectCode)->toBe(82);
    Http::assertNothingSent();
});

it('qc 0 region null falls through to rossvyaz', function (): void {
    fakeDadata(['qc' => 0, 'region' => null, 'provider' => 'Tele2']);
    resolverSeedRange(subject: 82);

    $r = app(LeadRegionResolver::class)->resolve(resolverLead());

    expect($r->source)->toBe('rossvyaz')->and($r->subjectCode)->toBe(82)->and($r->phoneOperator)->toBe('Tele2');
});

it('unmappable dadata region falls through to rossvyaz', function (): void {
    fakeDadata(['qc' => 0, 'region' => 'Несуществующий край', 'provider' => 'МТС']);
    resolverSeedRange(subject: 82);

    $r = app(LeadRegionResolver::class)->resolve(resolverLead());

    expect($r->source)->toBe('rossvyaz')->and($r->subjectCode)->toBe(82);
});

it('all three layers fail returns unknown with null subject_code', function (): void {
    fakeDadata(['qc' => 1]); // → rossvyaz
    // no phone_ranges seeded → rossvyaz miss; tag empty → null

    $r = app(LeadRegionResolver::class)->resolve(resolverLead(tag: ''));

    expect($r->source)->toBe('unknown')->and($r->subjectCode)->toBeNull();
});

it('disabled feature flag returns tag without any dadata call', function (): void {
    config(['services.dadata.enabled' => false]);
    Http::fake(['cleaner.dadata.ru/*' => Http::response([['qc' => 0]], 200)]);

    $r = app(LeadRegionResolver::class)->resolve(resolverLead(tag: 'Москва'));

    expect($r->source)->toBe('tag')->and($r->subjectCode)->toBe(82);
    Http::assertNothingSent();
});

it('persistent idempotency: already-resolved lead skips dadata', function (): void {
    Http::fake(['cleaner.dadata.ru/*' => Http::response([['qc' => 0, 'region' => 'Москва']], 200)]);
    $lead = resolverLead();
    $lead->resolved_subject_code = 83;
    $lead->region_source = 'dadata';
    $lead->dadata_qc = 0;
    $lead->phone_operator = 'МегаФон';

    $r = app(LeadRegionResolver::class)->resolve($lead);

    expect($r->subjectCode)->toBe(83)->and($r->source)->toBe('dadata');
    Http::assertNothingSent();
});
