<?php

declare(strict_types=1);

namespace Tests\Unit\Model;

use App\Enums\Role;
use App\Models\Member;
use App\Models\Organization;
use App\Models\Passport\AuthCode;
use App\Models\Passport\Client;
use App\Models\Passport\Token;
use App\Models\ProjectMember;
use App\Models\TimeEntry;
use App\Models\User;
use App\Providers\Filament\AdminPanelProvider;
use Filament\Panel;
use Illuminate\Support\Facades\Config;
use PHPUnit\Framework\Attributes\CoversClass;

#[CoversClass(User::class)]
class UserModelTest extends ModelTestAbstract
{
    public function test_normal_user_can_not_access_admin_panel(): void
    {
        // Arrange
        Config::set('auth.super_admins', ['some@email.test', 'other@email.test']);
        $user = User::factory()->create();
        $panelProvider = new AdminPanelProvider(app());
        $mainPanel = $panelProvider->panel(Panel::make());

        // Act
        $canAccess = $user->canAccessPanel($mainPanel);

        // Assert
        $this->assertFalse($canAccess);
    }

    public function test_user_in_super_admin_config_can_access_admin_panel(): void
    {
        // Arrange
        Config::set('auth.super_admins', ['some@email.test', 'other@email.test']);
        $user = User::factory()->create([
            'email' => 'some@email.test',
        ]);
        $panelProvider = new AdminPanelProvider(app());
        $mainPanel = $panelProvider->panel(Panel::make());

        // Act
        $canAccess = $user->canAccessPanel($mainPanel);

        // Assert
        $this->assertTrue($canAccess);
    }

    public function test_scope_belongs_to_organization_returns_only_users_of_organization_including_owners(): void
    {
        // Arrange
        $owner = User::factory()->create();
        $organization = Organization::factory()->withOwner($owner)->create();
        $user = User::factory()->create();
        $user->organizations()->attach($organization, [
            'role' => Role::Employee->value,
        ]);
        $owner->organizations()->attach($organization, [
            'role' => Role::Owner->value,
        ]);
        $otherOrganization = Organization::factory()->create();
        $otherUser = User::factory()->create();
        $otherUser->organizations()->attach($otherOrganization, [
            'role' => Role::Employee->value,
        ]);

        // Act
        $users = User::query()
            ->belongsToOrganization($organization)
            ->get();

        // Assert
        $this->assertCount(2, $users);
        $userIds = $users->pluck('id')->toArray();
        $this->assertContains($user->getKey(), $userIds);
        $this->assertContains($owner->getKey(), $userIds);
    }

    public function test_is_member_of_organization_returns_true_for_user_attached_to_organization(): void
    {
        // Arrange
        $organization = Organization::factory()->create();
        $user = User::factory()->create();
        $user->organizations()->attach($organization, [
            'role' => Role::Employee->value,
        ]);

        // Act
        $isMemberOfOrganization = $user->isMemberOfOrganization($organization);

        // Assert
        $this->assertTrue($isMemberOfOrganization);
    }

    public function test_is_member_of_organization_returns_false_for_user_not_attached_to_organization(): void
    {
        // Arrange
        $organization = Organization::factory()->create();
        $user = User::factory()->create();

        // Act
        $isMemberOfOrganization = $user->isMemberOfOrganization($organization);

        // Assert
        $this->assertFalse($isMemberOfOrganization);
    }

    public function test_is_member_of_organization_uses_loaded_organizations_relation(): void
    {
        // Arrange
        $organization = Organization::factory()->create();
        $user = User::factory()
            ->attachToOrganization($organization, [
                'role' => Role::Employee->value,
            ])
            ->create();
        $user->load('organizations');

        // Act
        $isMemberOfOrganization = $user->isMemberOfOrganization($organization);

        // Assert
        $this->assertTrue($isMemberOfOrganization);
    }

    public function test_is_member_of_organization_does_not_query_when_organizations_relation_is_loaded(): void
    {
        // Arrange
        $organization = Organization::factory()->create();
        $user = User::factory()->create();
        $user->load('organizations');
        $user->organizations()->attach($organization, [
            'role' => Role::Employee->value,
        ]);

        // Act
        $isMemberOfOrganization = $user->isMemberOfOrganization($organization);

        // Assert
        $this->assertFalse($isMemberOfOrganization);
    }

    public function test_it_has_many_time_entries(): void
    {
        // Arrange
        $user = User::factory()->create();
        $timeEntries = TimeEntry::factory()->forUser($user)->createMany(3);

        // Act
        $user->refresh();
        $timeEntriesRel = $user->timeEntries;

        // Assert
        $this->assertNotNull($timeEntriesRel);
        $this->assertCount(3, $timeEntriesRel);
        $this->assertEqualsCanonicalizing(
            $timeEntries->pluck('id')->toArray(),
            $timeEntriesRel->pluck('id')->toArray()
        );
    }

    public function test_it_has_many_project_members(): void
    {
        // Arrange
        $user = User::factory()->create();
        $otherUser = User::factory()->create();
        $member = Member::factory()->forUser($user)->create();
        $otherMember = Member::factory()->forUser($otherUser)->create();
        $projectMembers = ProjectMember::factory()->forMember($member)->createMany(3);
        $otherProjectMembers = ProjectMember::factory()->forMember($otherMember)->createMany(3);

        // Act
        $user->refresh();
        $projectMembersRel = $user->projectMembers;

        // Assert
        $this->assertNotNull($projectMembersRel);
        $this->assertCount(3, $projectMembersRel);
        $this->assertEqualsCanonicalizing(
            $projectMembers->pluck('id')->toArray(),
            $projectMembersRel->pluck('id')->toArray()
        );
    }

    public function test_scope_active_returns_only_non_placeholder_users(): void
    {
        // Arrange
        $placeholder = User::factory()->create([
            'is_placeholder' => true,
        ]);
        $user = User::factory()->create([
            'is_placeholder' => false,
        ]);

        // Act
        $activeUsers = User::query()->active()->get();

        // Assert
        $this->assertCount(1, $activeUsers);
        $this->assertTrue($activeUsers->first()->is($user));
    }

    public function test_it_has_many_access_tokens(): void
    {
        // Arrange
        $user = User::factory()->create();
        $client = new Client;
        $client->name = 'desktop';
        $client->redirect_uris = ['solidtime://oauth/callback'];
        $client->grant_types = [];
        $client->revoked = false;
        $client->save();
        $token = new Token;
        $token->id = 'some-id';
        $token->user_id = $user->getKey();
        $token->client_id = $client->getKey();
        $token->revoked = false;
        $token->save();

        // Act
        $user->refresh();
        $tokensRel = $user->accessTokens;

        // Assert
        $this->assertNotNull($tokensRel);
        $this->assertCount(1, $tokensRel);
        $this->assertEqualsCanonicalizing(
            [$token->getKey()],
            $tokensRel->pluck('id')->toArray()
        );
    }

    public function test_it_has_many_auth_codes(): void
    {
        // Arrange
        $user = User::factory()->create();
        $client = new Client;
        $client->name = 'desktop';
        $client->redirect_uris = 'solidtime://oauth/callback';
        $client->grant_types = [];
        $client->revoked = false;
        $client->save();
        $authCode = new AuthCode;
        $authCode->id = 'some-id';
        $authCode->user_id = $user->getKey();
        $authCode->client_id = $client->getKey();
        $authCode->revoked = false;
        $authCode->save();

        // Act
        $user->refresh();
        $authCodesRel = $user->authCodes;

        // Assert
        $this->assertNotNull($authCodesRel);
        $this->assertCount(1, $authCodesRel);
        $this->assertEqualsCanonicalizing(
            [$authCode->getKey()],
            $authCodesRel->pluck('id')->toArray()
        );
    }
}
