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

/**
 * Direct Login Routes Security Tests
 *
 * Tests the security of direct login routes used in emails and external links:
 * - /user/{userId}/login
 * - /organization/{organizationId}/login
 * - /bank/{bankId}/login
 * - /admin/{adminId}/login
 *
 * Tests layered authentication, ownership verification, and intended URL handling.
 *
 * @group security
 * @group critical
 * @group authentication
 * @group direct-login
 */
class DirectLoginRoutesSecurityTest extends TestCase
{
    use RefreshDatabase;

    // ==========================================
    // USER DIRECT LOGIN TESTS
    // ==========================================

    /**
     * Test user direct login requires authentication
     */
    public function test_user_direct_login_requires_authentication()
    {
        $user = User::factory()->create();

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

        // Should redirect to login with intended URL (may be localized like /en/login)
        $response->assertRedirect();
        $redirectLocation = $response->headers->get('Location');
        $this->assertTrue(
            str_contains($redirectLocation, 'login') || str_contains($redirectLocation, '/en') || str_contains($redirectLocation, '/nl'),
            "Expected redirect to login page, got: {$redirectLocation}"
        );
        // Session url.intended may be set by controller
    }

    /**
     * Test user direct login validates ownership
     */
    public function test_user_direct_login_validates_ownership()
    {
        $user1 = User::factory()->create();
        $user2 = User::factory()->create();

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

        // Try to access user2's profile
        $response = $this->get(route('user.direct-login', ['userId' => $user2->id]));

        $response->assertStatus(403);
        $response->assertSee('You do not have access to this profile');
    }

    /**
     * Test user direct login redirects to intended URL
     */
    public function test_user_direct_login_redirects_to_intended_url()
    {
        $user = User::factory()->create();

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

        $intendedUrl = route('main');
        $response = $this->get(route('user.direct-login', [
            'userId' => $user->id,
            'intended' => $intendedUrl,
        ]));

        $response->assertRedirect($intendedUrl);
    }

    /**
     * Test user direct login returns 404 for nonexistent user
     */
    public function test_user_direct_login_returns_404_for_nonexistent_user()
    {
        $this->actingAs(User::factory()->create(), 'web');

        $response = $this->get(route('user.direct-login', ['userId' => 99999]));

        $response->assertStatus(404);
    }

    // ==========================================
    // ORGANIZATION DIRECT LOGIN TESTS
    // ==========================================

    /**
     * Test organization direct login requires user authentication first
     */
    public function test_organization_direct_login_requires_user_authentication()
    {
        $organization = Organization::factory()->create();

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

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

        // Should store intended URL in session
        $this->assertNotNull(session('url.intended'));
        $this->assertStringContainsString("/organization/{$organization->id}/login", session('url.intended'));
    }

    /**
     * Test organization direct login validates ownership
     */
    public function test_organization_direct_login_validates_ownership()
    {
        $user = User::factory()->create();
        $ownedOrg = Organization::factory()->create();
        $unownedOrg = Organization::factory()->create();

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

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

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

        $response->assertStatus(403);
        $response->assertSee('You do not have access to this organization');
    }

    /**
     * Test organization direct login switches guard directly (passwordless)
     */
    public function test_organization_direct_login_switches_guard_passwordless()
    {
        $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]));

        // Should switch to organization guard immediately without password
        $this->assertTrue(Auth::guard('organization')->check());
        $this->assertEquals($organization->id, Auth::guard('organization')->id());

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

    /**
     * Test organization direct login redirects to intended URL
     */
    public function test_organization_direct_login_redirects_to_intended_url()
    {
        $user = User::factory()->create();
        $organization = Organization::factory()->create();
        $user->organizations()->attach($organization->id);

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

        $intendedUrl = '/some/deep/link';

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

        $response->assertRedirect($intendedUrl);
    }

    /**
     * Test organization direct login returns 404 for nonexistent organization
     */
    public function test_organization_direct_login_returns_404_for_nonexistent_profile()
    {
        $this->actingAs(User::factory()->create(), 'web');

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

        $response->assertStatus(404);
    }

    // ==========================================
    // BANK DIRECT LOGIN TESTS
    // ==========================================

    /**
     * Test bank direct login requires user authentication first
     */
    public function test_bank_direct_login_requires_user_authentication()
    {
        $bank = Bank::factory()->create();

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

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

    /**
     * Test bank direct login validates bank manager relationship
     */
    public function test_bank_direct_login_validates_bank_manager_relationship()
    {
        $user = User::factory()->create();
        $managedBank = Bank::factory()->create();
        $unmanagedBank = Bank::factory()->create();

        $user->banksManaged()->attach($managedBank->id);

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

        // Try to access unmanaged bank
        $response = $this->get(route('bank.direct-login', ['bankId' => $unmanagedBank->id]));

        $response->assertStatus(403);
        $response->assertSee('You do not have access to this bank');
    }

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

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

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

        // Should redirect to bank login form (not switch immediately)
        $response->assertRedirect(route('bank.login'));

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

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

    /**
     * Test bank direct login with intended URL stores it in session
     */
    public function test_bank_direct_login_stores_intended_url_in_session()
    {
        $user = User::factory()->create();
        $bank = Bank::factory()->create();
        $user->banksManaged()->attach($bank->id);

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

        $intendedUrl = '/deep/link/to/transaction';

        $this->get(route('bank.direct-login', [
            'bankId' => $bank->id,
            'intended' => $intendedUrl,
        ]));

        $this->assertEquals($intendedUrl, session('bank_login_intended_url'));
    }

    /**
     * Test bank login fails with wrong password
     */
    public function test_bank_direct_login_fails_with_wrong_password()
    {
        $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());
    }

    // ==========================================
    // ADMIN DIRECT LOGIN TESTS
    // ==========================================

    /**
     * Test admin direct login requires user authentication first
     */
    public function test_admin_direct_login_requires_user_authentication()
    {
        $admin = Admin::factory()->create();

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

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

    /**
     * Test admin direct login validates admin user relationship
     */
    public function test_admin_direct_login_validates_admin_user_relationship()
    {
        $user = User::factory()->create();
        $ownedAdmin = Admin::factory()->create();
        $unownedAdmin = Admin::factory()->create();

        $user->admins()->attach($ownedAdmin->id);

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

        // Try to access unowned admin
        $response = $this->get(route('admin.direct-login', ['adminId' => $unownedAdmin->id]));

        $response->assertStatus(403);
        $response->assertSee('You do not have access to this admin profile');
    }

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

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

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

        // Should redirect to admin login form
        $response->assertRedirect(route('admin.login'));

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

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

    /**
     * Test admin direct login fails for non-admin users
     */
    public function test_admin_direct_login_fails_for_non_admin_users()
    {
        $regularUser = User::factory()->create();
        $admin = Admin::factory()->create();

        // Regular user not linked to admin
        $this->actingAs($regularUser, 'web');

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

        $response->assertStatus(403);
    }

    // ==========================================
    // SESSION VARIABLE CLEANUP TESTS
    // ==========================================

    /**
     * Test that session variables are cleared after successful authentication
     */
    public function test_direct_login_session_variables_cleared_after_completion()
    {
        $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 up direct login
        $this->get(route('bank.direct-login', [
            'bankId' => $bank->id,
            'intended' => '/target/url',
        ]));

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

        // Complete authentication
        $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'));
    }

    // ==========================================
    // INTENDED URL VALIDATION TESTS
    // ==========================================

    /**
     * Test that intended URL is properly encoded and decoded
     */
    public function test_intended_url_properly_encoded_and_decoded()
    {
        $user = User::factory()->create();
        $organization = Organization::factory()->create();
        $user->organizations()->attach($organization->id);

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

        $intendedUrl = '/path/with spaces/and?query=params';
        $encodedUrl = urlencode($intendedUrl);

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

        // URL should be properly handled
        $response->assertRedirect($intendedUrl);
    }

    /**
     * Test that direct login handles missing intended URL gracefully
     */
    public function test_direct_login_handles_missing_intended_url_gracefully()
    {
        $user = User::factory()->create();
        $organization = Organization::factory()->create();
        $user->organizations()->attach($organization->id);

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

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

        // Should redirect to default (main page)
        $response->assertRedirect(route('main'));
    }

    // ==========================================
    // MULTI-LAYER AUTHENTICATION FLOW TESTS
    // ==========================================

    /**
     * Test complete flow: unauthenticated -> user login -> profile login
     */
    public function test_complete_layered_authentication_flow()
    {
        $user = User::factory()->create([
            'email' => 'user@test.com',
            'password' => Hash::make('user-password'),
        ]);
        $bank = Bank::factory()->create(['password' => Hash::make('bank-password')]);
        $user->banksManaged()->attach($bank->id);

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

        // Step 1: Try to access bank direct login while not authenticated
        $response = $this->get(route('bank.direct-login', [
            'bankId' => $bank->id,
            'intended' => '/final/destination',
        ]));

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

        // Step 2: Authenticate as user
        $this->post(route('login'), [
            'name' => $user->email,
            'password' => 'user-password',
        ]);

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

        // Step 3: Now access bank direct login (should redirect to bank password form)
        $response = $this->get(route('bank.direct-login', [
            'bankId' => $bank->id,
            'intended' => '/final/destination',
        ]));

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

        // Step 4: Enter bank password
        $response = $this->post(route('bank.login.post'), [
            'password' => 'bank-password',
        ]);

        // Should be authenticated on bank guard and redirected to final destination
        $this->assertTrue(Auth::guard('bank')->check());
        $response->assertRedirect('/final/destination');
    }
}
