<?php

declare(strict_types=1);

use App\Models\Deal;
use App\Models\ImportLog;
use App\Models\ImportUnknownStatus;
use App\Models\Tenant;
use App\Models\User;
use App\Services\Import\CsvLeadsParser;
use App\Services\Import\HistoricalImportService;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\DB;
use Tests\Concerns\SharesSupplierPdo;

uses(DatabaseTransactions::class, SharesSupplierPdo::class);
// HistoricalImportService::importBatch вызывает MonthlyPartitionManager::ensureRange,
// которая делает CREATE через pgsql_supplier — нужен shared PDO, иначе DDL уйдёт
// мимо test-транзакции.

beforeEach(function (): void {
    $this->tenant = Tenant::factory()->create(['balance_leads' => 5]);
    $this->user = User::factory()->for($this->tenant)->create();
    DB::statement('SET app.current_tenant_id = '.$this->tenant->id);
    $this->service = app(HistoricalImportService::class);
});

function importLog(Tenant $tenant, User $user, bool $dryRun = false): ImportLog
{
    return ImportLog::create([
        'tenant_id' => $tenant->id,
        'user_id' => $user->id,
        'filename' => 'leads.csv',
        'file_path' => 'imports/x.csv',
        'dry_run' => $dryRun,
    ]);
}

function parseFixture(string $body): array
{
    $header = "\xEF\xBB\xBF".'id,Проект,Тег проекта,Телефон,Создано,Напоминание,Комментарий,Состояние,Имя';

    return (new CsvLeadsParser)->parse($header."\n".$body)->rows;
}

test('импортирует исторические лиды, создавая партиции под старые даты', function (): void {
    $rows = parseFixture(
        '5001,Окна,окна,79161112233,2023/07/10 10:00:00,,Комментарий,Переговоры,Иван'
    );

    $result = $this->service->import($this->tenant->id, $this->user->id, importLog($this->tenant, $this->user), $rows);

    expect($result->added)->toBe(1)
        ->and($result->updated)->toBe(0);

    $deal = Deal::query()->where('source_crm_id', 5001)->firstOrFail();
    expect($deal->status)->toBe('in_progress')
        ->and($deal->phone)->toBe('79161112233')
        ->and($deal->received_at->format('Y-m-d'))->toBe('2023-07-10');
});

test('баланс лидов не списывается, фиксируется транзакция historical_import', function (): void {
    $rows = parseFixture(
        '5002,Окна,окна,79161112234,2023/07/11 10:00:00,,,Новые,'
    );

    $this->service->import($this->tenant->id, $this->user->id, importLog($this->tenant, $this->user), $rows);

    expect($this->tenant->fresh()->balance_leads)->toBe(5); // не изменился

    $tx = DB::table('balance_transactions')
        ->where('tenant_id', $this->tenant->id)
        ->where('type', 'historical_import')
        ->first();
    expect($tx)->not->toBeNull()
        ->and((int) $tx->amount_leads)->toBe(0);
});

test('повторный импорт того же файла не создаёт дублей (idempotent UPDATE)', function (): void {
    $rows = parseFixture(
        '5003,Окна,окна,79161112235,2023/08/01 10:00:00,,Старый,Новые,'
    );
    $this->service->import($this->tenant->id, $this->user->id, importLog($this->tenant, $this->user), $rows);

    $rows2 = parseFixture(
        '5003,Окна,окна,79161112235,2023/08/01 10:00:00,,Обновлённый,Оплачено,Пётр'
    );
    $result = $this->service->import($this->tenant->id, $this->user->id, importLog($this->tenant, $this->user), $rows2);

    expect($result->added)->toBe(0)
        ->and($result->updated)->toBe(1)
        ->and(Deal::query()->where('source_crm_id', 5003)->count())->toBe(1);

    $deal = Deal::query()->where('source_crm_id', 5003)->firstOrFail();
    expect($deal->status)->toBe('won')               // §6.5 стадия 3a: status перезаписан
        ->and($deal->contact_name)->toBe('Пётр')
        ->and($deal->comment)->toBe('Обновлённый');
});

test('неизвестный статус → сделка new + запись в import_unknown_statuses', function (): void {
    $log = importLog($this->tenant, $this->user);
    $rows = parseFixture(
        "5005,Окна,окна,79161112237,2023/10/01 10:00:00,,,Архив,\n".
        '5006,Окна,окна,79161112238,2023/10/02 10:00:00,,,Архив,'
    );
    $result = $this->service->import($this->tenant->id, $this->user->id, $log, $rows);

    expect($result->unknownStatuses)->toBe(['Архив' => 2])
        ->and(Deal::query()->where('source_crm_id', 5005)->firstOrFail()->status)->toBe('new');

    $unknown = ImportUnknownStatus::query()->where('status_ru', 'Архив')->firstOrFail();
    expect($unknown->occurrences)->toBe(2)
        ->and($unknown->mapped_to_slug)->toBeNull();
});

test('resolved-маппинг tenant-а применяется к ранее неизвестному статусу', function (): void {
    ImportUnknownStatus::create([
        'tenant_id' => $this->tenant->id,
        'status_ru' => 'Архив',
        'occurrences' => 1,
        'mapped_to_slug' => 'lost',
        'resolved_at' => now(),
    ]);
    $rows = parseFixture(
        '5007,Окна,окна,79161112239,2023/11/01 10:00:00,,,Архив,'
    );
    $this->service->import($this->tenant->id, $this->user->id, importLog($this->tenant, $this->user), $rows);

    expect(Deal::query()->where('source_crm_id', 5007)->firstOrFail()->status)->toBe('lost');
});

test('dry_run не пишет сделки, но считает проекцию', function (): void {
    $rows = parseFixture(
        '5008,Окна,окна,79161112240,2023/12/01 10:00:00,,,Новые,'
    );
    $result = $this->service->import($this->tenant->id, $this->user->id, importLog($this->tenant, $this->user, dryRun: true), $rows);

    expect($result->added)->toBe(1)
        ->and(Deal::query()->where('source_crm_id', 5008)->exists())->toBeFalse();
});

test('неизвестные статусы и resolved-маппинг изолированы по тенантам', function (): void {
    // Тенант B уже резолвил «Архив» → closed и накопил 9 вхождений.
    $otherTenant = Tenant::factory()->create();
    ImportUnknownStatus::create([
        'tenant_id' => $otherTenant->id,
        'status_ru' => 'Архив',
        'occurrences' => 9,
        'mapped_to_slug' => 'lost',
        'resolved_at' => now(),
    ]);

    // Тенант A (this) импортирует лид со статусом «Архив». Под BYPASSRLS queue
    // worker'ом без явного where(tenant_id) сервис подхватил бы маппинг тенанта B
    // и инкрементировал бы его строку — это и проверяется.
    $rows = parseFixture(
        '6001,Окна,окна,79161119999,2023/06/01 10:00:00,,,Архив,'
    );
    $this->service->import($this->tenant->id, $this->user->id, importLog($this->tenant, $this->user), $rows);

    // Сделка тенанта A — 'new': маппинг тенанта B НЕ применён.
    expect(Deal::query()->where('source_crm_id', 6001)->firstOrFail()->status)->toBe('new');

    // У тенанта A — собственная запись import_unknown_statuses, occurrences=1.
    $ownRow = ImportUnknownStatus::query()
        ->where('tenant_id', $this->tenant->id)
        ->where('status_ru', 'Архив')
        ->firstOrFail();
    expect($ownRow->occurrences)->toBe(1);

    // Строка тенанта B не тронута (occurrences остался 9).
    $otherRow = ImportUnknownStatus::query()
        ->where('tenant_id', $otherTenant->id)
        ->where('status_ru', 'Архив')
        ->firstOrFail();
    expect($otherRow->occurrences)->toBe(9);
});
