<?php

namespace Tests\Feature\Admin;

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 Carbon\Carbon;
use App\Support\InvestmentLedger;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Hash;
use Tests\TestCase;

class AdminUserManagementTest extends TestCase
{
    use RefreshDatabase;

    /**
     * @return array<string, mixed>
     */
    private function payloadFor(User $target, array $overrides = []): array
    {
        return array_merge([
            'first_name' => $target->first_name,
            'last_name' => $target->last_name,
            'email' => $target->email,
            'contact_number' => $target->contact_number,
            'country' => $target->country,
            'referral_program_enabled' => $target->referral_program_enabled,
            'jabes_women_enabled' => (bool) $target->jabes_women_enabled,
            'access_blocked' => $target->blocked_at !== null,
            'referred_by_user_id' => $target->referred_by_user_id ?? '',
        ], $overrides);
    }

    public function test_blocked_user_is_logged_out_when_accessing_dashboard(): void
    {
        $user = User::factory()->create([
            'blocked_at' => now(),
        ]);

        $this->actingAs($user)
            ->get(route('dashboard'))
            ->assertRedirect(route('login'));

        $this->assertGuest();
    }

    public function test_admin_can_update_investor_profile_and_block_toggle(): void
    {
        $admin = User::factory()->admin()->create();
        $investor = User::factory()->create([
            'first_name' => 'Joe',
            'email' => 'joe@test.com',
        ]);

        $this->actingAs($admin)
            ->patch(route('admin.users.update', $investor), $this->payloadFor($investor, [
                'first_name' => 'Jane',
                'email' => 'jane@test.com',
                'access_blocked' => true,
                'referral_program_enabled' => true,
            ]))
            ->assertRedirect(route('admin.users.edit', $investor));

        $investor->refresh();

        $this->assertSame('Jane', $investor->first_name);
        $this->assertSame('jane@test.com', $investor->email);
        $this->assertTrue($investor->referral_program_enabled);
        $this->assertNotNull($investor->blocked_at);

        $this->actingAs($admin)
            ->patch(route('admin.users.update', $investor), $this->payloadFor($investor, [
                'access_blocked' => false,
                'email' => 'jane@test.com',
                'first_name' => 'Jane',
            ]))
            ->assertRedirect(route('admin.users.edit', $investor));

        $investor->refresh();
        $this->assertNull($investor->blocked_at);
    }

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

        $this->actingAs($admin)
            ->patch(route('admin.users.update', $admin), $this->payloadFor($admin, ['access_blocked' => true]))
            ->assertSessionHasErrors(['access_blocked']);
    }

    public function test_admin_cannot_suspend_another_admin_account(): void
    {
        $admin = User::factory()->admin()->create();
        $otherAdmin = User::factory()->admin()->create([
            'email' => 'other-admin@test.com',
        ]);

        $this->actingAs($admin)
            ->patch(route('admin.users.update', $otherAdmin), $this->payloadFor($otherAdmin, ['access_blocked' => true]))
            ->assertSessionHasErrors(['access_blocked']);
    }

    public function test_admin_can_set_password_for_investor(): void
    {
        $admin = User::factory()->admin()->create();
        $investor = User::factory()->create(['email' => 'inv-pw@test.com']);
        $oldHash = $investor->password;

        $this->actingAs($admin)
            ->patch(route('admin.users.update', $investor), array_merge($this->payloadFor($investor), [
                'password' => 'NewSecurePass1!',
                'password_confirmation' => 'NewSecurePass1!',
            ]))
            ->assertRedirect(route('admin.users.edit', $investor));

        $investor->refresh();
        $this->assertNotSame($oldHash, $investor->password);
        $this->assertTrue(Hash::check('NewSecurePass1!', $investor->password));
    }

    public function test_admin_cannot_set_password_for_another_admin(): void
    {
        $admin = User::factory()->admin()->create();
        $otherAdmin = User::factory()->admin()->create(['email' => 'admin2@test.com']);
        $oldHash = $otherAdmin->password;

        $this->actingAs($admin)
            ->patch(route('admin.users.update', $otherAdmin), array_merge($this->payloadFor($otherAdmin), [
                'password' => 'NewSecurePass1!',
                'password_confirmation' => 'NewSecurePass1!',
            ]))
            ->assertSessionHasErrors(['password']);

        $otherAdmin->refresh();
        $this->assertSame($oldHash, $otherAdmin->password);
    }

    public function test_admin_can_assign_and_clear_manual_referrer(): void
    {
        $admin = User::factory()->admin()->create();
        $referrer = User::factory()->create(['email' => 'ref@test.com']);
        $investor = User::factory()->create(['email' => 'inv@test.com']);

        $this->actingAs($admin)
            ->patch(route('admin.users.update', $investor), $this->payloadFor($investor, [
                'referred_by_user_id' => $referrer->id,
            ]))
            ->assertRedirect(route('admin.users.edit', $investor));

        $investor->refresh();
        $this->assertSame($referrer->id, $investor->referred_by_user_id);

        $this->actingAs($admin)
            ->patch(route('admin.users.update', $investor), $this->payloadFor($investor, [
                'referred_by_user_id' => '',
            ]))
            ->assertRedirect(route('admin.users.edit', $investor));

        $investor->refresh();
        $this->assertNull($investor->referred_by_user_id);
    }

    public function test_admin_cannot_assign_self_as_referrer(): void
    {
        $admin = User::factory()->admin()->create();
        $investor = User::factory()->create();

        $this->actingAs($admin)
            ->patch(route('admin.users.update', $investor), $this->payloadFor($investor, [
                'referred_by_user_id' => $investor->id,
            ]))
            ->assertSessionHasErrors(['referred_by_user_id']);
    }

    public function test_admin_cannot_assign_referrer_that_creates_cycle(): void
    {
        $admin = User::factory()->admin()->create();
        $u1 = User::factory()->create(['email' => 'u1@test.com']);
        $u2 = User::factory()->create([
            'email' => 'u2@test.com',
            'referred_by_user_id' => $u1->id,
        ]);

        $this->actingAs($admin)
            ->patch(route('admin.users.update', $u1), $this->payloadFor($u1, [
                'referred_by_user_id' => $u2->id,
            ]))
            ->assertSessionHasErrors(['referred_by_user_id']);
    }

    public function test_admin_can_set_referred_by_on_admin_account(): void
    {
        $admin = User::factory()->admin()->create();
        $other = User::factory()->create();

        $this->actingAs($admin)
            ->patch(route('admin.users.update', $admin), $this->payloadFor($admin, [
                'referred_by_user_id' => $other->id,
            ]))
            ->assertRedirect(route('admin.users.edit', $admin));

        $admin->refresh();
        $this->assertSame($other->id, $admin->referred_by_user_id);
    }

    public function test_admin_can_attach_and_detach_manual_direct_referral(): void
    {
        $admin = User::factory()->admin()->create();
        $sponsor = User::factory()->create(['email' => 'sponsor@test.com']);
        $referred = User::factory()->create(['email' => 'referred@test.com']);

        $this->actingAs($admin)
            ->post(route('admin.users.manual-direct-referrals.attach', $sponsor), [
                'referred_user_id' => $referred->id,
            ])
            ->assertRedirect(route('admin.users.edit', $sponsor));

        $referred->refresh();
        $this->assertSame($sponsor->id, $referred->referred_by_user_id);

        $this->actingAs($admin)
            ->delete(route('admin.users.manual-direct-referrals.detach', [
                'user' => $sponsor,
                'referred' => $referred,
            ]))
            ->assertRedirect(route('admin.users.edit', $sponsor));

        $referred->refresh();
        $this->assertNull($referred->referred_by_user_id);
    }

    public function test_admin_cannot_attach_self_as_direct_referral(): void
    {
        $admin = User::factory()->admin()->create();
        $sponsor = User::factory()->create();

        $this->actingAs($admin)
            ->post(route('admin.users.manual-direct-referrals.attach', $sponsor), [
                'referred_user_id' => $sponsor->id,
            ])
            ->assertSessionHasErrors(['referred_user_id']);
    }

    public function test_admin_cannot_attach_direct_referral_that_creates_cycle(): void
    {
        $admin = User::factory()->admin()->create();
        $u1 = User::factory()->create(['email' => 's1@test.com']);
        $u2 = User::factory()->create([
            'email' => 's2@test.com',
            'referred_by_user_id' => $u1->id,
        ]);

        $this->actingAs($admin)
            ->post(route('admin.users.manual-direct-referrals.attach', $u2), [
                'referred_user_id' => $u1->id,
            ])
            ->assertSessionHasErrors(['referred_user_id']);
    }

    public function test_admin_cannot_attach_admin_user_as_direct_referral(): void
    {
        $admin = User::factory()->admin()->create();
        $sponsor = User::factory()->create();
        $otherAdmin = User::factory()->admin()->create(['email' => 'sponsor-admin@test.com']);

        $this->actingAs($admin)
            ->post(route('admin.users.manual-direct-referrals.attach', $sponsor), [
                'referred_user_id' => $otherAdmin->id,
            ])
            ->assertSessionHasErrors(['referred_user_id']);
    }

    public function test_admin_can_attach_direct_referral_for_admin_sponsor(): void
    {
        $actingAdmin = User::factory()->admin()->create();
        $sponsorAdmin = User::factory()->admin()->create(['email' => 'pat@test.com']);
        $investor = User::factory()->create();

        $this->actingAs($actingAdmin)
            ->post(route('admin.users.manual-direct-referrals.attach', $sponsorAdmin), [
                'referred_user_id' => $investor->id,
            ])
            ->assertRedirect(route('admin.users.edit', $sponsorAdmin));

        $investor->refresh();
        $this->assertSame($sponsorAdmin->id, $investor->referred_by_user_id);
    }

    public function test_detach_direct_referral_fails_when_user_not_under_sponsor(): void
    {
        $admin = User::factory()->admin()->create();
        $sponsor = User::factory()->create();
        $other = User::factory()->create(['referred_by_user_id' => null]);

        $this->actingAs($admin)
            ->delete(route('admin.users.manual-direct-referrals.detach', [
                'user' => $sponsor,
                'referred' => $other,
            ]))
            ->assertNotFound();
    }

    public function test_admin_can_add_manual_referral_progress_without_affecting_balance(): void
    {
        $admin = User::factory()->admin()->create();
        $investor = User::factory()->create(['referral_program_enabled' => true]);

        InvestmentSetting::current()->update([
            'referral_direct_earning_limit' => 1000,
            'referral_indirect_earning_limit' => 500,
        ]);

        $this->actingAs($admin)
            ->post(route('admin.users.manual-referral-progress', $investor), [
                'level' => 1,
                'amount' => 150.25,
                'note' => 'Histórico migrado',
            ])
            ->assertRedirect(route('admin.users.edit', $investor));

        $this->assertSame(150.25, InvestmentLedger::referralProgressEarningsAtLevel($investor->id, 1));
        $this->assertSame(0.0, InvestmentLedger::referralEarningsAtLevel($investor->id, 1));
        $this->assertSame(0.0, InvestmentLedger::totalWithdrawableForUser($investor->id));
        $this->assertSame(0.0, InvestmentLedger::referralBalanceForUser($investor->id));

        $this->assertDatabaseHas('investment_transactions', [
            'beneficiary_user_id' => $investor->id,
            'type' => InvestmentTransactionType::ReferralManualDirectAdjustment->value,
            'amount' => '150.25',
            'referral_level' => 1,
            'admin_note' => 'Histórico migrado',
            'created_by_user_id' => $admin->id,
        ]);

        $this->actingAs($admin)
            ->get(route('admin.users.edit', $investor))
            ->assertOk()
            ->assertInertia(fn ($page) => $page
                ->component('admin/user-edit')
                ->where('referral_earnings_progress.direct_earned_usd', '150.25')
                ->has('manual_referral_progress_adjustments', 1));
    }

    public function test_admin_can_add_manual_balance_to_investor(): void
    {
        $admin = User::factory()->admin()->create();
        $investor = User::factory()->create();

        $this->actingAs($admin)
            ->post(route('admin.users.manual-balance', $investor), [
                'amount' => 250.5,
                'note' => 'Ajuste promocional',
            ])
            ->assertRedirect(route('admin.users.edit', $investor));

        $this->assertSame(250.5, InvestmentLedger::totalWithdrawableForUser($investor->id));

        $this->assertDatabaseHas('investment_transactions', [
            'type' => InvestmentTransactionType::AdminAdjustment->value,
            'amount' => '250.50',
            'admin_note' => 'Ajuste promocional',
            'created_by_user_id' => $admin->id,
        ]);
    }

    public function test_admin_can_view_and_revert_manual_balance_credit(): void
    {
        $admin = User::factory()->admin()->create();
        $investor = User::factory()->create();

        $this->actingAs($admin)
            ->post(route('admin.users.manual-balance', $investor), [
                'amount' => 100,
                'note' => 'Bono de bienvenida',
            ]);

        $credit = InvestmentTransaction::query()
            ->where('type', InvestmentTransactionType::AdminAdjustment)
            ->firstOrFail();

        $this->actingAs($admin)
            ->get(route('admin.users.edit', $investor))
            ->assertOk()
            ->assertInertia(fn ($page) => $page
                ->component('admin/user-edit')
                ->has('manual_balance_credits', 1)
                ->where('manual_balance_credits.0.id', $credit->id)
                ->where('manual_balance_credits.0.amount_usd', '100.00')
                ->where('manual_balance_credits.0.admin_note', 'Bono de bienvenida')
                ->where('manual_balance_credits.0.can_revert', true));

        $this->assertSame(100.0, InvestmentLedger::totalWithdrawableForUser($investor->id));

        $creditId = $credit->id;

        $this->actingAs($admin)
            ->post(route('admin.users.manual-balance.revert', [
                'user' => $investor,
                'transaction' => $credit,
            ]))
            ->assertRedirect(route('admin.users.edit', $investor));

        $this->assertDatabaseMissing('investment_transactions', ['id' => $creditId]);
        $this->assertSame(0.0, InvestmentLedger::totalWithdrawableForUser($investor->id));
        $this->assertDatabaseMissing('investment_transactions', [
            'type' => InvestmentTransactionType::AdminAdjustmentReversal->value,
        ]);
    }

    public function test_admin_cannot_revert_manual_balance_if_insufficient_container_balance(): void
    {
        $admin = User::factory()->admin()->create();
        $investor = User::factory()->create();

        $this->actingAs($admin)
            ->post(route('admin.users.manual-balance', $investor), ['amount' => 100]);

        $credit = InvestmentTransaction::query()
            ->where('type', InvestmentTransactionType::AdminAdjustment)
            ->firstOrFail();

        $investment = $credit->investment;
        $investment->update(['capital_balance' => 25]);

        $this->actingAs($admin)
            ->post(route('admin.users.manual-balance.revert', [
                'user' => $investor,
                'transaction' => $credit,
            ]))
            ->assertSessionHasErrors('transaction');
    }

    public function test_admin_can_enable_jabes_women_for_investor(): void
    {
        $admin = User::factory()->admin()->create();
        $investor = User::factory()->create(['jabes_women_enabled' => false]);

        $this->actingAs($admin)
            ->patch(route('admin.users.update', $investor), $this->payloadFor($investor, [
                'jabes_women_enabled' => true,
            ]))
            ->assertRedirect(route('admin.users.edit', $investor));

        $investor->refresh();
        $this->assertTrue($investor->jabes_women_enabled);
    }

    public function test_jabes_women_user_receives_flag_in_inertia_auth(): void
    {
        $user = User::factory()->jabesWomen()->create();

        $this->actingAs($user)
            ->get(route('dashboard'))
            ->assertOk()
            ->assertInertia(fn ($page) => $page
                ->where('auth.user.jabes_women_enabled', true));
    }

    public function test_admin_can_add_manual_balance_to_admin_account(): void
    {
        $actingAdmin = User::factory()->admin()->create();
        $targetAdmin = User::factory()->admin()->create(['email' => 'other-admin@test.com']);

        $this->actingAs($actingAdmin)
            ->post(route('admin.users.manual-balance', $targetAdmin), [
                'amount' => 75,
            ])
            ->assertRedirect(route('admin.users.edit', $targetAdmin));

        $this->assertSame(75.0, InvestmentLedger::totalWithdrawableForUser($targetAdmin->id));
    }

    public function test_admin_can_enable_jabes_women_for_admin_user(): void
    {
        $actingAdmin = User::factory()->admin()->create();
        $targetAdmin = User::factory()->admin()->create([
            'email' => 'jw-admin@test.com',
            'jabes_women_enabled' => false,
        ]);

        $this->actingAs($actingAdmin)
            ->patch(route('admin.users.update', $targetAdmin), $this->payloadFor($targetAdmin, [
                'jabes_women_enabled' => true,
            ]))
            ->assertRedirect(route('admin.users.edit', $targetAdmin));

        $targetAdmin->refresh();
        $this->assertTrue($targetAdmin->jabes_women_enabled);
    }

    public function test_admin_user_edit_includes_referral_earnings_progress(): void
    {
        $admin = User::factory()->admin()->create();
        $earner = User::factory()->create(['referral_program_enabled' => true]);

        InvestmentSetting::current()->update([
            'referral_direct_earning_limit' => 500,
            'referral_indirect_earning_limit' => null,
        ]);

        $investor = User::factory()->create();
        /** @var Investment $investment */
        $investment = $investor->investments()->create([
            'amount' => 1000,
            'duration_months' => 4,
            'monthly_return_snapshot' => 10,
            'txid' => 'tx-admin-referral-progress',
            '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::ReferralDirectBonus,
            'amount' => 125.50,
            'balance_after' => 0,
            'beneficiary_user_id' => $earner->id,
            'referral_level' => 1,
        ]);

        $this->actingAs($admin)
            ->get(route('admin.users.edit', $earner))
            ->assertOk()
            ->assertInertia(fn ($page) => $page
                ->component('admin/user-edit')
                ->where('referral_earnings_progress.direct_earned_usd', '125.50')
                ->where('referral_earnings_progress.indirect_earned_usd', '0.00')
                ->where('referral_earnings_progress.direct_limit_usd', '500.00')
                ->where('referral_earnings_progress.indirect_limit_usd', null));
    }
}
