<?php

namespace Tests\Unit;

use App\Enums\InvestmentStatus;
use App\Enums\InvestmentTransactionType;
use App\Models\Investment;
use App\Models\InvestmentSetting;
use App\Models\InvestmentTransaction;
use App\Models\User;
use App\Support\InvestmentLedger;
use App\Support\UserInvestmentPortfolio;
use Carbon\Carbon;
use Illuminate\Foundation\Testing\RefreshDatabase;
use PHPUnit\Framework\Attributes\DataProvider;
use Tests\TestCase;

class ContractMonthLabelTest extends TestCase
{
    use RefreshDatabase;
    #[DataProvider('labelCases')]
    public function test_format_contract_month_label(string $start, string $endExclusive, string $expected): void
    {
        $this->assertSame(
            $expected,
            UserInvestmentPortfolio::formatContractMonthLabel(
                Carbon::parse($start)->startOfDay(),
                Carbon::parse($endExclusive)->startOfDay(),
            ),
        );
    }

    public static function labelCases(): array
    {
        return [
            'single day at registration' => ['2026-05-31', '2026-06-01', 'Mayo 31-31'],
            'full june' => ['2026-06-01', '2026-07-01', 'Junio 1-30'],
            'partial july' => ['2026-07-25', '2026-07-31', 'Julio 25-30'],
            'full april' => ['2026-04-01', '2026-05-01', 'Abril 1-30'],
        ];
    }

    public function test_calendar_month_intervals_match_duration_months(): void
    {
        $investment = new Investment([
            'confirmed_at' => Carbon::parse('2026-05-31'),
            'maturity_at' => Carbon::parse('2026-10-01'),
            'duration_months' => 4,
        ]);

        $intervals = UserInvestmentPortfolio::baseCalendarMonthIntervalsForInvestment($investment);

        $this->assertCount(4, $intervals);

        $labels = array_map(
            static fn (array $segment): string => UserInvestmentPortfolio::formatContractMonthLabel(
                $segment['start'],
                $segment['end'],
            ),
            $intervals,
        );

        $this->assertSame(
            ['Mayo 31-31', 'Junio 1-30', 'Julio 1-31', 'Agosto 1-31'],
            $labels,
        );
    }

    public function test_period_accredited_percent_is_not_inflated_for_single_day_cupo(): void
    {
        $principal = 200.0;
        $expected = round($principal * (3.5 / 100) / 31, 2);
        $actual = 0.23;

        $accreditedPct = round($actual / $principal * 100, 4);
        $expectedPeriodPct = round($expected / $principal * 100, 4);

        $this->assertSame(0.23, $expected);
        $this->assertEqualsWithDelta(0.115, $accreditedPct, 0.01);
        $this->assertEqualsWithDelta(0.115, $expectedPeriodPct, 0.01);
        $this->assertLessThan(1.0, $accreditedPct);
    }

    public function test_skipped_contract_month_adds_extension_and_extends_maturity(): void
    {
        $this->travelTo('2026-07-20 09:00:00');

        InvestmentSetting::current()->update(['monthly_return' => 3.5]);

        $user = User::factory()->create();
        $admin = User::factory()->admin()->create();

        /** @var Investment $investment */
        $investment = Investment::query()->create([
            'user_id' => $user->id,
            'amount' => 200,
            'duration_months' => 4,
            'monthly_return_snapshot' => 3.5,
            'txid' => 'tx-skip-extension',
            'status' => InvestmentStatus::Active,
            'confirmed_at' => Carbon::parse('2026-05-31'),
            'maturity_at' => Carbon::parse('2026-10-01'),
            'confirmed_by' => $admin->id,
            'capital_balance' => 200,
        ]);

        InvestmentTransaction::query()->create([
            'investment_id' => $investment->id,
            'type' => InvestmentTransactionType::Deposit,
            'amount' => 200,
            'balance_after' => 200,
            'platform_month_accrual_id' => null,
            'investment_month_index' => null,
            'applied_percent' => null,
        ]);

        $this->assertSame(1, InvestmentLedger::applyDailyAccrualForDate(Carbon::parse('2026-05-31')));
        $this->assertSame(1, InvestmentLedger::applyDailyAccrualForDate(Carbon::parse('2026-07-15')));

        $schedule = UserInvestmentPortfolio::contractMonthScheduleForInvestment($investment->fresh());

        $this->assertCount(5, $schedule);

        $may = collect($schedule)->firstWhere('month_index', 1);
        $this->assertNotNull($may);
        $this->assertFalse($may['is_skipped']);

        $june = collect($schedule)->firstWhere('month_index', 2);
        $this->assertNotNull($june);
        $this->assertTrue($june['is_skipped']);
        $this->assertFalse($june['is_extension']);

        $september = collect($schedule)->firstWhere('month_index', 5);
        $this->assertNotNull($september);
        $this->assertTrue($september['is_extension']);
        $this->assertFalse($september['is_skipped']);
        $this->assertSame(
            'Septiembre 1-30',
            UserInvestmentPortfolio::formatContractMonthLabel($september['start'], $september['end']),
        );

        UserInvestmentPortfolio::syncContractMaturityExtension($investment->fresh());
        $this->assertSame('2026-10-01', $investment->fresh()->maturity_at?->toDateString());
        $this->assertSame(2, UserInvestmentPortfolio::accreditedContractMonths($investment->fresh()));

        $this->travelBack();
    }
}
