<?php

namespace Tests\Feature;

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 Carbon\Carbon;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class ReferralProgramTest extends TestCase
{
    use RefreshDatabase;

    public function test_referral_page_lists_direct_and_indirect_referrals(): void
    {
        $referrer = User::factory()->create([
            'referral_code' => 'REFONE01',
            'referral_program_enabled' => true,
        ]);
        $direct = User::factory()->create(['referred_by_user_id' => $referrer->id]);
        User::factory()->create(['referred_by_user_id' => $direct->id]);

        $this->actingAs($referrer)
            ->get(route('referrals.index'))
            ->assertOk()
            ->assertInertia(fn ($page) => $page
                ->component('referrals/index')
                ->where('referral.code', 'REFONE01')
                ->where('referral.direct_count', 1)
                ->where('referral.indirect_count', 1));
    }

    public function test_investment_approval_creates_direct_and_indirect_referral_bonuses(): void
    {
        InvestmentSetting::current()->update([
            'monthly_return' => 10,
            'referral_direct_percent' => 7,
            'referral_indirect_percent' => 5,
        ]);

        $admin = User::factory()->admin()->create();
        $indirectReferrer = User::factory()->create(['referral_program_enabled' => true]);
        $directReferrer = User::factory()->create([
            'referred_by_user_id' => $indirectReferrer->id,
            'referral_program_enabled' => true,
        ]);
        $investor = User::factory()->create([
            'referred_by_user_id' => $directReferrer->id,
        ]);

        /** @var Investment $investment */
        $investment = $investor->investments()->create([
            'amount' => 1000,
            'duration_months' => 4,
            'monthly_return_snapshot' => 10,
            'txid' => 'tx-referral-approval',
            'status' => InvestmentStatus::Active,
            'confirmed_at' => Carbon::parse('2099-01-01'),
            'maturity_at' => Carbon::parse('2100-01-01'),
            'confirmed_by' => $admin->id,
            'capital_balance' => 1000,
        ]);

        InvestmentTransaction::query()->create([
            'investment_id' => $investment->id,
            'type' => InvestmentTransactionType::Deposit,
            'amount' => 1000,
            'balance_after' => 1000,
        ]);

        InvestmentLedger::creditReferralBonusesOnInvestmentApproval($investment);

        $direct = round(1000 * 0.07, 2);
        $indirect = round(1000 * 0.05, 2);

        $this->assertDatabaseHas('investment_transactions', [
            'investment_id' => $investment->id,
            'beneficiary_user_id' => $directReferrer->id,
            'type' => InvestmentTransactionType::ReferralDirectBonus->value,
            'amount' => number_format($direct, 2, '.', ''),
            'referral_level' => 1,
        ]);

        $this->assertDatabaseHas('investment_transactions', [
            'investment_id' => $investment->id,
            'beneficiary_user_id' => $indirectReferrer->id,
            'type' => InvestmentTransactionType::ReferralIndirectBonus->value,
            'amount' => number_format($indirect, 2, '.', ''),
            'referral_level' => 2,
        ]);

        $this->assertSame(
            $direct,
            InvestmentLedger::totalWithdrawableForUser($directReferrer->id),
        );
        $this->assertSame(
            $indirect,
            InvestmentLedger::totalWithdrawableForUser($indirectReferrer->id),
        );
    }

    public function test_daily_accrual_does_not_create_referral_bonuses(): void
    {
        InvestmentSetting::current()->update([
            'monthly_return' => 10,
            'referral_direct_percent' => 7,
            'referral_indirect_percent' => 5,
        ]);

        $this->travelTo('2099-01-15 10:00:00');

        $admin = User::factory()->admin()->create();
        $directReferrer = User::factory()->create(['referral_program_enabled' => true]);
        $investor = User::factory()->create([
            'referred_by_user_id' => $directReferrer->id,
        ]);

        /** @var Investment $investment */
        $investment = $investor->investments()->create([
            'amount' => 1000,
            'duration_months' => 4,
            'monthly_return_snapshot' => 10,
            'txid' => 'tx-no-ref-accrual',
            'status' => InvestmentStatus::Active,
            'confirmed_at' => Carbon::parse('2099-01-01'),
            'maturity_at' => Carbon::parse('2100-01-01'),
            'confirmed_by' => $admin->id,
            'capital_balance' => 1000,
        ]);

        InvestmentTransaction::query()->create([
            'investment_id' => $investment->id,
            'type' => InvestmentTransactionType::Deposit,
            'amount' => 1000,
            'balance_after' => 1000,
        ]);

        InvestmentLedger::applyDailyAccrualForDate(Carbon::parse('2099-01-15'));

        $this->assertDatabaseMissing('investment_transactions', [
            'investment_id' => $investment->id,
            'type' => InvestmentTransactionType::ReferralDirectBonus->value,
        ]);

        $this->travelBack();
    }

    public function test_direct_referral_bonus_stops_when_earning_limit_reached(): void
    {
        InvestmentSetting::current()->update([
            'referral_direct_percent' => 7,
            'referral_direct_earning_limit' => 1.00,
            'referral_indirect_earning_limit' => null,
        ]);

        $admin = User::factory()->admin()->create();
        $directReferrer = User::factory()->create(['referral_program_enabled' => true]);
        $investor = User::factory()->create([
            'referred_by_user_id' => $directReferrer->id,
        ]);

        /** @var Investment $investment */
        $investment = $investor->investments()->create([
            'amount' => 1000,
            'duration_months' => 4,
            'monthly_return_snapshot' => 10,
            'txid' => 'tx-ref-cap-direct',
            'status' => InvestmentStatus::Active,
            'confirmed_at' => Carbon::parse('2099-02-01'),
            'maturity_at' => Carbon::parse('2100-02-01'),
            'confirmed_by' => $admin->id,
            'capital_balance' => 1000,
        ]);

        InvestmentTransaction::query()->create([
            'investment_id' => $investment->id,
            'type' => InvestmentTransactionType::Deposit,
            'amount' => 1000,
            'balance_after' => 1000,
        ]);

        InvestmentTransaction::query()->create([
            'investment_id' => $investment->id,
            'beneficiary_user_id' => $directReferrer->id,
            'type' => InvestmentTransactionType::ReferralDirectBonus,
            'amount' => 1.00,
            'balance_after' => 1.00,
            'referral_level' => 1,
        ]);

        InvestmentLedger::creditReferralBonusesOnInvestmentApproval($investment);

        $this->assertSame(
            1,
            InvestmentTransaction::query()
                ->where('beneficiary_user_id', $directReferrer->id)
                ->where('type', InvestmentTransactionType::ReferralDirectBonus->value)
                ->count(),
        );
    }

    public function test_direct_referral_bonus_credits_partial_amount_up_to_limit(): void
    {
        InvestmentSetting::current()->update([
            'referral_direct_percent' => 7,
            'referral_direct_earning_limit' => 1.00,
        ]);

        $admin = User::factory()->admin()->create();
        $directReferrer = User::factory()->create(['referral_program_enabled' => true]);
        $investor = User::factory()->create([
            'referred_by_user_id' => $directReferrer->id,
        ]);

        /** @var Investment $investment */
        $investment = $investor->investments()->create([
            'amount' => 1000,
            'duration_months' => 4,
            'monthly_return_snapshot' => 10,
            'txid' => 'tx-ref-partial-direct',
            'status' => InvestmentStatus::Active,
            'confirmed_at' => Carbon::parse('2099-03-01'),
            'maturity_at' => Carbon::parse('2100-03-01'),
            'confirmed_by' => $admin->id,
            'capital_balance' => 1000,
        ]);

        InvestmentTransaction::query()->create([
            'investment_id' => $investment->id,
            'type' => InvestmentTransactionType::Deposit,
            'amount' => 1000,
            'balance_after' => 1000,
        ]);

        InvestmentTransaction::query()->create([
            'investment_id' => $investment->id,
            'beneficiary_user_id' => $directReferrer->id,
            'type' => InvestmentTransactionType::ReferralDirectBonus,
            'amount' => 0.95,
            'balance_after' => 0.95,
            'referral_level' => 1,
        ]);

        InvestmentLedger::creditReferralBonusesOnInvestmentApproval($investment);

        $this->assertDatabaseHas('investment_transactions', [
            'beneficiary_user_id' => $directReferrer->id,
            'type' => InvestmentTransactionType::ReferralDirectBonus->value,
            'amount' => '0.05',
            'referral_level' => 1,
        ]);
    }

    public function test_indirect_referral_bonus_stops_when_earning_limit_reached(): void
    {
        InvestmentSetting::current()->update([
            'referral_indirect_percent' => 5,
            'referral_direct_earning_limit' => null,
            'referral_indirect_earning_limit' => 0.50,
        ]);

        $admin = User::factory()->admin()->create();
        $indirectReferrer = User::factory()->create(['referral_program_enabled' => true]);
        $directReferrer = User::factory()->create([
            'referred_by_user_id' => $indirectReferrer->id,
            'referral_program_enabled' => true,
        ]);
        $investor = User::factory()->create([
            'referred_by_user_id' => $directReferrer->id,
        ]);

        /** @var Investment $investment */
        $investment = $investor->investments()->create([
            'amount' => 1000,
            'duration_months' => 4,
            'monthly_return_snapshot' => 10,
            'txid' => 'tx-ref-cap-indirect',
            'status' => InvestmentStatus::Active,
            'confirmed_at' => Carbon::parse('2099-04-01'),
            'maturity_at' => Carbon::parse('2100-04-01'),
            'confirmed_by' => $admin->id,
            'capital_balance' => 1000,
        ]);

        InvestmentTransaction::query()->create([
            'investment_id' => $investment->id,
            'type' => InvestmentTransactionType::Deposit,
            'amount' => 1000,
            'balance_after' => 1000,
        ]);

        InvestmentTransaction::query()->create([
            'investment_id' => $investment->id,
            'beneficiary_user_id' => $indirectReferrer->id,
            'type' => InvestmentTransactionType::ReferralIndirectBonus,
            'amount' => 0.50,
            'balance_after' => 0.50,
            'referral_level' => 2,
        ]);

        InvestmentLedger::creditReferralBonusesOnInvestmentApproval($investment);

        $this->assertSame(
            1,
            InvestmentTransaction::query()
                ->where('beneficiary_user_id', $indirectReferrer->id)
                ->where('type', InvestmentTransactionType::ReferralIndirectBonus->value)
                ->count(),
        );
    }

    public function test_pending_referrals_can_be_applied_manually_for_legacy_investments(): void
    {
        InvestmentSetting::current()->update([
            'referral_direct_percent' => 7,
            'referral_indirect_percent' => 5,
        ]);

        $admin = User::factory()->admin()->create();
        $indirectReferrer = User::factory()->create(['referral_program_enabled' => true]);
        $directReferrer = User::factory()->create([
            'referred_by_user_id' => $indirectReferrer->id,
            'referral_program_enabled' => true,
        ]);
        $investor = User::factory()->create([
            'referred_by_user_id' => $directReferrer->id,
        ]);

        /** @var Investment $investment */
        $investment = $investor->investments()->create([
            'amount' => 1000,
            'duration_months' => 4,
            'monthly_return_snapshot' => 10,
            'txid' => 'tx-legacy-recalc',
            'status' => InvestmentStatus::Active,
            'confirmed_at' => Carbon::parse('2099-01-01'),
            'maturity_at' => Carbon::parse('2100-01-01'),
            'confirmed_by' => $admin->id,
            'capital_balance' => 1000,
        ]);

        InvestmentTransaction::query()->create([
            'investment_id' => $investment->id,
            'type' => InvestmentTransactionType::Deposit,
            'amount' => 1000,
            'balance_after' => 1000,
        ]);

        $pending = InvestmentLedger::pendingInvestmentReferralBonuses($investment);
        $this->assertCount(2, $pending);

        $this->actingAs($admin)
            ->getJson(route('admin.investments.pending-referrals', $investment))
            ->assertOk()
            ->assertJsonCount(2, 'pending');

        $credited = InvestmentLedger::applyInvestmentReferralBonuses($investment, [1, 2]);
        $this->assertSame(2, $credited);

        $this->assertDatabaseHas('investment_transactions', [
            'investment_id' => $investment->id,
            'beneficiary_user_id' => $directReferrer->id,
            'type' => InvestmentTransactionType::ReferralDirectBonus->value,
            'amount' => '70.00',
        ]);

        $this->assertSame([], InvestmentLedger::pendingInvestmentReferralBonuses($investment->fresh()));
    }

    public function test_referral_bonus_appears_only_in_referrer_movements_not_referred_investor(): void
    {
        InvestmentSetting::current()->update([
            'referral_direct_percent' => 7,
        ]);

        $admin = User::factory()->admin()->create();
        $referrer = User::factory()->create(['referral_program_enabled' => true]);
        $investor = User::factory()->create(['referred_by_user_id' => $referrer->id]);

        /** @var Investment $investment */
        $investment = $investor->investments()->create([
            'amount' => 1000,
            'duration_months' => 4,
            'monthly_return_snapshot' => 10,
            'txid' => 'tx-movements-visibility',
            'status' => InvestmentStatus::Active,
            'confirmed_at' => Carbon::parse('2099-01-01'),
            'maturity_at' => Carbon::parse('2100-01-01'),
            'confirmed_by' => $admin->id,
            'capital_balance' => 1000,
        ]);

        InvestmentTransaction::query()->create([
            'investment_id' => $investment->id,
            'type' => InvestmentTransactionType::Deposit,
            'amount' => 1000,
            'balance_after' => 1000,
        ]);

        InvestmentLedger::creditReferralBonusesOnInvestmentApproval($investment);

        $this->actingAs($referrer)
            ->get(route('balances.movements'))
            ->assertOk()
            ->assertInertia(fn ($page) => $page
                ->where('transactions.data', fn ($rows): bool => collect($rows)
                    ->contains('type', InvestmentTransactionType::ReferralDirectBonus->value)));

        $this->actingAs($investor)
            ->get(route('balances.movements'))
            ->assertOk()
            ->assertInertia(fn ($page) => $page
                ->where('transactions.data', fn ($rows): bool => collect($rows)
                    ->every(fn (array $row): bool => ! in_array($row['type'], [
                        InvestmentTransactionType::ReferralDirectBonus->value,
                        InvestmentTransactionType::ReferralIndirectBonus->value,
                    ], true))));
    }

    public function test_referral_movements_show_consolidated_running_balance(): void
    {
        InvestmentSetting::current()->update([
            'referral_direct_percent' => 7,
        ]);

        $admin = User::factory()->admin()->create();
        $referrer = User::factory()->create(['referral_program_enabled' => true]);
        $investor = User::factory()->create(['referred_by_user_id' => $referrer->id]);

        /** @var Investment $referrerInvestment */
        $referrerInvestment = $referrer->investments()->create([
            'amount' => 200,
            'duration_months' => 4,
            'monthly_return_snapshot' => 10,
            'txid' => 'tx-referrer-own',
            'status' => InvestmentStatus::Active,
            'confirmed_at' => Carbon::parse('2099-01-01'),
            'maturity_at' => Carbon::parse('2100-01-01'),
            'confirmed_by' => $admin->id,
            'capital_balance' => 200,
        ]);

        InvestmentTransaction::query()->create([
            'investment_id' => $referrerInvestment->id,
            'type' => InvestmentTransactionType::Deposit,
            'amount' => 200,
            'balance_after' => 200,
        ]);

        InvestmentTransaction::query()->create([
            'investment_id' => $referrerInvestment->id,
            'type' => InvestmentTransactionType::PeriodAccrual,
            'amount' => 10,
            'balance_after' => 210,
            'gain_period_start' => '2099-01-01',
            'gain_period_end' => '2099-01-31',
        ]);

        /** @var Investment $referredInvestment */
        $referredInvestment = $investor->investments()->create([
            'amount' => 1000,
            'duration_months' => 4,
            'monthly_return_snapshot' => 10,
            'txid' => 'tx-referred-for-bonus',
            'status' => InvestmentStatus::Active,
            'confirmed_at' => Carbon::parse('2099-01-01'),
            'maturity_at' => Carbon::parse('2100-01-01'),
            'confirmed_by' => $admin->id,
            'capital_balance' => 1000,
        ]);

        InvestmentTransaction::query()->create([
            'investment_id' => $referredInvestment->id,
            'type' => InvestmentTransactionType::Deposit,
            'amount' => 1000,
            'balance_after' => 1000,
        ]);

        InvestmentLedger::creditReferralBonusesOnInvestmentApproval($referredInvestment);

        $referralBonus = InvestmentTransaction::query()
            ->where('beneficiary_user_id', $referrer->id)
            ->where('type', InvestmentTransactionType::ReferralDirectBonus)
            ->firstOrFail();

        $bonus = round(1000 * 0.07, 2);

        $runningBalances = InvestmentLedger::runningBalanceAfterByLedgerTransactionId($referrer->id);
        $previousRunning = 0.0;
        foreach (array_keys($runningBalances) as $txnId) {
            if ($txnId === $referralBonus->id) {
                break;
            }
            $previousRunning = $runningBalances[$txnId];
        }

        $expectedRunning = round($previousRunning + $bonus, 2);

        $this->assertSame($expectedRunning, $runningBalances[(int) $referralBonus->id]);

        $this->actingAs($referrer)
            ->get(route('balances.movements'))
            ->assertOk()
            ->assertInertia(fn ($page) => $page
                ->where('transactions.data', fn ($rows): bool => collect($rows)
                    ->contains(fn (array $row): bool => $row['type'] === InvestmentTransactionType::ReferralDirectBonus->value
                        && $row['balance_after_usd'] === number_format($expectedRunning, 2, '.', ''))));
    }

    public function test_unpooled_referral_earnings_are_synced_into_withdrawable_balance(): void
    {
        InvestmentSetting::current()->update([
            'referral_direct_percent' => 7,
        ]);

        $admin = User::factory()->admin()->create();
        $referrer = User::factory()->create(['referral_program_enabled' => true]);
        $investor = User::factory()->create(['referred_by_user_id' => $referrer->id]);

        /** @var Investment $investment */
        $investment = $investor->investments()->create([
            'amount' => 1000,
            'duration_months' => 4,
            'monthly_return_snapshot' => 10,
            'txid' => 'tx-unpooled-referral',
            'status' => InvestmentStatus::Active,
            'confirmed_at' => Carbon::parse('2099-01-01'),
            'maturity_at' => Carbon::parse('2100-01-01'),
            'confirmed_by' => $admin->id,
            'capital_balance' => 1000,
        ]);

        InvestmentTransaction::query()->create([
            'investment_id' => $investment->id,
            'type' => InvestmentTransactionType::Deposit,
            'amount' => 1000,
            'balance_after' => 1000,
        ]);

        InvestmentTransaction::query()->create([
            'investment_id' => $investment->id,
            'beneficiary_user_id' => $referrer->id,
            'type' => InvestmentTransactionType::ReferralDirectBonus,
            'amount' => 70,
            'balance_after' => 70,
            'referral_level' => 1,
        ]);

        $this->assertNull(
            Investment::query()
                ->where('txid', InvestmentLedger::ADMIN_CREDIT_TXID_PREFIX.$referrer->id)
                ->first(),
        );

        $this->assertSame(70.0, InvestmentLedger::totalWithdrawableForUser($referrer->id));

        $container = Investment::query()
            ->where('txid', InvestmentLedger::ADMIN_CREDIT_TXID_PREFIX.$referrer->id)
            ->firstOrFail();

        $this->assertSame('70.00', (string) $container->capital_balance);

        // Idempotente: una segunda lectura no duplica el saldo.
        $this->assertSame(70.0, InvestmentLedger::totalWithdrawableForUser($referrer->id));
    }
}
