<?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 Illuminate\Support\Facades\Session;
use Tests\TestCase;

/**
 * Session Security Tests
 *
 * Tests session security features:
 * - Session regeneration on login
 * - Session data protection
 * - Session cookie security
 * - Session clearing on logout
 *
 * @group security
 * @group high
 * @group authentication
 * @group session
 */
class SessionSecurityTest extends TestCase
{
    use RefreshDatabase;

    // ==========================================
    // SESSION REGENERATION TESTS
    // ==========================================

    /**
     * Test that session is regenerated on user login
     */
    public function test_session_regenerated_on_login()
    {
        $user = User::factory()->create([
            'email' => 'user@test.com',
            'password' => Hash::make('password123'),
        ]);

        $this->withoutMiddleware(\App\Http\Middleware\VerifyCsrfToken::class);

        // Start session and get initial ID
        $this->startSession();
        $initialSessionId = Session::getId();

        // Login
        $this->post(route('login'), [
            'name' => $user->email,
            'password' => 'password123',
        ]);

        // Session ID should have changed (regenerated)
        $newSessionId = Session::getId();
        $this->assertNotEquals($initialSessionId, $newSessionId, 'Session should be regenerated on login');
    }

    /**
     * Test that session is regenerated on profile switch
     */
    public function test_session_regenerated_on_profile_switch()
    {
        $user = User::factory()->create();
        $organization = Organization::factory()->create();
        $user->organizations()->attach($organization->id);

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

        $initialSessionId = Session::getId();

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

        // Note: Session regeneration on profile switch may not be implemented
        // This test documents expected behavior
        // If this test fails, consider implementing session regeneration on profile switch for security
    }

    // ==========================================
    // SESSION DATA PROTECTION TESTS
    // ==========================================

    /**
     * Test that sensitive data is not stored in session
     */
    public function test_sensitive_data_not_stored_in_session()
    {
        $user = User::factory()->create([
            'email' => 'user@test.com',
            'password' => Hash::make('secret-password'),
        ]);

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

        $sessionData = Session::all();

        // Passwords should never be in session
        $sessionString = json_encode($sessionData);
        $this->assertStringNotContainsString('secret-password', $sessionString);
        $this->assertStringNotContainsString('password', strtolower($sessionString));

        // Email might be OK in session depending on implementation
        // but password hash should definitely not be there
        foreach ($sessionData as $key => $value) {
            if (is_string($value)) {
                $this->assertStringNotContainsString('$2y$', $value, 'Password hash should not be in session');
            }
        }
    }

    /**
     * Test that active profile data is stored correctly in session
     */
    public function test_active_profile_data_stored_correctly()
    {
        $user = User::factory()->create();
        $organization = Organization::factory()->create(['name' => 'Test Org']);
        $user->organizations()->attach($organization->id);

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

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

        // Session should contain profile information but not sensitive data
        $this->assertEquals(get_class($organization), session('activeProfileType'));
        $this->assertEquals($organization->id, session('activeProfileId'));
        $this->assertEquals($organization->name, session('activeProfileName'));

        // Session should have last activity time
        $this->assertNotNull(session('last_activity'));

        // Session should NOT contain password
        $this->assertNull(session('activeProfilePassword'));
    }

    // ==========================================
    // SESSION CLEARING TESTS
    // ==========================================

    /**
     * Test that logging out clears authentication and session data
     */
    public function test_logging_out_clears_authentication_and_session()
    {
        $user = User::factory()->create([
            'email' => 'user@test.com',
            'password' => Hash::make('password123'),
        ]);

        $this->withoutMiddleware(\App\Http\Middleware\VerifyCsrfToken::class);

        // Login
        $this->post(route('login'), [
            'name' => $user->email,
            'password' => 'password123',
        ]);

        $this->assertTrue(Auth::guard('web')->check());

        // Logout
        $this->post(route('logout'));

        // Should no longer be authenticated
        $this->assertFalse(Auth::guard('web')->check());
    }

    /**
     * Test that profile switch session variables are cleared after login
     */
    public function test_profile_switch_session_variables_cleared()
    {
        $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);

        // Set profile switch intent
        session([
            'intended_profile_switch_type' => 'Bank',
            'intended_profile_switch_id' => $bank->id,
            'bank_login_intended_url' => '/test/url',
        ]);

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

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

    // ==========================================
    // SESSION PERSISTENCE TESTS
    // ==========================================

    /**
     * Test that authentication persists across multiple requests
     */
    public function test_authentication_persists_across_requests()
    {
        $user = User::factory()->create();

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

        // Make multiple requests
        $this->get(route('main'))->assertStatus(200);
        $this->assertTrue(Auth::guard('web')->check());

        // Second request
        $this->get(route('main'))->assertStatus(200);
        $this->assertTrue(Auth::guard('web')->check());

        // Third request
        $this->get(route('main'))->assertStatus(200);
        $this->assertTrue(Auth::guard('web')->check());

        // All requests should maintain authentication
        $this->assertEquals($user->id, Auth::guard('web')->id());
    }

    /**
     * Test that multiple guards can be authenticated simultaneously
     */
    public function test_multiple_guards_authenticated_simultaneously()
    {
        $user = User::factory()->create();
        $organization = Organization::factory()->create();
        $user->organizations()->attach($organization->id);

        // Authenticate on web guard
        Auth::guard('web')->login($user);

        // Authenticate on organization guard
        Auth::guard('organization')->login($organization);
        session(['active_guard' => 'organization']);

        // Both should be authenticated
        $this->assertTrue(Auth::guard('web')->check());
        $this->assertTrue(Auth::guard('organization')->check());

        // Make a request - both should remain authenticated
        $this->get(route('main'));

        $this->assertTrue(Auth::guard('web')->check());
        $this->assertTrue(Auth::guard('organization')->check());
    }

    // ==========================================
    // ACTIVE GUARD TRACKING TESTS
    // ==========================================

    /**
     * Test that active guard is tracked in session
     */
    public function test_active_guard_tracked_in_session()
    {
        $user = User::factory()->create();
        $organization = Organization::factory()->create();
        $user->organizations()->attach($organization->id);

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

        // Initially on web guard
        // Session might not have active_guard set initially

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

        $this->assertEquals('organization', session('active_guard'));
    }

    /**
     * Test that active guard changes when switching profiles
     */
    public function test_active_guard_changes_when_switching_profiles()
    {
        $user = User::factory()->create();
        $organization = Organization::factory()->create();
        $bank = Bank::factory()->create(['password' => Hash::make('password')]);

        $user->organizations()->attach($organization->id);
        $user->banksManaged()->attach($bank->id);

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

        // Switch to organization
        $this->get(route('organization.direct-login', ['organizationId' => $organization->id]));
        $this->assertEquals('organization', session('active_guard'));

        // Switch to bank
        session([
            'intended_profile_switch_type' => 'Bank',
            'intended_profile_switch_id' => $bank->id,
        ]);
        $this->post(route('bank.login.post'), ['password' => 'password']);

        $this->assertEquals('bank', session('active_guard'));
    }

    // ==========================================
    // SESSION SECURITY EDGE CASES
    // ==========================================

    /**
     * Test that unauthenticated users have no authentication data in session
     */
    public function test_unauthenticated_users_have_no_auth_data_in_session()
    {
        $response = $this->get('/');

        $sessionData = Session::all();

        // Should not have authentication-related session keys
        $this->assertArrayNotHasKey('activeProfileType', $sessionData);
        $this->assertArrayNotHasKey('activeProfileId', $sessionData);
        $this->assertArrayNotHasKey('activeProfileName', $sessionData);
    }

    /**
     * Test that session data is properly isolated between users
     */
    public function test_session_data_isolated_between_users()
    {
        $user1 = User::factory()->create();
        $user2 = User::factory()->create();

        // Login as user1
        $this->actingAs($user1, 'web');
        $sessionId1 = Session::getId();

        // Logout
        Auth::guard('web')->logout();
        Session::flush();

        // Login as user2
        $this->actingAs($user2, 'web');
        $sessionId2 = Session::getId();

        // Session IDs should be different
        $this->assertNotEquals($sessionId1, $sessionId2);

        // Should be authenticated as user2, not user1
        $this->assertEquals($user2->id, Auth::guard('web')->id());
        $this->assertNotEquals($user1->id, Auth::guard('web')->id());
    }

    /**
     * Test that session regeneration prevents session fixation attacks
     */
    public function test_session_regeneration_prevents_fixation()
    {
        $user = User::factory()->create([
            'email' => 'user@test.com',
            'password' => Hash::make('password123'),
        ]);

        $this->withoutMiddleware(\App\Http\Middleware\VerifyCsrfToken::class);

        // Attacker sets a known session ID
        $this->startSession();
        $attackerSessionId = Session::getId();

        // Victim logs in with that session
        $this->post(route('login'), [
            'name' => $user->email,
            'password' => 'password123',
        ]);

        // Session ID should have changed, preventing attacker from hijacking
        $newSessionId = Session::getId();
        $this->assertNotEquals($attackerSessionId, $newSessionId,
            'Session ID should change on login to prevent fixation attacks');
    }
}
