<?php

namespace Tests\Unit;

use App\DTOs\ExtendWithInterestData;
use App\Enums\CreditAuditEvent;
use App\Models\Client;
use App\Models\Company;
use App\Models\Credit;
use App\Models\CreditRestructureLog;
use App\Models\Installment;
use App\Models\User;
use App\Services\Credits\CreditOperationOrchestrator;
use App\Services\Credits\CreditOperationValidator;
use Carbon\Carbon;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;

/**
 * Tests for the ExtendWithInterest operation.
 *
 * This operation allows extending a credit that has capital payments,
 * which is the correct business behavior for microcredit operations.
 */
class ExtendWithInterestOperationTest extends TestCase
{
    use DatabaseTransactions;

    private Company $company;

    private User $user;

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

        // Create a company and user for testing
        $this->company = Company::factory()->create([
            'interest_method' => 'flat_rate',
        ]);

        $this->user = User::factory()->create([
            'company_id' => $this->company->id,
        ]);

        $this->actingAs($this->user);
    }

    /**
     * Helper to create a credit with installments in a specific state.
     */
    private function createCreditWithPayments(
        int $totalInstallments = 10,
        int $paidInstallments = 6,
        float $amount = 500000,
        float $interestRate = 40,
    ): Credit {
        $totalWithInterest = $amount * (1 + $interestRate / 100);
        $installmentAmount = $totalWithInterest / $totalInstallments;

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

        $credit = Credit::create([
            'company_id' => $this->company->id,
            'client_id' => $client->id,
            'collector_user_id' => $this->user->id,
            'created_by_user_id' => $this->user->id,
            'status' => Credit::STATUS_DELAYED,
            'amount' => $amount,
            'interest_rate' => $interestRate,
            'installments_count' => $totalInstallments,
            'periodicity' => 'weekly',
            'start_date' => Carbon::now()->subWeeks($totalInstallments),
        ]);

        // Create installments
        for ($i = 1; $i <= $totalInstallments; $i++) {
            $isPaid = $i <= $paidInstallments;

            Installment::create([
                'company_id' => $this->company->id,
                'credit_id' => $credit->id,
                'installment_number' => $i,
                'status' => $isPaid ? Installment::STATUS_PAID : Installment::STATUS_PENDING,
                'due_date' => Carbon::now()->subWeeks($totalInstallments - $i),
                'paid_date' => $isPaid ? Carbon::now()->subWeeks($totalInstallments - $i) : null,
                'principal_amount' => $amount / $totalInstallments,
                'interest_amount' => ($amount * $interestRate / 100) / $totalInstallments,
                'total_amount' => $installmentAmount,
                'principal_balance_after' => $amount - ($i * $amount / $totalInstallments),
                'amount_paid' => $isPaid ? $installmentAmount : 0,
            ]);
        }

        return $credit->fresh(['installments']);
    }

    #[Test]
    public function it_calculates_additional_interest_correctly(): void
    {
        // Arrange: Create a credit like Yulisa Rodriguez's case
        // Capital: 500,000, Total: 700,000, 6 paid, 4 pending
        $credit = $this->createCreditWithPayments(
            totalInstallments: 10,
            paidInstallments: 6,
            amount: 500000,
            interestRate: 40
        );

        $remainingBalance = 280000; // 4 * 70,000
        $additionalPeriods = 1;
        $rate = 40;

        // Expected: 280,000 * 0.40 * 1 = 112,000
        $expectedAdditionalInterest = $remainingBalance * ($rate / 100) * $additionalPeriods;

        // Assert the calculation
        $this->assertEquals(112000, $expectedAdditionalInterest);
    }

    #[Test]
    public function validator_allows_credits_with_capital_payments(): void
    {
        // Arrange: Create a credit with capital payments
        $credit = $this->createCreditWithPayments(
            totalInstallments: 10,
            paidInstallments: 6
        );

        // Verify it has capital payments
        $this->assertTrue($credit->hasCapitalPayments());

        // Act: Check if validator allows extension with interest
        $validator = app(CreditOperationValidator::class);
        $canExtend = $validator->canExtendWithInterest($credit);

        // Assert: Should be allowed
        $this->assertTrue($canExtend);
    }

    #[Test]
    public function validator_blocks_normal_extension_for_credits_with_capital_payments(): void
    {
        // Arrange: Create a credit with capital payments
        $credit = $this->createCreditWithPayments(
            totalInstallments: 10,
            paidInstallments: 6
        );

        // Act: Check normal extension (should be blocked)
        $orchestrator = app(CreditOperationOrchestrator::class);
        $canExtend = $orchestrator->canExtend($credit);

        // Assert: Normal extension should be blocked
        $this->assertFalse($canExtend);
    }

    #[Test]
    public function dto_calculates_operation_type_correctly(): void
    {
        $dto = new ExtendWithInterestData(
            creditId: 1,
            newInstallmentsCount: 8,
            additionalInterestPeriods: 1,
            newInterestRate: '40',
        );

        $this->assertEquals('extension_with_interest', $dto->operationType());
        $this->assertEquals(1, $dto->additionalInterestPeriods);
        $this->assertEquals('40', $dto->newInterestRate);
    }

    #[Test]
    public function dto_defaults_to_zero_additional_periods_when_negative(): void
    {
        $dto = new ExtendWithInterestData(
            creditId: 1,
            newInstallmentsCount: 8,
            additionalInterestPeriods: -5, // Invalid negative value
        );

        // Should be normalized to 0
        $this->assertEquals(0, $dto->additionalInterestPeriods);
    }

    #[Test]
    public function audit_event_has_correct_label(): void
    {
        $event = CreditAuditEvent::CREDIT_EXTENDED_WITH_INTEREST;

        $this->assertEquals('Crédito extendido con réditos', $event->label());
        $this->assertTrue($event->isStructuralOperation());
    }

    #[Test]
    public function new_credit_has_zero_interest_rate_because_interest_is_capitalized(): void
    {
        // Arrange: Create a credit like the user's case
        // Balance: 280,000, Rate: 25%, 1 period = 70,000 interest
        // New total should be 350,000 with 0% interest on the new credit
        $credit = $this->createCreditWithPayments(
            totalInstallments: 10,
            paidInstallments: 6,
            amount: 500000,
            interestRate: 25 // 25% rate
        );

        // Calculate expected values
        // Original total: 500,000 * 1.25 = 625,000
        // Per installment: 62,500
        // Remaining: 4 * 62,500 = 250,000
        $remainingBalance = 250000;
        $additionalInterest = $remainingBalance * 0.25 * 1; // 62,500
        $expectedNewPrincipal = $remainingBalance + $additionalInterest; // 312,500
        $newInstallmentsCount = 10;
        $expectedInstallmentAmount = $expectedNewPrincipal / $newInstallmentsCount; // 31,250

        // Act: Execute extension with interest
        $dto = new ExtendWithInterestData(
            creditId: $credit->id,
            newInstallmentsCount: $newInstallmentsCount,
            additionalInterestPeriods: 1,
            newInterestRate: '25',
            periodicity: 'weekly',
            startDate: Carbon::today(),
        );

        $orchestrator = app(CreditOperationOrchestrator::class);
        $result = $orchestrator->extendWithInterest($dto);

        // Assert: New credit should have interest_rate = 0 (stored as DECIMAL → '0.00')
        $newCredit = $result->newCredit;
        $this->assertEquals(0, (float) $newCredit->interest_rate);

        // Assert: Amount should be the total (balance + capitalized interest)
        $this->assertEquals($expectedNewPrincipal, (float) $newCredit->amount);

        // Assert: Each installment total_amount should be principal / count
        // (no additional interest applied because rate is 0)
        $newCredit->load('installments');
        $firstInstallment = $newCredit->installments->first();

        // Total should equal the principal divided by installments
        // With 0% interest, total_amount = principal_amount
        $this->assertEquals($newInstallmentsCount, $newCredit->installments->count());

        // Verify no interest was added on top of the already-capitalized amount
        $totalFromInstallments = $newCredit->installments->sum('total_amount');
        $this->assertEquals($expectedNewPrincipal, $totalFromInstallments);
    }

    #[Test]
    public function restructure_log_records_correct_metadata(): void
    {
        // Arrange
        $credit = $this->createCreditWithPayments(
            totalInstallments: 10,
            paidInstallments: 6,
            amount: 500000,
            interestRate: 25
        );

        // Act
        $dto = new ExtendWithInterestData(
            creditId: $credit->id,
            newInstallmentsCount: 10,
            additionalInterestPeriods: 1,
            newInterestRate: '25',
            periodicity: 'weekly',
            startDate: Carbon::today(),
            reason: 'Test extension',
        );

        $orchestrator = app(CreditOperationOrchestrator::class);
        $result = $orchestrator->extendWithInterest($dto);

        // Assert: Check restructure log
        $log = CreditRestructureLog::where('new_credit_id', $result->newCredit->id)->first();

        $this->assertNotNull($log);
        $this->assertEquals(0, (float) $log->new_interest_rate); // New credit has 0% rate (DECIMAL → '0.00')
        $this->assertTrue($log->metadata['interest_capitalized'] ?? false);
        $this->assertEquals(25, $log->metadata['interest_rate_applied']); // Rate used to calculate additional interest
        $this->assertEquals(0, $log->metadata['new_credit_interest_rate']);
    }
}
