<?php

declare(strict_types=1);

namespace Tests\Unit\Services\Metrics;

use App\Models\Client;
use App\Models\Company;
use App\Models\Credit;
use App\Models\Installment;
use App\Models\Payment;
use App\Models\User;
use App\Services\Dashboard\DashboardCacheService;
use App\Services\Metrics\PortfolioMetricsService;
use Carbon\Carbon;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\Cache;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;

class PortfolioMetricsServiceTest extends TestCase
{
    use DatabaseTransactions;

    private PortfolioMetricsService $service;

    private Company $company;

    private User $collector;

    private Client $client;

    protected function setUp(): void
    {
        parent::setUp();

        // Usar array cache para tests
        Cache::flush();

        $this->service = new PortfolioMetricsService(
            new DashboardCacheService
        );

        // Crear datos de prueba
        $this->company = Company::factory()->create();
        $this->collector = User::factory()->create(['company_id' => $this->company->id]);
        $this->client = Client::factory()->create(['company_id' => $this->company->id]);
    }

    #[Test]
    public function it_calculates_portfolio_summary()
    {
        // Crear crédito activo
        $credit = $this->createCredit(100000, 'active');
        $this->createInstallments($credit, 10, 10000);

        $summary = $this->service->getSummary($this->company->id);

        $this->assertArrayHasKey('active_credits_count', $summary);
        $this->assertArrayHasKey('active_portfolio_balance', $summary);
        $this->assertArrayHasKey('total_disbursed', $summary);
        $this->assertArrayHasKey('credits_by_status', $summary);

        $this->assertEquals(1, $summary['active_credits_count']);
        $this->assertEquals(100000, $summary['total_disbursed']);
    }

    #[Test]
    public function it_excludes_parent_credits_from_count()
    {
        // Crear crédito padre (tiene hijos)
        $parentCredit = $this->createCredit(100000, 'extended');

        // Crear crédito hijo
        $childCredit = $this->createCredit(120000, 'active', $parentCredit->id);
        $this->createInstallments($childCredit, 10, 12000);

        $summary = $this->service->getSummary($this->company->id);

        // Solo debe contar el crédito hijo (leaf credit)
        $this->assertEquals(1, $summary['active_credits_count']);
    }

    #[Test]
    public function it_calculates_active_portfolio_balance()
    {
        $credit = $this->createCredit(100000, 'active');
        $this->createInstallments($credit, 4, 27500); // 110000 total (con interés)

        // Pagar una cuota
        $installment = $credit->installments()->first();
        $installment->update([
            'status' => Installment::STATUS_PAID,
            'amount_paid' => $installment->total_amount,
            'principal_balance_after' => 0,
        ]);

        $balance = $this->service->getActivePortfolioBalance($this->company->id);

        // Balance = 3 cuotas restantes × 27500
        $this->assertEquals(82500, $balance);
    }

    #[Test]
    public function it_calculates_credits_by_status()
    {
        // getCreditsByStatus() hace JOIN con installments — los créditos necesitan cuotas
        $c1 = $this->createCredit(100000, 'active');
        $this->createInstallments($c1, 4, 27500);
        $c2 = $this->createCredit(100000, 'active');
        $this->createInstallments($c2, 4, 27500);
        $c3 = $this->createCredit(100000, 'delayed');
        $this->createInstallments($c3, 4, 27500);
        $c4 = $this->createCredit(100000, 'overdue');
        $this->createInstallments($c4, 4, 27500);
        $this->createCredit(100000, 'paid'); // No debe contar (status inactivo)

        $byStatus = $this->service->getCreditsByStatus($this->company->id);

        $this->assertEquals(2, $byStatus['active']['count']);
        $this->assertEquals(1, $byStatus['delayed']['count']);
        $this->assertEquals(1, $byStatus['overdue']['count']);
    }

    #[Test]
    public function it_calculates_collected_today()
    {
        $credit = $this->createCredit(100000, 'active');
        $this->createInstallments($credit, 4, 27500);

        // Pago de hoy
        Payment::create([
            'company_id' => $this->company->id,
            'credit_id' => $credit->id,
            'registered_by_user_id' => $this->collector->id,
            'amount' => 27500,
            'applied_amount' => 27500,
            'payment_date' => now()->toDateString(),
            'payment_method' => 'cash',
            'voided' => false,
        ]);

        // Pago de ayer (no debe contar)
        Payment::create([
            'company_id' => $this->company->id,
            'credit_id' => $credit->id,
            'registered_by_user_id' => $this->collector->id,
            'amount' => 27500,
            'applied_amount' => 27500,
            'payment_date' => now()->subDay()->toDateString(),
            'payment_method' => 'cash',
            'voided' => false,
        ]);

        $collectedToday = $this->service->getCollectedToday($this->company->id);

        $this->assertEquals(27500, $collectedToday);
    }

    #[Test]
    public function it_calculates_portfolio_composition()
    {
        // Crédito al día
        $credit1 = $this->createCredit(100000, 'active');
        $this->createInstallments($credit1, 4, 27500, now()->addDays(7)); // Vence en 7 días

        // Crédito con mora leve (10 días)
        $credit2 = $this->createCredit(100000, 'delayed');
        $this->createInstallments($credit2, 4, 27500, now()->subDays(10)); // Venció hace 10 días

        // Crédito con mora grave (45 días)
        $credit3 = $this->createCredit(100000, 'overdue');
        $this->createInstallments($credit3, 4, 27500, now()->subDays(45)); // Venció hace 45 días

        $composition = $this->service->getPortfolioComposition($this->company->id);

        $this->assertArrayHasKey('current', $composition);
        $this->assertArrayHasKey('early_delinquency', $composition);
        $this->assertArrayHasKey('late_delinquency', $composition);
        $this->assertArrayHasKey('severe_delinquency', $composition);
    }

    #[Test]
    public function it_calculates_metrics_by_collector()
    {
        $collector1 = User::factory()->create(['company_id' => $this->company->id]);
        $collector2 = User::factory()->create(['company_id' => $this->company->id]);

        // Créditos para collector 1
        $credit1 = $this->createCredit(100000, 'active', null, $collector1->id);
        $this->createInstallments($credit1, 4, 27500);

        // Créditos para collector 2
        $credit2 = $this->createCredit(200000, 'active', null, $collector2->id);
        $this->createInstallments($credit2, 4, 55000);

        $metrics = $this->service->getMetricsByCollector($this->company->id);

        $this->assertCount(2, $metrics);
        $this->assertEquals(1, $metrics->firstWhere('collector_id', $collector1->id)['credits_count']);
        $this->assertEquals(1, $metrics->firstWhere('collector_id', $collector2->id)['credits_count']);
    }

    #[Test]
    public function it_calculates_daily_collection_trend()
    {
        $credit = $this->createCredit(100000, 'active');

        // Crear pagos en los últimos 5 días
        for ($i = 4; $i >= 0; $i--) {
            Payment::create([
                'company_id' => $this->company->id,
                'credit_id' => $credit->id,
                'registered_by_user_id' => $this->collector->id,
                'amount' => 10000 + ($i * 1000),
                'applied_amount' => 10000 + ($i * 1000),
                'payment_date' => now()->subDays($i)->toDateString(),
                'payment_method' => 'cash',
                'voided' => false,
            ]);
        }

        $trend = $this->service->getDailyCollectionTrend($this->company->id, 7);

        $this->assertCount(7, $trend);
        $this->assertArrayHasKey('date', $trend->first());
        $this->assertArrayHasKey('amount', $trend->first());
    }

    #[Test]
    public function it_isolates_data_by_company()
    {
        $company2 = Company::factory()->create();
        $client2 = Client::factory()->create(['company_id' => $company2->id]);

        // Crédito en company 1
        $credit1 = $this->createCredit(100000, 'active');
        $this->createInstallments($credit1, 4, 27500);

        // Crédito en company 2
        $collector2 = User::factory()->create(['company_id' => $company2->id]);
        Credit::create([
            'company_id' => $company2->id,
            'client_id' => $client2->id,
            'collector_user_id' => $collector2->id,
            'created_by_user_id' => $collector2->id,
            'amount' => 200000,
            'interest_rate' => 10,
            'installments_count' => 4,
            'periodicity' => 'weekly',
            'start_date' => now(),
            'status' => 'active',
        ]);

        $summary1 = $this->service->getSummary($this->company->id);
        $summary2 = $this->service->getSummary($company2->id);

        $this->assertEquals(1, $summary1['active_credits_count']);
        $this->assertEquals(1, $summary2['active_credits_count']);
        $this->assertEquals(100000, $summary1['total_disbursed']);
        $this->assertEquals(200000, $summary2['total_disbursed']);
    }

    /*=========================================================
    | HELPERS
    =========================================================*/

    private function createCredit(
        float $amount,
        string $status,
        ?int $parentId = null,
        ?int $collectorId = null
    ): Credit {
        return Credit::create([
            'company_id' => $this->company->id,
            'client_id' => $this->client->id,
            'collector_user_id' => $collectorId ?? $this->collector->id,
            'created_by_user_id' => $this->collector->id,
            'parent_credit_id' => $parentId,
            'amount' => $amount,
            'interest_rate' => 10,
            'installments_count' => 4,
            'periodicity' => 'weekly',
            'start_date' => now(),
            'status' => $status,
        ]);
    }

    private function createInstallments(
        Credit $credit,
        int $count,
        float $amount,
        ?Carbon $firstDueDate = null
    ): void {
        $dueDate = $firstDueDate ?? now()->addWeek();

        for ($i = 1; $i <= $count; $i++) {
            Installment::create([
                'company_id' => $credit->company_id,
                'credit_id' => $credit->id,
                'installment_number' => $i,
                'due_date' => $dueDate->copy()->addWeeks($i - 1),
                'principal_amount' => $amount * 0.9,
                'interest_amount' => $amount * 0.1,
                'total_amount' => $amount,
                'principal_balance_after' => $amount,
                'amount_paid' => 0,
                'status' => Installment::STATUS_PENDING,
            ]);
        }
    }
}
