<?php

declare(strict_types=1);

namespace Tests\Unit\Endpoint\Web;

use App\Enums\Role;
use App\Http\Controllers\Web\OrganizationInvitationController;
use App\Models\Member;
use App\Models\OrganizationInvitation;
use App\Models\User;
use App\Service\MemberService;
use Illuminate\Support\Facades\URL;
use PHPUnit\Framework\Attributes\CoversClass;

#[CoversClass(OrganizationInvitationController::class)]
#[CoversClass(MemberService::class)]
class OrganizationInvitationEndpointTest extends EndpointTestAbstract
{
    public function test_legacy_url_still_works(): void
    {
        // Arrange
        $user = $this->createUserWithPermission();
        $invitation = OrganizationInvitation::factory()
            ->forOrganization($user->organization)
            ->create();

        // Act
        $acceptUrl = URL::temporarySignedRoute(
            'team-invitations.accept',
            now()->addMinutes(60),
            [$invitation->getKey()]
        );
        $response = $this->get($acceptUrl);

        // Assert
        $response->assertValid();
        $response->assertRedirect(route('register'));
        $response->assertSessionHas('bannerText', 'Please create an account to finish joining the '.$user->organization->name.' organization.');
        $response->assertSessionHas('bannerStyle', 'info');
        $invitation->refresh();
        $this->assertNotNull($invitation->accepted_at);
    }

    public function test_can_accept_invitation_without_an_account_with_the_email_address_and_redirects_to_registration(): void
    {
        // Arrange
        $user = $this->createUserWithPermission();
        $invitation = OrganizationInvitation::factory()
            ->forOrganization($user->organization)
            ->create();

        // Act
        $acceptUrl = URL::to(URL::temporarySignedRoute(
            'organization-invitations.accept',
            now()->addMinutes(60),
            [$invitation->getKey()],
            false
        ));
        $response = $this->get($acceptUrl);

        // Assert
        $response->assertValid();
        $response->assertRedirect(route('register'));
        $response->assertSessionHas('bannerText', 'Please create an account to finish joining the '.$user->organization->name.' organization.');
        $response->assertSessionHas('bannerStyle', 'info');
        $invitation->refresh();
        $this->assertNotNull($invitation->accepted_at);
    }

    public function test_can_accept_invitation_with_an_account_with_the_email_address_and_redirects_to_dashboard(): void
    {
        // Arrange
        $user = $this->createUserWithPermission();
        $user2 = $this->createUserWithPermission();
        $invitation = OrganizationInvitation::factory()
            ->forOrganization($user->organization)
            ->create([
                'role' => Role::Employee->value,
                'email' => $user2->user->email,
            ]);
        $this->actingAs($user2->user);

        // Act
        $acceptUrl = URL::to(URL::temporarySignedRoute(
            'organization-invitations.accept',
            now()->addMinutes(60),
            [$invitation->getKey()],
            false
        ));
        $response = $this->get($acceptUrl);

        // Assert
        $response->assertValid();
        $response->assertRedirect(route('dashboard'));
        $response->assertSessionHas('bannerText', 'Great! You have accepted the invitation to join the '.$user->organization->name.' organization.');
        $response->assertSessionHas('bannerStyle', 'success');
        $this->assertDatabaseHas(Member::class, [
            'user_id' => $user2->user->getKey(),
            'organization_id' => $user->organization->getKey(),
            'role' => Role::Employee->value,
        ]);
        $this->assertDatabaseMissing(OrganizationInvitation::class, [
            'id' => $invitation->getKey(),
        ]);
    }

    public function test_accepting_invitation_while_logged_out_redirects_to_login(): void
    {
        // Arrange
        $user = $this->createUserWithPermission();
        $invitee = User::factory()->create([
            'email' => 'invitee@example.com',
        ]);
        $invitation = OrganizationInvitation::factory()
            ->forOrganization($user->organization)
            ->create([
                'role' => Role::Employee->value,
                'email' => $invitee->email,
            ]);

        // Act (no actingAs — request is unauthenticated)
        $acceptUrl = URL::to(URL::temporarySignedRoute(
            'organization-invitations.accept',
            now()->addMinutes(60),
            [$invitation->getKey()],
            false
        ));
        $response = $this->get($acceptUrl);

        // Assert
        $response->assertValid();
        $response->assertRedirect(route('login'));
        $response->assertSessionHas('bannerText', 'Great! You have accepted the invitation to join the '.$user->organization->name.' organization. Please log in to access it.');
        $response->assertSessionHas('bannerStyle', 'success');
        // Member was added silently — invitation is consumed.
        $this->assertDatabaseHas(Member::class, [
            'user_id' => $invitee->getKey(),
            'organization_id' => $user->organization->getKey(),
            'role' => Role::Employee->value,
        ]);
        $this->assertDatabaseMissing(OrganizationInvitation::class, [
            'id' => $invitation->getKey(),
        ]);
    }

    public function test_fails_if_user_is_already_member_of_the_organization(): void
    {
        // Arrange
        $user = $this->createUserWithPermission();
        $user2 = $this->createUserWithPermission();
        $invitation = OrganizationInvitation::factory()
            ->forOrganization($user->organization)
            ->create([
                'role' => Role::Employee->value,
                'email' => $user2->user->email,
            ]);
        Member::factory()->forOrganization($user->organization)->forUser($user2->user)->create();
        $this->actingAs($user2->user);

        // Act
        $acceptUrl = URL::to(URL::temporarySignedRoute(
            'organization-invitations.accept',
            now()->addMinutes(60),
            [$invitation->getKey()],
            false
        ));
        $response = $this->get($acceptUrl);

        // Assert
        $response->assertValid();
        $response->assertRedirect(route('dashboard'));
        $response->assertSessionHas('bannerText', 'You are already a member of the '.$user->organization->name.' organization.');
        $response->assertSessionHas('bannerStyle', 'danger');
    }

    public function test_accepting_invitation_with_existing_account_migrates_data_of_placeholder_users_with_same_email_to_new_member(): void
    {
        // Arrange
        $user = $this->createUserWithPermission();
        $user2 = $this->createUserWithPermission();
        $invitation = OrganizationInvitation::factory()
            ->forOrganization($user->organization)
            ->create([
                'role' => Role::Employee->value,
                'email' => $user2->user->email,
            ]);
        $placeholder1 = User::factory()->placeholder()->create([
            'email' => $user2->user->email,
        ]);
        $placeholder1Member = Member::factory()->forOrganization($user->organization)->forUser($placeholder1)->role(Role::Placeholder)->create();
        $placeholder2 = User::factory()->placeholder()->create([
            'email' => $user2->user->email,
        ]);
        $placeholder2Member = Member::factory()->forOrganization($user->organization)->forUser($placeholder2)->role(Role::Placeholder)->create();

        $this->actingAs($user2->user);

        // Act
        $acceptUrl = URL::to(URL::temporarySignedRoute(
            'organization-invitations.accept',
            now()->addMinutes(60),
            [$invitation->getKey()],
            false
        ));
        $response = $this->get($acceptUrl);

        // Assert
        $response->assertValid();
        $response->assertRedirect(route('dashboard'));
        $response->assertSessionHas('bannerText', 'Great! You have accepted the invitation to join the '.$user->organization->name.' organization.');
        $response->assertSessionHas('bannerStyle', 'success');
        $this->assertDatabaseHas(Member::class, [
            'user_id' => $user2->user->getKey(),
            'organization_id' => $user->organization->getKey(),
            'role' => Role::Employee->value,
        ]);
        $this->assertDatabaseMissing(User::class, [
            'id' => $placeholder1->getKey(),
        ]);
        $this->assertDatabaseMissing(User::class, [
            'id' => $placeholder2->getKey(),
        ]);
        $this->assertDatabaseMissing(Member::class, [
            'id' => $placeholder1Member->getKey(),
        ]);
        $this->assertDatabaseMissing(Member::class, [
            'id' => $placeholder2Member->getKey(),
        ]);
        $this->assertDatabaseMissing(OrganizationInvitation::class, [
            'id' => $invitation->getKey(),
        ]);
    }

    public function test_fails_with_invalid_signature(): void
    {
        // Arrange
        $user = $this->createUserWithPermission();
        $invitation = OrganizationInvitation::factory()
            ->forOrganization($user->organization)
            ->create();

        // Act
        $response = $this->get(URL::temporarySignedRoute(
            'organization-invitations.accept',
            now()->addMinutes(60),
            [$invitation->getKey()]).
            '?invalid'
        );

        // Assert
        $response->assertForbidden();
    }

    public function test_fails_if_invitation_has_owner_role(): void
    {
        // Arrange
        $user = $this->createUserWithPermission();
        $invitation = OrganizationInvitation::factory()
            ->forOrganization($user->organization)
            ->create([
                'role' => Role::Owner->value,
            ]);

        // Act
        $acceptUrl = URL::to(URL::temporarySignedRoute(
            'organization-invitations.accept',
            now()->addMinutes(60),
            [$invitation->getKey()],
            false
        ));
        $response = $this->get($acceptUrl);

        // Assert
        $response->assertStatus(500);
    }
}
