<?php

declare(strict_types=1);

use App\Models\BalanceTransaction;
use App\Models\Project;
use App\Models\Tenant;
use App\Models\User;
use Database\Seeders\PricingTierSeeder;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\DB;

uses(DatabaseTransactions::class);

beforeEach(function () {
    $this->seed(PricingTierSeeder::class);
    $this->tenant = Tenant::factory()->create([
        'balance_rub' => '14250.00',
        'balance_leads' => 285,
    ]);
    $this->user = User::factory()->create(['tenant_id' => $this->tenant->id]);
    $this->actingAs($this->user);
});

// ---- wallet ----

test('GET /api/billing/wallet возвращает баланс тенанта', function () {
    $this->getJson('/api/billing/wallet')
        ->assertOk()
        ->assertJsonPath('balance_rub', '14250.00');
});

test('GET /api/billing/wallet возвращает тариф, если он назначен', function () {
    $tariffId = DB::table('tariff_plans')->where('code', 'pro')->value('id');
    $this->tenant->update(['current_tariff_id' => $tariffId]);

    $response = $this->getJson('/api/billing/wallet');

    $response->assertOk()
        ->assertJsonPath('tariff.code', 'pro')
        ->assertJsonPath('tariff.name', 'Про');
    expect($response->json('tariff.features'))->toBeArray();
});

test('GET /api/billing/wallet возвращает tariff=null без назначенного тарифа', function () {
    $this->getJson('/api/billing/wallet')
        ->assertOk()
        ->assertJsonPath('tariff', null);
});

test('GET /api/billing/wallet: runway_days = null без активных проектов', function () {
    // B1-fix: без активных проектов дневной заказ = 0 → не от чего считать → null.
    $this->getJson('/api/billing/wallet')
        ->assertOk()
        ->assertJsonPath('runway_days', null);
});

test('GET /api/billing/wallet: runway_days = floor(affordable / дневной заказ проектов)', function () {
    // B1-fix (24.06.2026): считаем от дневного заказа активных проектов, не от расхода.
    DB::statement('SET app.current_tenant_id = '.$this->tenant->id);
    Project::factory()->create([
        'tenant_id' => $this->tenant->id,
        'is_active' => true,
        'daily_limit_target' => 7,
    ]);

    // Кошелёк 14250 ₽, tier1 500₽, delivered=0 → affordable = 28 лидов.
    // Дневной заказ 7 → runway = floor(28 / 7) = 4.
    expect($this->getJson('/api/billing/wallet')->json('runway_days'))->toBe(4);
});

test('GET /api/billing/wallet: runway_days = 0 при отрицательном балансе', function () {
    $this->tenant->update(['balance_rub' => '-500.00']);
    BalanceTransaction::factory()->create([
        'tenant_id' => $this->tenant->id,
        'type' => 'lead_charge',
        'amount_rub' => '-3000.00',
        'created_at' => now()->subDays(10),
    ]);

    // Баланс уже отрицательный → runway не может быть отрицательным, клампится в 0.
    expect($this->getJson('/api/billing/wallet')->json('runway_days'))->toBe(0);
});

test('GET /api/billing/wallet без auth: 401', function () {
    auth()->logout();
    $this->getJson('/api/billing/wallet')->assertStatus(401);
});

test('GET /api/billing/wallet возвращает affordable_leads + current_tier + next_tier + tiers_preview', function () {
    $this->tenant->update([
        'balance_rub' => '5000.00',
        'delivered_in_month' => 30,
    ]);

    $resp = $this->getJson('/api/billing/wallet');

    $resp->assertOk()->assertJsonStructure([
        'balance_rub',
        'affordable_leads',
        'current_tier' => ['no', 'price_rub', 'leads_left_in_tier'],
        'next_tier' => ['no', 'price_rub', 'leads_in_tier'],
        'delivered_in_month',
        'runway_days',
        'tiers_preview' => [['tier_no', 'leads_in_tier', 'price_rub']],
        'tariff',
    ]);

    // Recomputed against real PricingTierSeeder:
    // tier 1: 100 leads × 500₽; delivered=30 → 70 slots left; balance 5000 → afford 10 in tier 1.
    expect($resp->json('affordable_leads'))->toBe(10);
    expect($resp->json('current_tier.no'))->toBe(1);
    expect($resp->json('current_tier.price_rub'))->toBe('500.00');
    expect($resp->json('current_tier.leads_left_in_tier'))->toBe(70);
    expect($resp->json('next_tier.no'))->toBe(2);
    expect($resp->json('next_tier.price_rub'))->toBe('450.00');
    expect($resp->json('next_tier.leads_in_tier'))->toBe(200);
    expect($resp->json('delivered_in_month'))->toBe(30);
    expect($resp->json('tiers_preview'))->toHaveCount(7);
    expect($resp->json('tiers_preview.0'))->toMatchArray([
        'tier_no' => 1, 'leads_in_tier' => 100, 'price_rub' => '500.00',
    ]);
    expect($resp->json('tiers_preview.6'))->toMatchArray([
        'tier_no' => 7, 'leads_in_tier' => null, 'price_rub' => '250.00',
    ]);
});

test('GET /api/billing/wallet НЕ возвращает balance_leads (Billing v2 Spec A)', function () {
    $resp = $this->getJson('/api/billing/wallet');
    $resp->assertOk();
    expect($resp->json())->not->toHaveKey('balance_leads');
});

test('GET /api/billing/wallet: tariff НЕ содержит price_monthly или billing_model (Spec A унификация)', function () {
    $tariffId = DB::table('tariff_plans')->where('code', 'pro')->value('id');
    $this->tenant->update(['current_tariff_id' => $tariffId]);

    $tariff = $this->getJson('/api/billing/wallet')->json('tariff');
    expect($tariff)->not->toBeNull();
    expect($tariff)->not->toHaveKey('price_monthly');
    expect($tariff)->not->toHaveKey('billing_model');
    expect($tariff)->toHaveKeys(['code', 'name', 'features']);
});

// ---- transactions ----

test('GET /api/billing/transactions возвращает транзакции тенанта', function () {
    BalanceTransaction::factory()->count(3)->create(['tenant_id' => $this->tenant->id]);

    $response = $this->getJson('/api/billing/transactions');

    $response->assertOk();
    expect($response->json('data'))->toHaveCount(3);
    expect($response->json('meta.total'))->toBe(3);
    expect($response->json('data.0'))->toHaveKeys(['id', 'code', 'type', 'amount_rub', 'created_at']);
});

test('GET /api/billing/transactions изолирован по тенанту', function () {
    BalanceTransaction::factory()->create(['tenant_id' => $this->tenant->id]);
    $other = Tenant::factory()->create();
    BalanceTransaction::factory()->create(['tenant_id' => $other->id]);

    expect($this->getJson('/api/billing/transactions')->json('data'))->toHaveCount(1);
});

test('GET /api/billing/transactions фильтрует по type', function () {
    BalanceTransaction::factory()->create(['tenant_id' => $this->tenant->id, 'type' => 'topup']);
    BalanceTransaction::factory()->create(['tenant_id' => $this->tenant->id, 'type' => 'lead_charge', 'amount_rub' => '-50.00']);
    BalanceTransaction::factory()->create(['tenant_id' => $this->tenant->id, 'type' => 'refund', 'amount_rub' => '10.00']);

    $this->getJson('/api/billing/transactions?type=topup')
        ->assertJsonCount(1, 'data')->assertJsonPath('data.0.type', 'topup');
    $this->getJson('/api/billing/transactions?type=lead_charge')
        ->assertJsonCount(1, 'data')->assertJsonPath('data.0.type', 'lead_charge');
    // Billing v2 Spec A: 'refund' убран из whitelist — фильтр игнорируется, возвращает все 3 строки.
    $this->getJson('/api/billing/transactions?type=refund')
        ->assertJsonCount(3, 'data');
});

test('GET /api/billing/transactions: пагинация 20/страница', function () {
    BalanceTransaction::factory()->count(25)->create(['tenant_id' => $this->tenant->id]);

    expect($this->getJson('/api/billing/transactions?page=1')->json('data'))->toHaveCount(20);
    expect($this->getJson('/api/billing/transactions?page=2')->json('data'))->toHaveCount(5);
});

test('GET /api/billing/transactions без auth: 401', function () {
    auth()->logout();
    $this->getJson('/api/billing/transactions')->assertStatus(401);
});

test('GET /api/billing/transactions?type=refund — фильтр игнорируется (Spec A удалил возвраты)', function () {
    BalanceTransaction::factory()->create(['tenant_id' => $this->tenant->id, 'type' => 'topup']);
    BalanceTransaction::factory()->create(['tenant_id' => $this->tenant->id, 'type' => 'lead_charge']);

    // ?type=refund must NOT narrow the filter — falls through to "no filter"
    $resp = $this->getJson('/api/billing/transactions?type=refund');
    $resp->assertOk();
    expect($resp->json('meta.total'))->toBe(2);
});

test('GET /api/billing/transactions?type=migration — фильтр работает (новый тип из Spec A)', function () {
    BalanceTransaction::factory()->create(['tenant_id' => $this->tenant->id, 'type' => 'migration']);
    BalanceTransaction::factory()->create(['tenant_id' => $this->tenant->id, 'type' => 'topup']);

    $resp = $this->getJson('/api/billing/transactions?type=migration');
    $resp->assertOk();
    expect($resp->json('meta.total'))->toBe(1);
    expect($resp->json('data.0.type'))->toBe('migration');
});

test('GET /api/billing/transactions: display_amount_rub = "0.00" для исторических prepaid lead_charge', function () {
    // Historic prepaid: type=lead_charge, amount_rub='0.00' (deduction was в leads, не в rub)
    BalanceTransaction::create([
        'tenant_id' => $this->tenant->id,
        'type' => 'lead_charge',
        'amount_rub' => '0.00',
        'amount_leads' => -1,
        'balance_rub_after' => '14250.00',
        'balance_leads_after' => 284,
    ]);

    $resp = $this->getJson('/api/billing/transactions');
    $resp->assertOk();
    expect($resp->json('data.0.display_amount_rub'))->toBe('0.00');
    expect($resp->json('data.0.amount_rub'))->toBe('0.00');
});

test('GET /api/billing/transactions: display_amount_rub = amount_rub для новых rub-списаний', function () {
    BalanceTransaction::create([
        'tenant_id' => $this->tenant->id,
        'type' => 'lead_charge',
        'amount_rub' => '-500.00',
        'amount_leads' => null,
        'balance_rub_after' => '13750.00',
    ]);

    $resp = $this->getJson('/api/billing/transactions');
    $resp->assertOk();
    expect($resp->json('data.0.display_amount_rub'))->toBe('-500.00');
});

// ---- invoices ----

test('GET /api/billing/invoices возвращает пустой список без счетов', function () {
    $this->getJson('/api/billing/invoices')
        ->assertOk()
        ->assertJsonCount(0, 'data');
});

test('GET /api/billing/invoices возвращает счета тенанта и изолирует чужие', function () {
    $leId = DB::table('legal_entities')->insertGetId([
        'code' => 'ooo_test_'.uniqid(),
        'name' => 'ООО Тест',
        'legal_form' => 'OOO',
        'inn' => '7700000000',
        'created_at' => now(),
    ]);
    DB::table('saas_invoices')->insert([
        'tenant_id' => $this->tenant->id,
        'legal_entity_id' => $leId,
        'invoice_number' => 'СЧ-2026-00001',
        'payer_type' => 'legal',
        'amount_net' => '990.00',
        'amount_total' => '990.00',
        'status' => 'issued',
        'issued_at' => now(),
        'expires_at' => now()->addDays(5),
    ]);
    $other = Tenant::factory()->create();
    DB::table('saas_invoices')->insert([
        'tenant_id' => $other->id,
        'legal_entity_id' => $leId,
        'invoice_number' => 'СЧ-2026-00002',
        'payer_type' => 'legal',
        'amount_net' => '500.00',
        'amount_total' => '500.00',
        'status' => 'issued',
        'issued_at' => now(),
        'expires_at' => now()->addDays(5),
    ]);

    $response = $this->getJson('/api/billing/invoices');

    $response->assertOk();
    expect($response->json('data'))->toHaveCount(1);
    expect($response->json('data.0.invoice_number'))->toBe('СЧ-2026-00001');
});

test('GET /api/billing/invoices без auth: 401', function () {
    auth()->logout();
    $this->getJson('/api/billing/invoices')->assertStatus(401);
});
