<?php

declare(strict_types=1);

namespace Tests\Unit\Services;

use App\Models\Client;
use App\Models\Company;
use App\Models\Credit;
use App\Models\Installment;
use App\Models\User;
use App\Services\Metrics\CollectionMetricsService;
use App\Services\PaymentManager;
use Carbon\Carbon;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Event;
use Spatie\Permission\Models\Role;
use Tests\TestCase;

class CollectionMetricsPerformanceTest extends TestCase
{
    use DatabaseTransactions;

    public function test_collector_performance_uses_at_most_5_queries(): void
    {
        $company = Company::factory()->create();
        Role::firstOrCreate(['name' => 'collector', 'guard_name' => 'web']);

        // Create 20 collectors (enough to expose N+1)
        User::factory()->count(20)->create(['company_id' => $company->id])
            ->each(fn ($u) => $u->assignRole('collector'));

        Cache::flush(); // ensure no cached result
        DB::flushQueryLog();
        DB::enableQueryLog();

        app(CollectionMetricsService::class)->getCollectorPerformanceToday($company->id);

        $queries = DB::getQueryLog();
        DB::disableQueryLog();

        $this->assertLessThanOrEqual(
            5,
            count($queries),
            sprintf(
                'getCollectorPerformanceToday() made %d queries with 20 collectors — expected ≤ 5. Queries: %s',
                count($queries),
                implode(' | ', array_column($queries, 'query'))
            )
        );
    }

    /**
     * #77 — "Collectors activos" = cobradores con cartera activa asignada
     * (definición unificada con la PWA). getTeamSummary expone ese conteo.
     */
    public function test_team_summary_counts_collectors_with_active_credits(): void
    {
        $company = Company::factory()->create();
        Role::firstOrCreate(['name' => 'collector', 'guard_name' => 'web']);
        Role::firstOrCreate(['name' => 'supervisor', 'guard_name' => 'web']);

        $supervisor = User::factory()->create(['company_id' => $company->id]);
        $supervisor->assignRole('supervisor');

        $collectorA = User::factory()->create(['company_id' => $company->id]);
        $collectorA->assignRole('collector');
        $collectorB = User::factory()->create(['company_id' => $company->id]);
        $collectorB->assignRole('collector');

        $client = Client::factory()->create(['company_id' => $company->id]);

        // A tiene un crédito activo; B no tiene cartera activa.
        Credit::factory()->create([
            'company_id' => $company->id,
            'client_id' => $client->id,
            'collector_user_id' => $collectorA->id,
            'status' => Credit::STATUS_ACTIVE,
        ]);

        Cache::flush();
        $team = app(CollectionMetricsService::class)->getTeamSummary($company->id, $supervisor->id);

        $this->assertSame(2, $team['total_collectors']);
        $this->assertSame(1, $team['collectors_with_active_credits']);
    }

    /**
     * getPerformanceAlerts debe usar queries agregadas (O(1)), no 2 por cobrador.
     */
    public function test_performance_alerts_avoids_n_plus_1(): void
    {
        $company = Company::factory()->create();
        Role::firstOrCreate(['name' => 'collector', 'guard_name' => 'web']);

        User::factory()->count(12)->create(['company_id' => $company->id])
            ->each(fn ($u) => $u->assignRole('collector'));

        Cache::flush();
        DB::flushQueryLog();
        DB::enableQueryLog();

        app(CollectionMetricsService::class)->getPerformanceAlerts($company->id);

        $count = count(DB::getQueryLog());
        DB::disableQueryLog();

        $this->assertLessThanOrEqual(6, $count, sprintf(
            'getPerformanceAlerts hizo %d queries con 12 cobradores — debe ser agregado (O(1)), no N+1.',
            $count
        ));
    }

    /**
     * "Cobradores activos hoy" se atribuye por el COBRADOR DEL CRÉDITO, no por quién
     * registró el pago. Con 2 cobradores y pagos en sus créditos registrados por un
     * tercero (supervisor), debe contar 2 — no 1 (que daría la dimensión registered_by).
     */
    public function test_active_collectors_today_attributed_by_credit_collector(): void
    {
        Event::fake();

        $company = Company::factory()->create();
        foreach (['supervisor', 'collector'] as $r) {
            Role::firstOrCreate(['name' => $r, 'guard_name' => 'web']);
        }

        $supervisor = User::factory()->create(['company_id' => $company->id]);
        $supervisor->assignRole('supervisor');

        $a = User::factory()->create(['company_id' => $company->id]);
        $a->assignRole('collector');
        $b = User::factory()->create(['company_id' => $company->id]);
        $b->assignRole('collector');

        $creditA = $this->makeCreditWithInstallment($company, $a);
        $creditB = $this->makeCreditWithInstallment($company, $b);

        // Ambos pagos de HOY los registra el supervisor, no los cobradores.
        $pm = app(PaymentManager::class);
        $pm->registerPayment(amount: 10_000, paymentDate: Carbon::today(), registeredByUserId: $supervisor->id, credit: $creditA, paymentMethod: 'cash');
        $pm->registerPayment(amount: 10_000, paymentDate: Carbon::today(), registeredByUserId: $supervisor->id, credit: $creditB, paymentMethod: 'cash');

        Cache::flush();
        $team = app(CollectionMetricsService::class)->getTeamSummary($company->id, $supervisor->id);

        $this->assertSame(2, $team['active_collectors_today']);
    }

    /** Crédito activo (hoja) con una cuota pendiente para el cobrador dado. */
    private function makeCreditWithInstallment(Company $company, User $collector): Credit
    {
        $client = Client::factory()->create(['company_id' => $company->id]);

        $credit = Credit::factory()->create([
            'company_id' => $company->id,
            'client_id' => $client->id,
            'collector_user_id' => $collector->id,
            'created_by_user_id' => $collector->id,
            'status' => Credit::STATUS_ACTIVE,
            'amount' => 100_000,
            'installments_count' => 1,
            'periodicity' => 'monthly',
            'start_date' => Carbon::today()->subDays(5),
            'due_date' => Carbon::today()->addDays(25),
        ]);

        Installment::factory()->create([
            'company_id' => $company->id,
            'credit_id' => $credit->id,
            'installment_number' => 1,
            'status' => Installment::STATUS_PENDING,
            'amount_paid' => 0,
            'total_amount' => 100_000,
            'principal_amount' => 80_000,
            'interest_amount' => 20_000,
            'principal_balance_after' => 100_000,
            'due_date' => Carbon::today()->addDays(25),
        ]);

        return $credit;
    }
}
