<?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;

/**
 * Multi-Guard Authentication Security Tests
 *
 * Tests the 4-guard authentication system:
 * - web: Individual user accounts
 * - organization: Non-profit organization profiles
 * - bank: Timebank operator profiles
 * - admin: Administrative profiles
 *
 * @group security
 * @group critical
 * @group authentication
 */
class MultiGuardAuthenticationTest extends TestCase
{
    use RefreshDatabase;

    // ==========================================
    // BASIC GUARD AUTHENTICATION TESTS
    // ==========================================

    /**
     * Test that a user can authenticate on the web guard
     */
    public function test_user_can_authenticate_on_web_guard()
    {
        $user = User::factory()->create([
            'email' => 'user@test.com',
            'password' => Hash::make('password123'),
        ]);

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

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

        // Assert authenticated on web guard
        $this->assertTrue(Auth::guard('web')->check());
        $this->assertEquals($user->id, Auth::guard('web')->id());

        // Assert not authenticated on other guards
        $this->assertFalse(Auth::guard('organization')->check());
        $this->assertFalse(Auth::guard('bank')->check());
        $this->assertFalse(Auth::guard('admin')->check());
    }

    /**
     * Test that authentication fails with invalid credentials
     */
    public function test_cannot_authenticate_with_invalid_credentials()
    {
        $user = User::factory()->create([
            'email' => 'user@test.com',
            'password' => Hash::make('correct-password'),
        ]);

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

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

        // Assert not authenticated on any guard
        $this->assertFalse(Auth::guard('web')->check());
        $this->assertFalse(Auth::guard('organization')->check());
        $this->assertFalse(Auth::guard('bank')->check());
        $this->assertFalse(Auth::guard('admin')->check());
    }

    /**
     * Test that a user cannot authenticate on wrong guard
     */
    public function test_cannot_authenticate_user_on_organization_guard()
    {
        $user = User::factory()->create([
            'password' => Hash::make('password123'),
        ]);

        // Attempt to manually authenticate user on organization guard (should not work)
        $result = Auth::guard('organization')->attempt([
            'email' => $user->email,
            'password' => 'password123',
        ]);

        $this->assertFalse($result);
        $this->assertFalse(Auth::guard('organization')->check());
    }

    // ==========================================
    // GUARD ISOLATION TESTS
    // ==========================================

    /**
     * Test that web guard remains active when elevated guard is active
     */
    public function test_web_guard_remains_active_with_elevated_guard()
    {
        $user = User::factory()->create();
        $organization = Organization::factory()->create([
            'password' => Hash::make('org-password'),
        ]);

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

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

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

        // Both guards should be active
        $this->assertTrue(Auth::guard('web')->check(), 'Web guard should remain active');
        $this->assertTrue(Auth::guard('organization')->check(), 'Organization guard should be active');
        $this->assertEquals('organization', session('active_guard'));
    }

    /**
     * Test that only one elevated guard can be active at a time
     */
    public function test_only_one_elevated_guard_active_at_time()
    {
        $user = User::factory()->create();
        $organization = Organization::factory()->create([
            'password' => Hash::make('password'),
        ]);
        $bank = Bank::factory()->create([
            'password' => Hash::make('password'),
        ]);

        // Link user to both profiles
        $user->organizations()->attach($organization->id);
        $user->banksManaged()->attach($bank->id);

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

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

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

        // Now use SwitchGuardTrait to switch to bank (should logout organization)
        $controller = new class {
            use \App\Traits\SwitchGuardTrait;
        };

        $controller->switchGuard('bank', $bank);

        // Only bank guard should be active among elevated guards
        $this->assertFalse(Auth::guard('organization')->check(), 'Organization guard should be logged out');
        $this->assertTrue(Auth::guard('bank')->check(), 'Bank guard should be active');
        $this->assertTrue(Auth::guard('web')->check(), 'Web guard should remain active');
        $this->assertEquals('bank', session('active_guard'));
    }

    /**
     * Test switching guard logs out other elevated guards
     */
    public function test_switching_guard_logs_out_other_elevated_guards()
    {
        $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')]);

        // Link user to all profiles
        $user->organizations()->attach($organization->id);
        $user->banksManaged()->attach($bank->id);
        $user->admins()->attach($admin->id);

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

        // Use SwitchGuardTrait
        $controller = new class {
            use \App\Traits\SwitchGuardTrait;
        };

        // Switch to organization
        $controller->switchGuard('organization', $organization);
        $this->assertTrue(Auth::guard('organization')->check());
        $this->assertFalse(Auth::guard('bank')->check());
        $this->assertFalse(Auth::guard('admin')->check());

        // Switch to bank
        $controller->switchGuard('bank', $bank);
        $this->assertFalse(Auth::guard('organization')->check(), 'Organization should be logged out');
        $this->assertTrue(Auth::guard('bank')->check());
        $this->assertFalse(Auth::guard('admin')->check());

        // Switch to admin
        $controller->switchGuard('admin', $admin);
        $this->assertFalse(Auth::guard('organization')->check());
        $this->assertFalse(Auth::guard('bank')->check(), 'Bank should be logged out');
        $this->assertTrue(Auth::guard('admin')->check());
    }

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

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

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

        $controller = new class {
            use \App\Traits\SwitchGuardTrait;
        };

        $controller->switchGuard('organization', $organization);

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

    /**
     * Test that logging out non-web guards sets active guard to web
     */
    public function test_logout_non_web_guards_sets_active_guard_to_web()
    {
        $user = User::factory()->create();
        $organization = Organization::factory()->create(['password' => Hash::make('password')]);
        $user->organizations()->attach($organization->id);

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

        $controller = new class {
            use \App\Traits\SwitchGuardTrait;
        };

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

        // Logout from non-web guards
        $controller->logoutNonWebGuards();

        $this->assertEquals('web', session('active_guard'));
        $this->assertFalse(Auth::guard('organization')->check());
        $this->assertTrue(Auth::guard('web')->check());
    }

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

    /**
     * Test that guest cannot access authenticated routes
     */
    public function test_guest_cannot_access_authenticated_routes()
    {
        $response = $this->get(route('main'));

        $response->assertRedirect();
    }

    /**
     * Test that authenticated user can access web guard routes
     */
    public function test_authenticated_user_can_access_web_guard_routes()
    {
        $user = User::factory()->create();

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

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

        $response->assertStatus(200);
    }

    /**
     * Test authentication persists across requests
     */
    public function test_authentication_persists_across_requests()
    {
        $user = User::factory()->create([
            'email' => 'user@test.com',
            'password' => Hash::make('password123'),
        ]);

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

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

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

        // Second request - should still be authenticated
        $response = $this->get(route('main'));

        $this->assertTrue(Auth::guard('web')->check());
        $response->assertStatus(200);
    }

    /**
     * Test that logging out clears authentication
     */
    public function test_logging_out_clears_authentication()
    {
        $user = User::factory()->create();

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

        Auth::guard('web')->logout();

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

    // ==========================================
    // CYCLOS LEGACY PASSWORD MIGRATION TESTS
    // ==========================================

    /**
     * Test that legacy Cyclos passwords are migrated on successful login
     */
    public function test_cyclos_password_migrated_on_successful_organization_login()
    {
        $user = User::factory()->create();
        $organization = Organization::factory()->create([
            'cyclos_salt' => 'legacy_salt',
            'password' => strtolower(hash('sha256', 'legacy_salt' . 'old-password')),
        ]);

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

        // Store intent in session
        session([
            'intended_profile_switch_type' => 'Organization',
            'intended_profile_switch_id' => $organization->id,
        ]);

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

        // Attempt login with old password
        $response = $this->post(route('organization.login.post'), [
            'password' => 'old-password',
        ]);

        // Refresh organization from database
        $organization->refresh();

        // Assert password was rehashed and salt removed
        $this->assertNull($organization->cyclos_salt, 'Cyclos salt should be removed');
        $this->assertTrue(Hash::check('old-password', $organization->password), 'Password should be rehashed with Laravel Hash');

        // Assert authentication succeeded
        $this->assertTrue(Auth::guard('organization')->check());
    }
}
