<?php

namespace Tests\Feature;

use App\Enums\InvestmentStatus;
use App\Enums\InvestmentTransactionType;
use App\Enums\WithdrawalRequestStatus;
use App\Models\Investment;
use App\Models\InvestmentSetting;
use App\Models\InvestmentTransaction;
use App\Models\User;
use App\Models\UserWallet;
use App\Models\WithdrawalRequest;
use App\Support\InvestmentLedger;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class WithdrawalRequestFlowTest extends TestCase
{
    use RefreshDatabase;

    /**
     * @return array{Investment, User, User, UserWallet}
     */
    protected function seededActiveInvestmentWithDeposit(): array
    {
        InvestmentSetting::current();
        $admin = User::factory()->admin()->create();
        $investor = User::factory()->create();

        /** @var Investment $investment */
        $investment = $investor->investments()->create([
            'amount' => 300,
            'duration_months' => 6,
            'monthly_return_snapshot' => 5,
            'txid' => 'tx-wr-flow',
            'status' => InvestmentStatus::Active,
            'confirmed_at' => now(),
            'confirmed_by' => $admin->id,
            'capital_balance' => 300,
        ]);

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

        $wallet = UserWallet::query()->create([
            'user_id' => $investor->id,
            'currency_network' => 'TRC20 USDT',
            'address' => 'TTestWalletWithdraw123',
            'label' => 'Main',
            'is_default' => true,
        ]);

        return [$investment, $investor, $admin, $wallet];
    }

    /**
     * @return array{Investment, Investment, User, User, UserWallet}
     */
    protected function seededTwoActiveInvestmentsWithDeposit(): array
    {
        InvestmentSetting::current();
        $admin = User::factory()->admin()->create();
        $investor = User::factory()->create();

        $makeInvestment = static function (
            User $owner,
            int $adminId,
            float $amount,
            string $txid,
        ): Investment {
            /** @var Investment $investment */
            $investment = $owner->investments()->create([
                'amount' => $amount,
                'duration_months' => 6,
                'monthly_return_snapshot' => 5,
                'txid' => $txid,
                'status' => InvestmentStatus::Active,
                'confirmed_at' => now(),
                'confirmed_by' => $adminId,
                'capital_balance' => $amount,
            ]);

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

            return $investment;
        };

        $first = $makeInvestment($investor, $admin->id, 100, 'tx-wr-split-a');
        $second = $makeInvestment($investor, $admin->id, 100, 'tx-wr-split-b');

        $wallet = UserWallet::query()->create([
            'user_id' => $investor->id,
            'currency_network' => 'TRC20 USDT',
            'address' => 'TTestPortfolioSplitWallet',
            'label' => 'Main',
            'is_default' => true,
        ]);

        return [$first, $second, $investor, $admin, $wallet];
    }

    public function test_investor_can_submit_withdrawal_request(): void
    {
        [, $investor, , $wallet] = $this->seededActiveInvestmentWithDeposit();

        $this->actingAs($investor)
            ->post(route('balances.withdrawals.store'), [
                'amount' => '100',
                'user_wallet_id' => $wallet->id,
            ])
            ->assertRedirect(route('balances.index'));

        $this->assertDatabaseHas('withdrawal_requests', [
            'investment_id' => null,
            'user_id' => $investor->id,
            'amount' => '100.00',
            'fee_total' => '0.00',
            'net_amount' => '100.00',
            'status' => WithdrawalRequestStatus::PendingEmail->value,
            'destination_address' => 'TTestWalletWithdraw123',
        ]);
    }

    public function test_investor_can_submit_requester_note_on_withdrawal(): void
    {
        [, $investor, , $wallet] = $this->seededActiveInvestmentWithDeposit();

        $this->actingAs($investor)
            ->post(route('balances.withdrawals.store'), [
                'amount' => '50',
                'user_wallet_id' => $wallet->id,
                'requester_note' => ' Preferencia: red TRC20, gracias. ',
            ])
            ->assertRedirect(route('balances.index'));

        $this->assertDatabaseHas('withdrawal_requests', [
            'investment_id' => null,
            'user_id' => $investor->id,
            'requester_note' => 'Preferencia: red TRC20, gracias.',
        ]);
    }

    public function test_investor_cannot_submit_second_pending_withdrawal_for_user(): void
    {
        [$investment, $investor, , $wallet] = $this->seededActiveInvestmentWithDeposit();

        WithdrawalRequest::query()->create([
            'investment_id' => $investment->id,
            'user_id' => $investor->id,
            'amount' => 10,
            'fee_total' => 0,
            'net_amount' => 10,
            'destination_currency_network' => 'TRC20 USDT',
            'destination_address' => 'TBlockTest',
            'destination_label' => 'Blocked',
            'status' => WithdrawalRequestStatus::PendingEmail,
            'admin_note' => null,
            'processed_by' => null,
            'processed_at' => null,
        ]);

        $this->actingAs($investor)
            ->post(route('balances.withdrawals.store'), [
                'amount' => '50',
                'user_wallet_id' => $wallet->id,
            ])
            ->assertSessionHasErrors('amount');

        $this->assertSame(
            1,
            WithdrawalRequest::query()
                ->where('user_id', $investor->id)
                ->whereIn('status', [WithdrawalRequestStatus::Pending, WithdrawalRequestStatus::PendingEmail])
                ->count(),
        );
    }

    public function test_admin_can_approve_request_and_debits_ledger(): void
    {
        [$investment, $investor, $admin] = $this->seededActiveInvestmentWithDeposit();

        /** @var WithdrawalRequest $requestRow */
        $requestRow = WithdrawalRequest::query()->create([
            'investment_id' => $investment->id,
            'user_id' => $investor->id,
            'amount' => 120,
            'status' => WithdrawalRequestStatus::Pending,
            'admin_note' => null,
            'processed_by' => null,
            'processed_at' => null,
        ]);

        $this->actingAs($admin)
            ->patch(route('admin.withdrawals.requests.approve', $requestRow), [
                'payout_txid' => 'payout-tx-abc-123',
            ])
            ->assertRedirect(route('admin.withdrawals.requests.index'));

        $investment->refresh();
        $this->assertSame('0.00', (string) $investment->capital_balance);

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

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

        $requestRow->refresh();
        $this->assertSame(WithdrawalRequestStatus::Approved, $requestRow->status);
        $this->assertSame('payout-tx-abc-123', $requestRow->payout_txid);
        $this->assertNotNull($requestRow->processed_at);

        $this->assertDatabaseHas('investment_transactions', [
            'investment_id' => $container->id,
            'type' => InvestmentTransactionType::Withdrawal->value,
            'amount' => '120.00',
            'balance_after' => '180.00',
            'withdrawal_request_id' => $requestRow->id,
        ]);
    }

    public function test_admin_can_approve_portfolio_withdrawal_from_user_balance(): void
    {
        [$invLow, $invHigh, $investor, $admin] = $this->seededTwoActiveInvestmentsWithDeposit();

        $this->assertLessThan($invHigh->id, $invLow->id);

        /** @var WithdrawalRequest $requestRow */
        $requestRow = WithdrawalRequest::query()->create([
            'investment_id' => null,
            'user_id' => $investor->id,
            'amount' => 120,
            'destination_currency_network' => 'TRC20 USDT',
            'destination_address' => 'TDestination',
            'destination_label' => 'Main',
            'status' => WithdrawalRequestStatus::Pending,
            'admin_note' => null,
            'processed_by' => null,
            'processed_at' => null,
        ]);

        $this->actingAs($admin)
            ->patch(route('admin.withdrawals.requests.approve', $requestRow), [
                'payout_txid' => 'payout-portfolio-split-1',
            ])
            ->assertRedirect(route('admin.withdrawals.requests.index'));

        $invLow->refresh();
        $invHigh->refresh();

        $this->assertSame('0.00', (string) $invLow->capital_balance);
        $this->assertSame('0.00', (string) $invHigh->capital_balance);

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

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

        $withdrawalLines = InvestmentTransaction::query()
            ->where('withdrawal_request_id', $requestRow->id)
            ->where('type', InvestmentTransactionType::Withdrawal)
            ->orderBy('id')
            ->get();

        $this->assertCount(1, $withdrawalLines);
        $this->assertSame($container->id, $withdrawalLines->first()?->investment_id);
        $this->assertSame(120.0, (float) $withdrawalLines->first()?->amount);

        $requestRow->refresh();
        $this->assertSame(WithdrawalRequestStatus::Approved, $requestRow->status);
    }

    public function test_admin_can_reject_request_without_touching_capital(): void
    {
        [$investment, $investor, $admin] = $this->seededActiveInvestmentWithDeposit();

        /** @var WithdrawalRequest $requestRow */
        $requestRow = WithdrawalRequest::query()->create([
            'investment_id' => $investment->id,
            'user_id' => $investor->id,
            'amount' => 200,
            'status' => WithdrawalRequestStatus::Pending,
            'admin_note' => null,
            'processed_by' => null,
            'processed_at' => null,
        ]);

        $this->actingAs($admin)
            ->patch(route('admin.withdrawals.requests.reject', $requestRow), [
                'admin_note' => 'KYC incompleto',
            ])
            ->assertRedirect(route('admin.withdrawals.requests.index'));

        $investment->refresh();
        $this->assertSame('300.00', (string) $investment->capital_balance);

        $requestRow->refresh();
        $this->assertSame(WithdrawalRequestStatus::Rejected, $requestRow->status);
        $this->assertSame('KYC incompleto', $requestRow->admin_note);

        $this->assertSame(
            0,
            InvestmentTransaction::query()
                ->where('investment_id', $investment->id)
                ->where('type', InvestmentTransactionType::Withdrawal)
                ->count(),
        );
    }

    public function test_admin_cannot_approve_without_payout_txid(): void
    {
        [$investment, $investor, $admin] = $this->seededActiveInvestmentWithDeposit();

        /** @var WithdrawalRequest $requestRow */
        $requestRow = WithdrawalRequest::query()->create([
            'investment_id' => $investment->id,
            'user_id' => $investor->id,
            'amount' => 50,
            'status' => WithdrawalRequestStatus::Pending,
            'admin_note' => null,
            'processed_by' => null,
            'processed_at' => null,
        ]);

        $this->actingAs($admin)
            ->patch(route('admin.withdrawals.requests.approve', $requestRow), [])
            ->assertSessionHasErrors('payout_txid');

        $requestRow->refresh();
        $this->assertSame(WithdrawalRequestStatus::Pending, $requestRow->status);
    }

    public function test_guest_redirects_investor_balance_routes(): void
    {
        $this->get('/balances')->assertRedirect(route('login'));
        $this->get('/balances/movimientos')->assertRedirect(route('login'));
        $this->get('/balances/billeteras')->assertRedirect(route('login'));
        $this->post(route('balances.withdrawals.store'), [])->assertRedirect(route('login'));
    }

    public function test_guest_redirects_when_opening_balances_admin_page(): void
    {
        $this->get('/admin/balances')
            ->assertRedirect(route('login'));
    }

    public function test_admin_balances_route_redirects_to_unified_investments(): void
    {
        InvestmentSetting::current();
        $admin = User::factory()->admin()->create();

        $this->actingAs($admin)
            ->get('/admin/balances')
            ->assertRedirect(route('admin.investments.index', ['tab' => 'active']));
    }
}
