<?php

namespace Tests\Feature\Security\Authorization;

use App\Models\Account;
use App\Models\Admin;
use App\Models\Bank;
use App\Models\Organization;
use App\Models\TransactionType;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Livewire\Livewire;
use Tests\TestCase;

/**
 * Payment Multi-Auth Tests (Livewire Pay Component)
 *
 * Tests that the Livewire Pay component correctly validates account ownership
 * and prevents unauthorized payments across all authentication guards.
 *
 * @group security
 * @group authorization
 * @group multi-guard
 * @group payment
 * @group critical
 */
class PaymentMultiAuthTest extends TestCase
{
    use RefreshDatabase;

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

        // Create transaction types (using insert to avoid mass assignment protection)
        \DB::table('transaction_types')->insert([
            ['id' => 1, 'name' => 'worked_hours', 'label' => 'Worked Hours', 'icon' => 'clock'],
            ['id' => 2, 'name' => 'gift', 'label' => 'Gift', 'icon' => 'gift'],
            ['id' => 3, 'name' => 'donation', 'label' => 'Donation', 'icon' => 'hand-thumb-up'],
        ]);
    }

    /**
     * Test user can make payment from their own account
     *
     * @test
     */
    public function user_can_make_payment_from_own_account()
    {
        $user = User::factory()->create();
        $recipient = User::factory()->create();

        $userAccount = Account::factory()->create([
            'accountable_type' => User::class,
            'accountable_id' => $user->id,
            'limit_min' => -1000,
            'limit_max' => 1000,
        ]);

        $recipientAccount = Account::factory()->create([
            'accountable_type' => User::class,
            'accountable_id' => $recipient->id,
            'limit_min' => -1000,
            'limit_max' => 1000,
        ]);

        $this->actingAs($user, 'web');
        session(['activeProfileType' => User::class]);
        session(['activeProfileId' => $user->id]);

        $response = Livewire::test(\App\Http\Livewire\Pay::class)
            ->set('fromAccountId', $userAccount->id)
            ->set('toAccountId', $recipientAccount->id)
            ->set('amount', 60)
            ->set('description', 'Test payment')
            ->set('transactionTypeSelected', ['id' => 1, 'name' => 'worked_hours'])
            ->call('doTransfer');

        $response->assertHasNoErrors();
        $this->assertDatabaseHas('transactions', [
            'from_account_id' => $userAccount->id,
            'to_account_id' => $recipientAccount->id,
            'amount' => 60,
        ]);
    }

    /**
     * Test user cannot make payment from another user's account
     *
     * @test
     */
    public function user_cannot_make_payment_from_another_users_account()
    {
        $user1 = User::factory()->create();
        $user2 = User::factory()->create();
        $recipient = User::factory()->create();

        $user1Account = Account::factory()->create([
            'accountable_type' => User::class,
            'accountable_id' => $user1->id,
        ]);

        $user2Account = Account::factory()->create([
            'accountable_type' => User::class,
            'accountable_id' => $user2->id,
        ]);

        $recipientAccount = Account::factory()->create([
            'accountable_type' => User::class,
            'accountable_id' => $recipient->id,
        ]);

        // Login as user1
        $this->actingAs($user1, 'web');
        session(['activeProfileType' => User::class]);
        session(['activeProfileId' => $user1->id]);

        // Try to make payment from user2's account
        $response = Livewire::test(\App\Http\Livewire\Pay::class)
            ->set('fromAccountId', $user2Account->id) // Unauthorized account!
            ->set('toAccountId', $recipientAccount->id)
            ->set('amount', 60)
            ->set('description', 'Unauthorized payment')
            ->set('transactionTypeSelected', ['id' => 1, 'name' => 'worked_hours'])
            ->call('doTransfer');

        // Should redirect back with error
        $response->assertRedirect();

        // Transaction should NOT be created
        $this->assertDatabaseMissing('transactions', [
            'from_account_id' => $user2Account->id,
            'amount' => 60,
            'description' => 'Unauthorized payment',
        ]);
    }

    /**
     * Test organization can make payment from their own account
     *
     * @test
     */
    public function organization_can_make_payment_from_own_account()
    {
        $user = User::factory()->create();
        $organization = Organization::factory()->create();
        $organization->users()->attach($user->id);
        $recipient = User::factory()->create();

        $orgAccount = Account::factory()->create([
            'accountable_type' => Organization::class,
            'accountable_id' => $organization->id,
            'limit_min' => -1000,
            'limit_max' => 1000,
        ]);

        $recipientAccount = Account::factory()->create([
            'accountable_type' => User::class,
            'accountable_id' => $recipient->id,
            'limit_min' => -1000,
            'limit_max' => 1000,
        ]);

        $this->actingAs($organization, 'organization');
        session(['activeProfileType' => Organization::class]);
        session(['activeProfileId' => $organization->id]);

        $response = Livewire::test(\App\Http\Livewire\Pay::class)
            ->set('fromAccountId', $orgAccount->id)
            ->set('toAccountId', $recipientAccount->id)
            ->set('amount', 120)
            ->set('description', 'Organization payment')
            ->set('transactionTypeSelected', ['id' => 1, 'name' => 'worked_hours'])
            ->call('doTransfer');

        $response->assertHasNoErrors();
        $this->assertDatabaseHas('transactions', [
            'from_account_id' => $orgAccount->id,
            'to_account_id' => $recipientAccount->id,
            'amount' => 120,
        ]);
    }

    /**
     * Test organization cannot make payment from another organization's account
     *
     * @test
     */
    public function organization_cannot_make_payment_from_another_organizations_account()
    {
        $user = User::factory()->create();
        $org1 = Organization::factory()->create();
        $org2 = Organization::factory()->create();
        $org1->users()->attach($user->id);
        $recipient = User::factory()->create();

        $org1Account = Account::factory()->create([
            'accountable_type' => Organization::class,
            'accountable_id' => $org1->id,
        ]);

        $org2Account = Account::factory()->create([
            'accountable_type' => Organization::class,
            'accountable_id' => $org2->id,
        ]);

        $recipientAccount = Account::factory()->create([
            'accountable_type' => User::class,
            'accountable_id' => $recipient->id,
        ]);

        // Login as org1
        $this->actingAs($org1, 'organization');
        session(['activeProfileType' => Organization::class]);
        session(['activeProfileId' => $org1->id]);

        // Try to make payment from org2's account
        $response = Livewire::test(\App\Http\Livewire\Pay::class)
            ->set('fromAccountId', $org2Account->id) // Unauthorized!
            ->set('toAccountId', $recipientAccount->id)
            ->set('amount', 120)
            ->set('description', 'Unauthorized org payment')
            ->set('transactionTypeSelected', ['id' => 1, 'name' => 'worked_hours'])
            ->call('doTransfer');

        $response->assertRedirect();

        $this->assertDatabaseMissing('transactions', [
            'from_account_id' => $org2Account->id,
            'description' => 'Unauthorized org payment',
        ]);
    }

    /**
     * Test bank can make payment from their own account
     *
     * @test
     */
    public function bank_can_make_payment_from_own_account()
    {
        $user = User::factory()->create();
        $bank = Bank::factory()->create();
        $bank->managers()->attach($user->id);
        $recipient = User::factory()->create();

        $bankAccount = Account::factory()->create([
            'accountable_type' => Bank::class,
            'accountable_id' => $bank->id,
            'limit_min' => -10000,
            'limit_max' => 10000,
        ]);

        $recipientAccount = Account::factory()->create([
            'accountable_type' => User::class,
            'accountable_id' => $recipient->id,
            'limit_min' => -1000,
            'limit_max' => 1000,
        ]);

        $this->actingAs($bank, 'bank');
        session(['activeProfileType' => Bank::class]);
        session(['activeProfileId' => $bank->id]);

        $response = Livewire::test(\App\Http\Livewire\Pay::class)
            ->set('fromAccountId', $bankAccount->id)
            ->set('toAccountId', $recipientAccount->id)
            ->set('amount', 200)
            ->set('description', 'Bank payment')
            ->set('transactionTypeSelected', ['id' => 1, 'name' => 'worked_hours'])
            ->call('doTransfer');

        $response->assertHasNoErrors();
        $this->assertDatabaseHas('transactions', [
            'from_account_id' => $bankAccount->id,
            'to_account_id' => $recipientAccount->id,
            'amount' => 200,
        ]);
    }

    /**
     * Test bank cannot make payment from another bank's account
     *
     * @test
     */
    public function bank_cannot_make_payment_from_another_banks_account()
    {
        $user = User::factory()->create();
        $bank1 = Bank::factory()->create();
        $bank2 = Bank::factory()->create();
        $bank1->users()->attach($user->id);
        $recipient = User::factory()->create();

        $bank1Account = Account::factory()->create([
            'accountable_type' => Bank::class,
            'accountable_id' => $bank1->id,
        ]);

        $bank2Account = Account::factory()->create([
            'accountable_type' => Bank::class,
            'accountable_id' => $bank2->id,
        ]);

        $recipientAccount = Account::factory()->create([
            'accountable_type' => User::class,
            'accountable_id' => $recipient->id,
        ]);

        // Login as bank1
        $this->actingAs($bank1, 'bank');
        session(['activeProfileType' => Bank::class]);
        session(['activeProfileId' => $bank1->id]);

        // Try to make payment from bank2's account
        $response = Livewire::test(\App\Http\Livewire\Pay::class)
            ->set('fromAccountId', $bank2Account->id) // Unauthorized!
            ->set('toAccountId', $recipientAccount->id)
            ->set('amount', 200)
            ->set('description', 'Unauthorized bank payment')
            ->set('transactionTypeSelected', ['id' => 1, 'name' => 'worked_hours'])
            ->call('doTransfer');

        $response->assertRedirect();

        $this->assertDatabaseMissing('transactions', [
            'from_account_id' => $bank2Account->id,
            'description' => 'Unauthorized bank payment',
        ]);
    }

    /**
     * Test session manipulation attack prevention
     *
     * @test
     */
    public function session_manipulation_attack_is_prevented()
    {
        $user1 = User::factory()->create();
        $user2 = User::factory()->create();
        $recipient = User::factory()->create();

        $user1Account = Account::factory()->create([
            'accountable_type' => User::class,
            'accountable_id' => $user1->id,
        ]);

        $user2Account = Account::factory()->create([
            'accountable_type' => User::class,
            'accountable_id' => $user2->id,
        ]);

        $recipientAccount = Account::factory()->create([
            'accountable_type' => User::class,
            'accountable_id' => $recipient->id,
        ]);

        // Login as user1
        $this->actingAs($user1, 'web');

        // Malicious: manipulate session to impersonate user2
        session(['activeProfileType' => User::class]);
        session(['activeProfileId' => $user2->id]); // Attacker sets this to user2!

        // Try to make payment from user2's account
        $response = Livewire::test(\App\Http\Livewire\Pay::class)
            ->set('fromAccountId', $user2Account->id)
            ->set('toAccountId', $recipientAccount->id)
            ->set('amount', 60)
            ->set('description', 'Session manipulation attack')
            ->set('transactionTypeSelected', ['id' => 1, 'name' => 'worked_hours'])
            ->call('doTransfer');

        // Should be blocked because doTransfer() checks getAccountsInfo()
        // which validates against the session's active profile
        $response->assertRedirect();

        // Transaction should NOT be created
        $this->assertDatabaseMissing('transactions', [
            'from_account_id' => $user2Account->id,
            'description' => 'Session manipulation attack',
        ]);
    }

    /**
     * Test cross-guard attack prevention (user trying to use org account)
     *
     * @test
     */
    public function cross_guard_attack_is_prevented()
    {
        $user = User::factory()->create();
        $organization = Organization::factory()->create();
        $organization->users()->attach($user->id);
        $recipient = User::factory()->create();

        $userAccount = Account::factory()->create([
            'accountable_type' => User::class,
            'accountable_id' => $user->id,
        ]);

        $orgAccount = Account::factory()->create([
            'accountable_type' => Organization::class,
            'accountable_id' => $organization->id,
        ]);

        $recipientAccount = Account::factory()->create([
            'accountable_type' => User::class,
            'accountable_id' => $recipient->id,
        ]);

        // Login as user (web guard)
        $this->actingAs($user, 'web');
        session(['activeProfileType' => User::class]);
        session(['activeProfileId' => $user->id]);

        // Try to make payment from organization's account while logged in as user
        $response = Livewire::test(\App\Http\Livewire\Pay::class)
            ->set('fromAccountId', $orgAccount->id) // Org account, but user guard!
            ->set('toAccountId', $recipientAccount->id)
            ->set('amount', 120)
            ->set('description', 'Cross-guard attack')
            ->set('transactionTypeSelected', ['id' => 1, 'name' => 'worked_hours'])
            ->call('doTransfer');

        $response->assertRedirect();

        $this->assertDatabaseMissing('transactions', [
            'from_account_id' => $orgAccount->id,
            'description' => 'Cross-guard attack',
        ]);
    }

    /**
     * Test payment to same account is prevented
     *
     * @test
     */
    public function payment_to_same_account_is_prevented()
    {
        $user = User::factory()->create();

        $userAccount = Account::factory()->create([
            'accountable_type' => User::class,
            'accountable_id' => $user->id,
        ]);

        $this->actingAs($user, 'web');
        session(['activeProfileType' => User::class]);
        session(['activeProfileId' => $user->id]);

        $response = Livewire::test(\App\Http\Livewire\Pay::class)
            ->set('fromAccountId', $userAccount->id)
            ->set('toAccountId', $userAccount->id) // Same account!
            ->set('amount', 60)
            ->set('description', 'Self payment')
            ->set('transactionTypeSelected', ['id' => 1, 'name' => 'worked_hours'])
            ->call('doTransfer');

        $response->assertRedirect();

        $this->assertDatabaseMissing('transactions', [
            'from_account_id' => $userAccount->id,
            'to_account_id' => $userAccount->id,
        ]);
    }

    /**
     * Test payment to non-existent account is prevented
     *
     * @test
     */
    public function payment_to_nonexistent_account_is_prevented()
    {
        $user = User::factory()->create();

        $userAccount = Account::factory()->create([
            'accountable_type' => User::class,
            'accountable_id' => $user->id,
        ]);

        $this->actingAs($user, 'web');
        session(['activeProfileType' => User::class]);
        session(['activeProfileId' => $user->id]);

        $response = Livewire::test(\App\Http\Livewire\Pay::class)
            ->set('fromAccountId', $userAccount->id)
            ->set('toAccountId', 99999) // Non-existent account
            ->set('amount', 60)
            ->set('description', 'Payment to nowhere')
            ->set('transactionTypeSelected', ['id' => 1, 'name' => 'worked_hours'])
            ->call('doTransfer');

        $response->assertRedirect();

        $this->assertDatabaseMissing('transactions', [
            'to_account_id' => 99999,
        ]);
    }

    /**
     * Test unauthenticated user cannot make payments
     *
     * @test
     */
    public function unauthenticated_user_cannot_make_payments()
    {
        $user = User::factory()->create();
        $recipient = User::factory()->create();

        $userAccount = Account::factory()->create([
            'accountable_type' => User::class,
            'accountable_id' => $user->id,
        ]);

        $recipientAccount = Account::factory()->create([
            'accountable_type' => User::class,
            'accountable_id' => $recipient->id,
        ]);

        // Not authenticated
        session(['activeProfileType' => User::class]);
        session(['activeProfileId' => $user->id]);

        $response = Livewire::test(\App\Http\Livewire\Pay::class)
            ->set('fromAccountId', $userAccount->id)
            ->set('toAccountId', $recipientAccount->id)
            ->set('amount', 60)
            ->set('description', 'Unauthenticated payment')
            ->set('transactionTypeSelected', ['id' => 1, 'name' => 'worked_hours'])
            ->call('doTransfer');

        // Should fail due to lack of authentication
        $this->assertDatabaseMissing('transactions', [
            'description' => 'Unauthenticated payment',
        ]);
    }

    /**
     * Test admin profile without account cannot make payments
     *
     * @test
     */
    public function admin_without_account_cannot_make_payments()
    {
        $user = User::factory()->create();
        $admin = Admin::factory()->create();
        $admin->users()->attach($user->id);
        $recipient = User::factory()->create();

        // Admin has no account
        $recipientAccount = Account::factory()->create([
            'accountable_type' => User::class,
            'accountable_id' => $recipient->id,
        ]);

        $this->actingAs($admin, 'admin');
        session(['activeProfileType' => Admin::class]);
        session(['activeProfileId' => $admin->id]);

        $response = Livewire::test(\App\Http\Livewire\Pay::class)
            ->set('fromAccountId', null) // No account
            ->set('toAccountId', $recipientAccount->id)
            ->set('amount', 60)
            ->set('description', 'Admin payment without account')
            ->set('transactionTypeSelected', ['id' => 1, 'name' => 'worked_hours'])
            ->call('doTransfer');

        // Should show error notification
        $response->assertDispatched('notification');
    }
}
