<?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\Transaction;
use App\Models\TransactionType;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Livewire\Livewire;
use Tests\TestCase;

/**
 * Transaction View Authorization Tests
 *
 * Tests that users can only view transactions involving their own accounts
 * and cannot view other users' transactions across all authentication guards.
 *
 * @group security
 * @group authorization
 * @group multi-guard
 * @group transaction-view
 * @group critical
 */
class TransactionViewAuthorizationTest 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',
        ]);
    }

    /**
     * Test user can view transaction they are involved in (sender)
     *
     * @test
     */
    public function user_can_view_transaction_as_sender()
    {
        $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,
        ]);

        $transaction = Transaction::factory()->create([
            'from_account_id' => $userAccount->id,
            'to_account_id' => $recipientAccount->id,
            'amount' => 60,
        ]);

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

        $response = $this->get(route('transaction.show', ['transactionId' => $transaction->id]));

        $response->assertStatus(200);
    }

    /**
     * Test user can view transaction they are involved in (recipient)
     *
     * @test
     */
    public function user_can_view_transaction_as_recipient()
    {
        $sender = User::factory()->create();
        $user = User::factory()->create();

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

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

        $transaction = Transaction::factory()->create([
            'from_account_id' => $senderAccount->id,
            'to_account_id' => $userAccount->id,
            'amount' => 60,
        ]);

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

        $response = $this->get(route('transaction.show', ['transactionId' => $transaction->id]));

        $response->assertStatus(200);
    }

    /**
     * Test user cannot view transaction they are not involved in
     *
     * @test
     */
    public function user_cannot_view_transaction_they_are_not_involved_in()
    {
        $user = User::factory()->create();
        $sender = User::factory()->create();
        $recipient = User::factory()->create();

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

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

        $transaction = Transaction::factory()->create([
            'from_account_id' => $senderAccount->id,
            'to_account_id' => $recipientAccount->id,
            'amount' => 60,
        ]);

        // User is NOT involved in this transaction
        $this->actingAs($user, 'web');
        session(['activeProfileType' => User::class]);
        session(['activeProfileId' => $user->id]);

        $response = $this->get(route('transaction.show', ['transactionId' => $transaction->id]));

        $response->assertStatus(403);
    }

    /**
     * Test organization can view transaction they are involved in
     *
     * @test
     */
    public function organization_can_view_transaction_they_are_involved_in()
    {
        $orgUser = User::factory()->create();
        $organization = Organization::factory()->create();
        $organization->users()->attach($orgUser->id);
        $recipient = User::factory()->create();

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

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

        $transaction = Transaction::factory()->create([
            'from_account_id' => $orgAccount->id,
            'to_account_id' => $recipientAccount->id,
            'amount' => 120,
        ]);

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

        $response = $this->get(route('transaction.show', ['transactionId' => $transaction->id]));

        $response->assertStatus(200);
    }

    /**
     * Test organization cannot view transaction of another organization
     *
     * @test
     */
    public function organization_cannot_view_another_organizations_transaction()
    {
        $user = User::factory()->create();
        $org1 = Organization::factory()->create();
        $org2 = Organization::factory()->create();
        $org1->users()->attach($user->id);
        $recipient = User::factory()->create();

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

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

        $transaction = Transaction::factory()->create([
            'from_account_id' => $org2Account->id,
            'to_account_id' => $recipientAccount->id,
            'amount' => 120,
        ]);

        // Logged in as org1, trying to view org2's transaction
        $this->actingAs($org1, 'organization');
        session(['activeProfileType' => Organization::class]);
        session(['activeProfileId' => $org1->id]);

        $response = $this->get(route('transaction.show', ['transactionId' => $transaction->id]));

        $response->assertStatus(403);
    }

    /**
     * Test bank can view transaction they are involved in
     *
     * @test
     */
    public function bank_can_view_transaction_they_are_involved_in()
    {
        $bankUser = User::factory()->create();
        $bank = Bank::factory()->create();
        $bank->managers()->attach($bankUser->id);
        $recipient = User::factory()->create();

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

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

        $transaction = Transaction::factory()->create([
            'from_account_id' => $bankAccount->id,
            'to_account_id' => $recipientAccount->id,
            'amount' => 200,
        ]);

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

        $response = $this->get(route('transaction.show', ['transactionId' => $transaction->id]));

        $response->assertStatus(200);
    }

    /**
     * Test session manipulation to view unauthorized transaction is blocked
     *
     * @test
     */
    public function session_manipulation_to_view_transaction_is_blocked()
    {
        $user1 = User::factory()->create();
        $user2 = User::factory()->create();
        $recipient = User::factory()->create();

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

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

        $transaction = Transaction::factory()->create([
            'from_account_id' => $user2Account->id,
            'to_account_id' => $recipientAccount->id,
            'amount' => 60,
        ]);

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

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

        $response = $this->get(route('transaction.show', ['transactionId' => $transaction->id]));

        // Should be blocked by statement() query which checks session against database
        $response->assertStatus(403);
    }

    /**
     * Test cross-guard attack to view transaction is blocked
     *
     * @test
     */
    public function cross_guard_attack_to_view_transaction_is_blocked()
    {
        $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,
        ]);

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

        $transaction = Transaction::factory()->create([
            'from_account_id' => $orgAccount->id,
            'to_account_id' => $recipientAccount->id,
            'amount' => 120,
        ]);

        // Logged in as user (web guard)
        $this->actingAs($user, 'web');

        // Malicious: manipulate session to view org's transaction
        session(['activeProfileType' => Organization::class]);
        session(['activeProfileId' => $organization->id]);

        $response = $this->get(route('transaction.show', ['transactionId' => $transaction->id]));

        // Should be blocked because auth guard doesn't match session
        $response->assertStatus(403);
    }

    /**
     * Test unauthenticated user cannot view transactions
     *
     * @test
     */
    public function unauthenticated_user_cannot_view_transactions()
    {
        $sender = User::factory()->create();
        $recipient = User::factory()->create();

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

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

        $transaction = Transaction::factory()->create([
            'from_account_id' => $senderAccount->id,
            'to_account_id' => $recipientAccount->id,
            'amount' => 60,
        ]);

        // Not authenticated
        $response = $this->get(route('transaction.show', ['transactionId' => $transaction->id]));

        $response->assertRedirect(route('login'));
    }

    /**
     * Test user can access transactions list page
     *
     * @test
     */
    public function user_can_access_transactions_list_page()
    {
        $user = User::factory()->create();

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

        $response = $this->get(route('transactions'));

        $response->assertStatus(200);
    }

    /**
     * Test organization can access transactions list page
     *
     * @test
     */
    public function organization_can_access_transactions_list_page()
    {
        $user = User::factory()->create();
        $organization = Organization::factory()->create();
        $organization->users()->attach($user->id);

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

        $response = $this->get(route('transactions'));

        $response->assertStatus(200);
    }

    /**
     * Test bank can access transactions list page
     *
     * @test
     */
    public function bank_can_access_transactions_list_page()
    {
        $user = User::factory()->create();
        $bank = Bank::factory()->create();
        $bank->managers()->attach($user->id);

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

        $response = $this->get(route('transactions'));

        $response->assertStatus(200);
    }

    /**
     * Test non-existent transaction returns 403
     *
     * @test
     */
    public function non_existent_transaction_returns_403()
    {
        $user = User::factory()->create();

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

        $response = $this->get(route('transaction.show', ['transactionId' => 99999]));

        $response->assertStatus(403);
    }

    /**
     * Test TransactionsTable Livewire component loads for user
     *
     * @test
     */
    public function transactions_table_livewire_component_loads_for_user()
    {
        $user = User::factory()->create();

        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\TransactionsTable::class);

        $response->assertStatus(200);
    }

    /**
     * Test TransactionsTable Livewire component loads for organization
     *
     * @test
     */
    public function transactions_table_livewire_component_loads_for_organization()
    {
        $user = User::factory()->create();
        $organization = Organization::factory()->create();
        $organization->users()->attach($user->id);

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

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

        $response = Livewire::test(\App\Http\Livewire\TransactionsTable::class);

        $response->assertStatus(200);
    }

    /**
     * Test TransactionsTable filters transactions by active profile
     *
     * @test
     */
    public function transactions_table_filters_by_active_profile()
    {
        $user1 = User::factory()->create();
        $user2 = 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,
        ]);

        // Transaction involving user1
        Transaction::factory()->create([
            'from_account_id' => $user1Account->id,
            'to_account_id' => $user2Account->id,
            'amount' => 60,
            'description' => 'User1 transaction',
        ]);

        // Transaction NOT involving user1
        Transaction::factory()->create([
            'from_account_id' => $user2Account->id,
            'to_account_id' => $user2Account->id, // Self transaction for testing
            'amount' => 30,
            'description' => 'User2 only transaction',
        ]);

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

        $response = Livewire::test(\App\Http\Livewire\TransactionsTable::class);

        // Should see user1's transaction
        $response->assertSee('User1 transaction');

        // Should NOT see user2's private transaction
        $response->assertDontSee('User2 only transaction');
    }
}
