<?php

namespace Tests\Feature\Security\Authorization;

use App\Helpers\ProfileAuthorizationHelper;
use App\Models\Admin;
use App\Models\Bank;
use App\Models\Organization;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

/**
 * ProfileAuthorizationHelper Multi-Guard Tests
 *
 * Tests that ProfileAuthorizationHelper correctly validates profile access
 * across all authentication guards (web, admin, organization, bank).
 *
 * @group security
 * @group authorization
 * @group multi-guard
 * @group critical
 */
class ProfileAuthorizationHelperTest extends TestCase
{
    use RefreshDatabase;

    /**
     * Test user can access their own user profile
     *
     * @test
     */
    public function user_can_access_own_user_profile()
    {
        $user = User::factory()->create();
        $this->actingAs($user, 'web');

        $result = ProfileAuthorizationHelper::can($user);

        $this->assertTrue($result);
    }

    /**
     * Test user cannot access another user's profile
     *
     * @test
     */
    public function user_cannot_access_another_users_profile()
    {
        $user1 = User::factory()->create();
        $user2 = User::factory()->create();
        $this->actingAs($user1, 'web');

        $result = ProfileAuthorizationHelper::can($user2);

        $this->assertFalse($result);
    }

    /**
     * Test admin can access their own admin profile
     *
     * @test
     */
    public function admin_can_access_own_admin_profile()
    {
        // Create admin and link to user
        $user = User::factory()->create();
        $admin = Admin::factory()->create();
        $admin->users()->attach($user->id);

        $this->actingAs($admin, 'admin');

        $result = ProfileAuthorizationHelper::can($admin);

        $this->assertTrue($result, 'Admin should be able to access their own admin profile');
    }

    /**
     * Test organization can access their own organization profile
     *
     * @test
     */
    public function organization_can_access_own_organization_profile()
    {
        // Create organization and link to user
        $user = User::factory()->create();
        $organization = Organization::factory()->create();
        $organization->users()->attach($user->id);

        $this->actingAs($organization, 'organization');

        $result = ProfileAuthorizationHelper::can($organization);

        $this->assertTrue($result, 'Organization should be able to access their own profile');
    }

    /**
     * Test bank can access their own bank profile
     *
     * @test
     */
    public function bank_can_access_own_bank_profile()
    {
        // Create bank and link to user
        $user = User::factory()->create();
        $bank = Bank::factory()->create();
        $bank->managers()->attach($user->id);

        $this->actingAs($bank, 'bank');

        $result = ProfileAuthorizationHelper::can($bank);

        $this->assertTrue($result, 'Bank should be able to access their own bank profile');
    }

    /**
     * Test user can access organization they are member of (for profile switching)
     *
     * @test
     */
    public function user_can_access_organization_they_are_member_of()
    {
        $user = User::factory()->create();
        $organization = Organization::factory()->create();
        $user->organizations()->attach($organization->id);

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

        // Use userOwnsProfile() for cross-guard ownership checks (profile switching scenario)
        $result = ProfileAuthorizationHelper::userOwnsProfile($organization);

        $this->assertTrue($result);
    }

    /**
     * Test user cannot access organization they are not member of
     *
     * @test
     */
    public function user_cannot_access_organization_they_are_not_member_of()
    {
        $user = User::factory()->create();
        $organization = Organization::factory()->create();
        // User is NOT attached to organization

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

        $result = ProfileAuthorizationHelper::can($organization);

        $this->assertFalse($result);
    }

    /**
     * Test user can access bank they manage (for profile switching)
     *
     * @test
     */
    public function user_can_access_bank_they_manage()
    {
        $user = User::factory()->create();
        $bank = Bank::factory()->create();
        $user->banksManaged()->attach($bank->id);

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

        // Use userOwnsProfile() for cross-guard ownership checks (profile switching scenario)
        $result = ProfileAuthorizationHelper::userOwnsProfile($bank);

        $this->assertTrue($result);
    }

    /**
     * Test user cannot access bank they don't manage
     *
     * @test
     */
    public function user_cannot_access_bank_they_dont_manage()
    {
        $user = User::factory()->create();
        $bank = Bank::factory()->create();
        // User is NOT attached to bank

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

        $result = ProfileAuthorizationHelper::can($bank);

        $this->assertFalse($result);
    }

    /**
     * Test user can access admin profile they are linked to (for profile switching)
     *
     * @test
     */
    public function user_can_access_admin_profile_they_are_linked_to()
    {
        $user = User::factory()->create();
        $admin = Admin::factory()->create();
        $user->admins()->attach($admin->id);

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

        // Use userOwnsProfile() for cross-guard ownership checks (profile switching scenario)
        $result = ProfileAuthorizationHelper::userOwnsProfile($admin);

        $this->assertTrue($result);
    }

    /**
     * Test user cannot access admin profile they are not linked to
     *
     * @test
     */
    public function user_cannot_access_admin_profile_they_are_not_linked_to()
    {
        $user = User::factory()->create();
        $admin = Admin::factory()->create();
        // User is NOT attached to admin

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

        $result = ProfileAuthorizationHelper::can($admin);

        $this->assertFalse($result);
    }

    /**
     * Test admin cannot directly switch to organization (must go through web user)
     *
     * In the application, profile switching flow is: User → Admin → back to User → Organization
     * Direct Admin → Organization switching is not supported.
     *
     * @test
     */
    public function admin_can_access_organization_via_linked_user()
    {
        // Create user linked to both admin and organization
        $user = User::factory()->create();
        $admin = Admin::factory()->create();
        $organization = Organization::factory()->create();

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

        $this->actingAs($admin, 'admin');

        // userOwnsProfile() only checks web guard, so this should return false
        // Profile switching requires being on web guard first
        $result = ProfileAuthorizationHelper::userOwnsProfile($organization);

        $this->assertFalse($result, 'Admin cannot directly switch to organization without going through web user first');
    }

    /**
     * Test organization cannot directly switch to bank (must go through web user)
     *
     * In the application, profile switching flow is: User → Organization → back to User → Bank
     * Direct Organization → Bank switching is not supported.
     *
     * @test
     */
    public function organization_can_access_bank_via_linked_user()
    {
        // Create user linked to both organization and bank
        $user = User::factory()->create();
        $organization = Organization::factory()->create();
        $bank = Bank::factory()->create();

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

        $this->actingAs($organization, 'organization');

        // userOwnsProfile() only checks web guard, so this should return false
        // Profile switching requires being on web guard first
        $result = ProfileAuthorizationHelper::userOwnsProfile($bank);

        $this->assertFalse($result, 'Organization cannot directly switch to bank without going through web user first');
    }

    /**
     * Test admin cannot access unrelated organization
     *
     * @test
     */
    public function admin_cannot_access_unrelated_organization()
    {
        $user = User::factory()->create();
        $admin = Admin::factory()->create();
        $organization = Organization::factory()->create();

        $admin->users()->attach($user->id);
        // User is NOT linked to organization

        $this->actingAs($admin, 'admin');

        $result = ProfileAuthorizationHelper::can($organization);

        $this->assertFalse($result, 'Admin should NOT access unrelated organization');
    }

    /**
     * Test authorize method throws 403 for unauthorized access
     *
     * @test
     */
    public function authorize_method_throws_403_for_unauthorized_access()
    {
        $this->expectException(\Symfony\Component\HttpKernel\Exception\HttpException::class);
        $this->expectExceptionMessage('Unauthorized');

        $user1 = User::factory()->create();
        $user2 = User::factory()->create();
        $this->actingAs($user1, 'web');

        ProfileAuthorizationHelper::authorize($user2);
    }

    /**
     * Test unauthenticated access is denied
     *
     * @test
     */
    public function unauthenticated_access_is_denied()
    {
        $user = User::factory()->create();

        $result = ProfileAuthorizationHelper::can($user);

        $this->assertFalse($result);
    }

    /**
     * Test authorize method throws 401 for unauthenticated access
     *
     * @test
     */
    public function authorize_method_throws_401_for_unauthenticated()
    {
        $this->expectException(\Symfony\Component\HttpKernel\Exception\HttpException::class);
        $this->expectExceptionMessage('Authentication required');

        $user = User::factory()->create();

        ProfileAuthorizationHelper::authorize($user);
    }

    /**
     * Test admin cannot access another admin profile
     *
     * @test
     */
    public function admin_cannot_access_another_admin_profile()
    {
        $user1 = User::factory()->create();
        $admin1 = Admin::factory()->create();
        $admin2 = Admin::factory()->create();

        $admin1->users()->attach($user1->id);

        $this->actingAs($admin1, 'admin');

        $result = ProfileAuthorizationHelper::can($admin2);

        $this->assertFalse($result, 'Admin should NOT access another admin profile');
    }
}
