<?php

namespace Tests\Feature\Security\Authentication;

use App\Models\Admin;
use App\Models\Bank;
use App\Models\Organization;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Tests\TestCase;

/**
 * Profile Switching Security Tests
 *
 * Tests the security of profile switching in the multi-guard system:
 * - Ownership verification
 * - Password requirements (organization: no password, bank/admin: requires password)
 * - Session state management
 * - Relationship validation
 *
 * @group security
 * @group critical
 * @group authentication
 * @group profile-switching
 */
class ProfileSwitchingSecurityTest extends TestCase
{
    use RefreshDatabase;

    // ==========================================
    // OWNERSHIP VERIFICATION TESTS
    // ==========================================

    /**
     * Test that user can only switch to owned organization profiles
     */
    public function test_user_can_only_switch_to_owned_organization()
    {
        $user = User::factory()->create();
        $ownedOrganization = Organization::factory()->create(['password' => Hash::make('password')]);
        $unownedOrganization = Organization::factory()->create(['password' => Hash::make('password')]);

        // Link user to owned organization only
        $user->organizations()->attach($ownedOrganization->id);

        $this->actingAs($user, 'web');

        // Set intent for owned organization - should work
        session([
            'intended_profile_switch_type' => 'Organization',
            'intended_profile_switch_id' => $ownedOrganization->id,
        ]);

        $response = $this->get(route('organization.login'));
        $response->assertStatus(200);
        $response->assertViewIs('profile-organization.login');

        // Set intent for unowned organization - should fail
        session([
            'intended_profile_switch_type' => 'Organization',
            'intended_profile_switch_id' => $unownedOrganization->id,
        ]);

        $response = $this->get(route('organization.login'));
        // Should show nothing or error because getTargetProfileByTypeAndId returns null
        $this->assertNull(session('intended_profile_switch_id') ?
            Organization::find(session('intended_profile_switch_id')) : null);
    }

    /**
     * Test that user cannot switch to unowned bank profile
     */
    public function test_cannot_switch_to_unowned_bank()
    {
        $user = User::factory()->create();
        $ownedBank = Bank::factory()->create(['password' => Hash::make('password')]);
        $unownedBank = Bank::factory()->create(['password' => Hash::make('password')]);

        // Link user to owned bank only
        $user->banksManaged()->attach($ownedBank->id);

        $this->actingAs($user, 'web');

        // Try to access unowned bank
        session([
            'intended_profile_switch_type' => 'Bank',
            'intended_profile_switch_id' => $unownedBank->id,
        ]);

        $response = $this->get(route('bank.login'));

        // The view will receive $profile = null from getTargetProfileByTypeAndId
        // This might cause an error or show an empty form
        $response->assertStatus(200); // The route exists but profile will be null
    }

    /**
     * Test that user cannot switch to unowned admin profile
     */
    public function test_cannot_switch_to_unowned_admin()
    {
        $user = User::factory()->create();
        $ownedAdmin = Admin::factory()->create(['password' => Hash::make('password')]);
        $unownedAdmin = Admin::factory()->create(['password' => Hash::make('password')]);

        // Link user to owned admin only
        $user->admins()->attach($ownedAdmin->id);

        $this->actingAs($user, 'web');

        // Try to access unowned admin
        session([
            'intended_profile_switch_type' => 'Admin',
            'intended_profile_switch_id' => $unownedAdmin->id,
        ]);

        $response = $this->get(route('admin.login'));

        $response->assertStatus(200); // Route exists but profile will be null
    }

    /**
     * Test profile switch validates relationship pivot tables
     */
    public function test_profile_switch_validates_relationship_pivot_tables()
    {
        $user = User::factory()->create();
        $organization = Organization::factory()->create(['password' => Hash::make('password')]);
        $bank = Bank::factory()->create(['password' => Hash::make('password')]);
        $admin = Admin::factory()->create(['password' => Hash::make('password')]);

        $this->actingAs($user, 'web');

        // Try organization without pivot entry
        session([
            'intended_profile_switch_type' => 'Organization',
            'intended_profile_switch_id' => $organization->id,
        ]);
        $response = $this->get(route('organization.login'));
        // getTargetProfileByTypeAndId will return null (no relationship)

        // Try bank without pivot entry
        session([
            'intended_profile_switch_type' => 'Bank',
            'intended_profile_switch_id' => $bank->id,
        ]);
        $response = $this->get(route('bank.login'));
        // getTargetProfileByTypeAndId will return null

        // Try admin without pivot entry
        session([
            'intended_profile_switch_type' => 'Admin',
            'intended_profile_switch_id' => $admin->id,
        ]);
        $response = $this->get(route('admin.login'));
        // getTargetProfileByTypeAndId will return null

        // Now add relationships and verify they work
        $user->organizations()->attach($organization->id);
        $user->banksManaged()->attach($bank->id);
        $user->admins()->attach($admin->id);

        session([
            'intended_profile_switch_type' => 'Organization',
            'intended_profile_switch_id' => $organization->id,
        ]);
        $response = $this->get(route('organization.login'));
        $response->assertStatus(200);
        $response->assertViewHas('profile', $organization);
    }

    // ==========================================
    // PASSWORD REQUIREMENT TESTS
    // ==========================================

    /**
     * Test that organization switch does not require password (via direct login)
     */
    public function test_organization_switch_does_not_require_password_via_direct_login()
    {
        $user = User::factory()->create();
        $organization = Organization::factory()->create(['password' => Hash::make('password')]);
        $user->organizations()->attach($organization->id);

        $this->actingAs($user, 'web');

        // Direct login for organization should switch immediately
        $response = $this->get(route('organization.direct-login', ['organizationId' => $organization->id]));

        // Should redirect to main page, not to password form
        $response->assertRedirect(route('main'));

        // Should be authenticated on organization guard
        $this->assertTrue(Auth::guard('organization')->check());
        $this->assertEquals($organization->id, Auth::guard('organization')->id());
    }

    /**
     * Test that bank switch requires password
     */
    public function test_bank_switch_requires_password()
    {
        $user = User::factory()->create();
        $bank = Bank::factory()->create(['password' => Hash::make('bank-password')]);
        $user->banksManaged()->attach($bank->id);

        $this->actingAs($user, 'web');

        // Direct login for bank should redirect to password form
        $response = $this->get(route('bank.direct-login', ['bankId' => $bank->id]));

        $response->assertRedirect(route('bank.login'));

        // Should NOT be authenticated on bank guard yet
        $this->assertFalse(Auth::guard('bank')->check());

        // Verify session intent was set
        $this->assertEquals('Bank', session('intended_profile_switch_type'));
        $this->assertEquals($bank->id, session('intended_profile_switch_id'));
    }

    /**
     * Test that admin switch requires password
     */
    public function test_admin_switch_requires_password()
    {
        $user = User::factory()->create();
        $admin = Admin::factory()->create(['password' => Hash::make('admin-password')]);
        $user->admins()->attach($admin->id);

        $this->actingAs($user, 'web');

        // Direct login for admin should redirect to password form
        $response = $this->get(route('admin.direct-login', ['adminId' => $admin->id]));

        $response->assertRedirect(route('admin.login'));

        // Should NOT be authenticated on admin guard yet
        $this->assertFalse(Auth::guard('admin')->check());

        // Verify session intent was set
        $this->assertEquals('Admin', session('intended_profile_switch_type'));
        $this->assertEquals($admin->id, session('intended_profile_switch_id'));
    }

    /**
     * Test that invalid password prevents bank profile switch
     */
    public function test_invalid_password_prevents_bank_profile_switch()
    {
        $user = User::factory()->create();
        $bank = Bank::factory()->create(['password' => Hash::make('correct-password')]);
        $user->banksManaged()->attach($bank->id);

        $this->actingAs($user, 'web');
        $this->withoutMiddleware(\App\Http\Middleware\VerifyCsrfToken::class);

        session([
            'intended_profile_switch_type' => 'Bank',
            'intended_profile_switch_id' => $bank->id,
        ]);

        $response = $this->post(route('bank.login.post'), [
            'password' => 'wrong-password',
        ]);

        $response->assertSessionHasErrors(['password']);
        $this->assertFalse(Auth::guard('bank')->check());
    }

    /**
     * Test that valid password allows bank profile switch
     */
    public function test_valid_password_allows_bank_profile_switch()
    {
        $user = User::factory()->create();
        $bank = Bank::factory()->create(['password' => Hash::make('correct-password')]);
        $user->banksManaged()->attach($bank->id);

        $this->actingAs($user, 'web');
        $this->withoutMiddleware(\App\Http\Middleware\VerifyCsrfToken::class);

        session([
            'intended_profile_switch_type' => 'Bank',
            'intended_profile_switch_id' => $bank->id,
        ]);

        $response = $this->post(route('bank.login.post'), [
            'password' => 'correct-password',
        ]);

        $response->assertRedirect(route('main'));
        $this->assertTrue(Auth::guard('bank')->check());
        $this->assertEquals($bank->id, Auth::guard('bank')->id());
    }

    // ==========================================
    // SESSION STATE MANAGEMENT TESTS
    // ==========================================

    /**
     * Test that profile switch clears session variables after successful authentication
     */
    public function test_profile_switch_clears_session_variables()
    {
        $user = User::factory()->create();
        $bank = Bank::factory()->create(['password' => Hash::make('password')]);
        $user->banksManaged()->attach($bank->id);

        $this->actingAs($user, 'web');
        $this->withoutMiddleware(\App\Http\Middleware\VerifyCsrfToken::class);

        session([
            'intended_profile_switch_type' => 'Bank',
            'intended_profile_switch_id' => $bank->id,
        ]);

        $this->assertNotNull(session('intended_profile_switch_type'));
        $this->assertNotNull(session('intended_profile_switch_id'));

        $this->post(route('bank.login.post'), [
            'password' => 'password',
        ]);

        // Session variables should be cleared after successful login
        $this->assertNull(session('intended_profile_switch_type'));
        $this->assertNull(session('intended_profile_switch_id'));
    }

    /**
     * Test that active profile information is stored in session
     */
    public function test_active_profile_stored_in_session()
    {
        $user = User::factory()->create();
        $organization = Organization::factory()->create(['password' => Hash::make('password')]);
        $user->organizations()->attach($organization->id);

        $this->actingAs($user, 'web');

        $response = $this->get(route('organization.direct-login', ['organizationId' => $organization->id]));

        // Check session contains profile information
        $this->assertEquals(get_class($organization), session('activeProfileType'));
        $this->assertEquals($organization->id, session('activeProfileId'));
        $this->assertEquals($organization->name, session('activeProfileName'));
        $this->assertNotNull(session('last_activity'));
    }

    // ==========================================
    // EDGE CASES
    // ==========================================

    /**
     * Test that switching to nonexistent profile fails gracefully
     */
    public function test_cannot_switch_to_nonexistent_profile()
    {
        $user = User::factory()->create();

        $this->actingAs($user, 'web');

        // Try to access nonexistent organization
        $response = $this->get(route('organization.direct-login', ['organizationId' => 99999]));

        $response->assertStatus(404);
    }

    /**
     * Test that switching to soft-deleted profile fails
     */
    public function test_cannot_switch_to_soft_deleted_profile()
    {
        $user = User::factory()->create();
        $organization = Organization::factory()->create(['password' => Hash::make('password')]);
        $user->organizations()->attach($organization->id);

        // Soft delete the organization
        $organization->delete();

        $this->actingAs($user, 'web');

        // Try to access deleted organization
        $response = $this->get(route('organization.direct-login', ['organizationId' => $organization->id]));

        $response->assertStatus(404);
    }

    /**
     * Test that profile switch requires user to be authenticated
     */
    public function test_profile_switch_requires_authentication()
    {
        $organization = Organization::factory()->create(['password' => Hash::make('password')]);

        // Try to access organization without being authenticated
        $response = $this->get(route('organization.direct-login', ['organizationId' => $organization->id]));

        // Should redirect to login
        $response->assertRedirect();
        $this->assertStringContainsString('login', $response->headers->get('Location'));
    }
}
