<?php

declare(strict_types=1);

use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\DB;
use Tests\Concerns\SharesSupplierPdo;

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

function rossvyazFixture(): string
{
    return base_path('tests/Fixtures/rossvyaz/sample.csv');
}

it('dry-run parses csv, maps regions to subject_code, builds staging, does not swap', function (): void {
    $this->artisan('phone-ranges:import', ['--file' => rossvyazFixture(), '--dry-run' => true])
        ->assertSuccessful();

    // Staging построен (dry-run не свапает и не дропает staging — данные видны в той же tx).
    expect(DB::table('phone_ranges_staging')->count())->toBe(3);

    $r495 = DB::selectOne('SELECT subject_code FROM phone_ranges_staging WHERE def_code = 495');
    $r921 = DB::selectOne('SELECT subject_code FROM phone_ranges_staging WHERE def_code = 921');
    $r999 = DB::selectOne('SELECT subject_code FROM phone_ranges_staging WHERE def_code = 999');

    expect((int) $r495->subject_code)->toBe(82)   // Москва
        ->and((int) $r921->subject_code)->toBe(83) // Санкт-Петербург
        ->and($r999->subject_code)->toBeNull();    // Атлантида — не маппится

    // Живой phone_ranges не тронут (свапа не было).
    expect(DB::table('phone_ranges')->count())->toBe(0);

    // Журнал импорта: dry-run → rolled_back, несматчившийся регион в error.
    $imp = DB::table('phone_ranges_imports')->orderByDesc('id')->first();
    expect($imp->status)->toBe('rolled_back')
        ->and($imp->error)->toContain('Атлантида');
});

it('maps all matched rows and counts unmatched separately', function (): void {
    $this->artisan('phone-ranges:import', ['--file' => rossvyazFixture(), '--dry-run' => true])
        ->assertSuccessful();

    $matched = DB::table('phone_ranges_staging')->whereNotNull('subject_code')->count();
    $unmatched = DB::table('phone_ranges_staging')->whereNull('subject_code')->count();

    expect($matched)->toBe(2)->and($unmatched)->toBe(1);
});

it('skips swap when checksum matches a completed import (idempotency)', function (): void {
    $checksum = hash_file('sha256', rossvyazFixture());
    DB::table('phone_ranges_imports')->insert([
        'source_url' => 'https://rossvyaz.gov.ru/prev',
        'checksum_sha256' => $checksum,
        'status' => 'completed',
        'imported_at' => now(),
        'completed_at' => now(),
    ]);

    // Не dry-run: но checksum совпал с completed → короткое замыкание ДО свапа.
    $this->artisan('phone-ranges:import', ['--file' => rossvyazFixture()])
        ->assertSuccessful();

    expect(DB::table('phone_ranges')->count())->toBe(0); // свапа не было

    $latest = DB::table('phone_ranges_imports')->orderByDesc('id')->first();
    expect($latest->status)->toBe('rolled_back');
});

it('force flag bypasses idempotency note even with matching checksum', function (): void {
    // С --dry-run + --force: идемпотентность игнорируется, но dry-run всё равно не свапает.
    $checksum = hash_file('sha256', rossvyazFixture());
    DB::table('phone_ranges_imports')->insert([
        'source_url' => 'https://rossvyaz.gov.ru/prev',
        'checksum_sha256' => $checksum,
        'status' => 'completed',
        'imported_at' => now(),
        'completed_at' => now(),
    ]);

    $this->artisan('phone-ranges:import', ['--file' => rossvyazFixture(), '--dry-run' => true, '--force' => true])
        ->assertSuccessful();

    // --force обошёл idempotency → staging построен заново (3 строки), но dry-run не свапнул.
    expect(DB::table('phone_ranges_staging')->count())->toBe(3);
    expect(DB::table('phone_ranges')->count())->toBe(0);
});

it('normalizes real Россвязь region formats to subject_code and fills region_normalized', function (): void {
    // Форматы из реального прод-реестра (топ unmapped 02.06.2026): префикс «г. »,
    // pipe-сегмент региона, сокращение «обл.», перевёрнутая «Республика Удмуртская»,
    // и безнадёжный city-only «г.о. Тольятти». def-коды 3-значные (chk_phone_ranges_def_code 300-999).
    $this->artisan('phone-ranges:import', ['--file' => base_path('tests/Fixtures/rossvyaz/messy.csv'), '--dry-run' => true])
        ->assertSuccessful();

    $moscow = DB::selectOne('SELECT subject_code, region_normalized FROM phone_ranges_staging WHERE def_code = 495');
    $orenburg = DB::selectOne('SELECT subject_code, region_normalized FROM phone_ranges_staging WHERE def_code = 922');
    $udmurtia = DB::selectOne('SELECT subject_code, region_normalized FROM phone_ranges_staging WHERE def_code = 987');
    $togliatti = DB::selectOne('SELECT subject_code, region_normalized FROM phone_ranges_staging WHERE def_code = 902');

    expect((int) $moscow->subject_code)->toBe(82)
        ->and($moscow->region_normalized)->toBe('Москва')
        ->and((int) $orenburg->subject_code)->toBe(62)
        ->and($orenburg->region_normalized)->toBe('Оренбургская область')
        ->and((int) $udmurtia->subject_code)->toBe(21)
        ->and($udmurtia->region_normalized)->toBe('Удмуртская Республика')
        ->and($togliatti->subject_code)->toBeNull()
        ->and($togliatti->region_normalized)->toBeNull();
});

it('rebuilds staging id even after the live id default was dropped (post-swap state)', function (): void {
    // После первого atomic-swap исходная id-последовательность уничтожается
    // (DROP phone_ranges_old CASCADE), и live.id остаётся без DEFAULT. Повторный
    // импорт обязан выдать staging.id из собственной последовательности, а не упасть
    // на NOT NULL. Симулируем это, сняв default у phone_ranges.id.
    DB::connection('pgsql_supplier')->statement('ALTER TABLE phone_ranges ALTER COLUMN id DROP DEFAULT');

    $this->artisan('phone-ranges:import', ['--file' => rossvyazFixture(), '--dry-run' => true])
        ->assertSuccessful();

    expect(DB::table('phone_ranges_staging')->count())->toBe(3)
        ->and(DB::table('phone_ranges_staging')->whereNull('id')->count())->toBe(0);
});
