<?php

declare(strict_types=1);

use App\Models\Deal;
use App\Models\LeadCharge;
use App\Models\Project;
use App\Models\Tenant;
use App\Models\User;
use Carbon\Carbon;
use Carbon\CarbonImmutable;
use Database\Seeders\PricingTierSeeder;
use Illuminate\Foundation\Testing\DatabaseTransactions;

uses(DatabaseTransactions::class);

/**
 * Вспомогательная функция: создать сделку с заданными параметрами.
 *
 * Фабрика Deal::factory() по умолчанию: received_at = now() (текущий месяц,
 * партиция deals_2026_05 существует). is_test = false, deleted_at = null.
 * Для тестовых дат subDays(1..6) — всё в мае 2026, партиция есть.
 */
function makeDashboardDeal(
    Tenant $tenant,
    Project $project,
    string $status,
    Carbon|CarbonImmutable $receivedAt,
    ?Carbon $deletedAt = null,
    bool $isTest = false,
): Deal {
    return Deal::factory()->create([
        'tenant_id' => $tenant->id,
        'project_id' => $project->id,
        'status' => $status,
        'received_at' => $receivedAt,
        'deleted_at' => $deletedAt,
        'is_test' => $isTest,
    ]);
}

/** Авторизоваться как пользователь данного тенанта (auth:sanctum + tenant). */
function actingForTenant(Tenant $tenant): void
{
    test()->actingAs(User::factory()->for($tenant)->create());
}

it('401 без авторизации', function () {
    $this->getJson('/api/dashboard/summary')->assertStatus(401);
});

it('возвращает структуру summary с range по умолчанию 7d', function () {
    $tenant = Tenant::factory()->create([
        'balance_rub' => '14250.00',
        'balance_leads' => 285,
    ]);
    actingForTenant($tenant);
    $this->getJson('/api/dashboard/summary')
        ->assertOk()
        ->assertJsonPath('range', '7d')
        ->assertJsonPath('balance.amount_rub', '14250.00')
        ->assertJsonStructure([
            'range',
            'leads_received' => ['value', 'delta_pct', 'delta_dir'],
            'conversion' => ['value', 'delta_pp', 'delta_dir'],
            'active_projects' => ['active'],
            'balance' => ['amount_rub', 'runway_days', 'runway_leads'],
            'activity' => ['points', 'labels', 'max'],
            'funnel',
            'avg_lead_cost_rub',
        ]);
});

it('leads_received считает только сделки окна, без deleted и is_test', function () {
    $tenant = Tenant::factory()->create();
    actingForTenant($tenant);
    $project = Project::factory()->create(['tenant_id' => $tenant->id]);
    // 3 живые сделки в окне 7d + 1 deleted + 1 is_test + 1 вне окна (8 дней назад)
    makeDashboardDeal($tenant, $project, 'new', now()->subDays(1));
    makeDashboardDeal($tenant, $project, 'new', now()->subDays(2));
    makeDashboardDeal($tenant, $project, 'won', now()->subDays(3));
    makeDashboardDeal($tenant, $project, 'new', now()->subDays(1), deletedAt: now());
    makeDashboardDeal($tenant, $project, 'new', now()->subDays(1), isTest: true);
    makeDashboardDeal($tenant, $project, 'new', now()->subDays(8));

    $this->getJson('/api/dashboard/summary?range=7d')
        ->assertOk()
        ->assertJsonPath('leads_received.value', 3);
});

it('conversion = доля статуса won в окне', function () {
    $tenant = Tenant::factory()->create();
    actingForTenant($tenant);
    $project = Project::factory()->create(['tenant_id' => $tenant->id]);
    makeDashboardDeal($tenant, $project, 'won', now()->subDays(1));
    makeDashboardDeal($tenant, $project, 'new', now()->subDays(1));
    makeDashboardDeal($tenant, $project, 'new', now()->subDays(1));
    makeDashboardDeal($tenant, $project, 'new', now()->subDays(1));
    // 1 won из 4 → 25.0%; PHP json_encode кодирует 25.0 как 25 (без дроби)
    $this->getJson('/api/dashboard/summary')
        ->assertOk()
        ->assertJsonPath('conversion.value', 25);
});

it('active_projects считает только is_active=true (лимита по числу проектов нет)', function () {
    $tenant = Tenant::factory()->create();
    actingForTenant($tenant);
    Project::factory()->create(['tenant_id' => $tenant->id, 'is_active' => true]);
    Project::factory()->create(['tenant_id' => $tenant->id, 'is_active' => true]);
    Project::factory()->create(['tenant_id' => $tenant->id, 'is_active' => false]);
    $this->getJson('/api/dashboard/summary')
        ->assertOk()
        ->assertJsonPath('active_projects.active', 2)
        ->assertJsonMissingPath('active_projects.limit');
});

it('funnel группирует живые сделки по статусу', function () {
    $tenant = Tenant::factory()->create();
    actingForTenant($tenant);
    $project = Project::factory()->create(['tenant_id' => $tenant->id]);
    makeDashboardDeal($tenant, $project, 'new', now()->subDays(1));
    makeDashboardDeal($tenant, $project, 'new', now()->subDays(1));
    makeDashboardDeal($tenant, $project, 'won', now()->subDays(1));
    $this->getJson('/api/dashboard/summary')
        ->assertOk()
        ->assertJsonPath('funnel.new', 2)
        ->assertJsonPath('funnel.won', 1);
});

it('activity возвращает 7 точек и 7 меток', function () {
    $tenant = Tenant::factory()->create();
    actingForTenant($tenant);
    $this->getJson('/api/dashboard/summary')
        ->assertOk()
        ->assertJsonCount(7, 'activity.points')
        ->assertJsonCount(7, 'activity.labels');
});

it('runway считается от рублёвого баланса и дневного заказа (единый источник с биллингом)', function () {
    // F3: runway_leads дашборда = affordable leads (рубли→лиды по тарифу), не balance_leads.
    // B1-fix (24.06.2026): runway_days = affordable / дневной заказ активных проектов,
    // а не от прошлого расхода. 14250₽, tier1 500₽, delivered=0 → affordable = 28 лидов.
    // Дневной заказ 7 → runway_days = floor(28 / 7) = 4. Те же числа, что в биллинге.
    $this->seed(PricingTierSeeder::class);
    $tenant = Tenant::factory()->create(['balance_rub' => '14250.00', 'balance_leads' => 285]);
    actingForTenant($tenant);
    Project::factory()->create([
        'tenant_id' => $tenant->id,
        'is_active' => true,
        'daily_limit_target' => 7,
    ]);

    $this->getJson('/api/dashboard/summary?range=today')
        ->assertOk()
        ->assertJsonPath('balance.runway_leads', 28) // от рублей, НЕ 285 (balance_leads)
        ->assertJsonPath('balance.runway_days', 4);
});

it('runway_days = null при отсутствии активных проектов B1-2 (а не 0)', function () {
    // B1-2 (UX-аудит 25.06): без активных проектов прогноз нечего считать.
    // Дашборд раньше слал 0 («хватит на 0 дней») при полном балансе, а биллинг — «∞».
    // Унифицируем: оба показывают «нет активных проектов» → API отдаёт null.
    $this->seed(PricingTierSeeder::class);
    $tenant = Tenant::factory()->create(['balance_rub' => '14250.00']);
    actingForTenant($tenant);
    // активных проектов нет (ни одного is_active=true)

    $this->getJson('/api/dashboard/summary?range=today')
        ->assertOk()
        ->assertJsonPath('balance.runway_days', null)
        ->assertJsonPath('balance.runway_leads', 28);
});

it('avg_lead_cost_rub = среднее rub-списаний окна, без prepaid и вне окна', function () {
    // Клиентская «средняя стоимость» = среднее фактически списанных rub-сумм
    // (lead_charges.price_per_lead_kopecks, charge_source='rub') за окно периода.
    // 500 + 700 + 600 = 1800 / 3 = 600 ₽. prepaid (0 ₽) и списание вне окна — не считаются.
    $tenant = Tenant::factory()->create();
    actingForTenant($tenant);
    LeadCharge::factory()->create([
        'tenant_id' => $tenant->id,
        'charge_source' => 'rub',
        'price_per_lead_kopecks' => 50000,
        'charged_at' => now()->subDays(1),
    ]);
    LeadCharge::factory()->create([
        'tenant_id' => $tenant->id,
        'charge_source' => 'rub',
        'price_per_lead_kopecks' => 70000,
        'charged_at' => now()->subDays(2),
    ]);
    LeadCharge::factory()->create([
        'tenant_id' => $tenant->id,
        'charge_source' => 'rub',
        'price_per_lead_kopecks' => 60000,
        'charged_at' => now()->subDays(3),
    ]);
    // prepaid (цена 0) — не участвует в средней
    LeadCharge::factory()->prepaid()->create([
        'tenant_id' => $tenant->id,
        'charged_at' => now()->subDays(1),
    ]);
    // rub-списание вне окна 7d (8 дней назад) — не участвует
    LeadCharge::factory()->create([
        'tenant_id' => $tenant->id,
        'charge_source' => 'rub',
        'price_per_lead_kopecks' => 100000,
        'charged_at' => now()->subDays(8),
    ]);

    $this->getJson('/api/dashboard/summary?range=7d')
        ->assertOk()
        ->assertJsonPath('avg_lead_cost_rub', 600);
});

it('avg_lead_cost_rub = null если нет rub-списаний в окне', function () {
    $tenant = Tenant::factory()->create();
    actingForTenant($tenant);
    // только prepaid — rub-списаний нет → среднего нет
    LeadCharge::factory()->prepaid()->create([
        'tenant_id' => $tenant->id,
        'charged_at' => now()->subDays(1),
    ]);

    $this->getJson('/api/dashboard/summary?range=7d')
        ->assertOk()
        ->assertJsonPath('avg_lead_cost_rub', null);
});
