<?php

namespace MintyPHP\Tests\Module\Notifications\Listeners;

use MintyPHP\Module\Notifications\Listeners\UserCreatedNotificationListener;
use MintyPHP\Module\Notifications\Service\NotificationMessage;
use MintyPHP\Module\Notifications\Service\NotificationService;
use MintyPHP\Repository\Tenant\UserTenantRepositoryInterface;
use MintyPHP\Repository\User\UserReadRepositoryInterface;
use MintyPHP\Service\Access\PermissionService;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;

class UserCreatedNotificationListenerTest extends TestCase
{
    private NotificationService&MockObject $notifService;
    private UserReadRepositoryInterface&MockObject $userRepo;
    private UserTenantRepositoryInterface&MockObject $utRepo;
    private UserCreatedNotificationListener $listener;

    protected function setUp(): void
    {
        $this->notifService = $this->createMock(NotificationService::class);
        $this->userRepo = $this->createMock(UserReadRepositoryInterface::class);
        $this->utRepo = $this->createMock(UserTenantRepositoryInterface::class);
        $this->listener = new UserCreatedNotificationListener($this->notifService, $this->userRepo, $this->utRepo);
    }

    public function testHandleCreatesNotificationsForTenantAdminUsers(): void
    {
        $this->userRepo->expects($this->any())->method('find')->with(10)->willReturn([
            'id' => 10,
            'display_name' => 'Alice Smith',
            'first_name' => 'Alice',
            'last_name' => 'Smith',
        ]);
        $this->utRepo->expects($this->any())->method('listTenantIdsByUserId')->with(10)->willReturn([1, 2]);
        $this->userRepo->expects($this->any())->method('listPrivilegedUserIdsByPermissionKeys')
            ->with([PermissionService::USERS_CREATE])
            ->willReturn([20, 30]);

        $this->notifService->expects($this->exactly(2))
            ->method('createForTenantAdminUsersFromMessage')
            ->willReturnCallback(function (int $tenantId, NotificationMessage $message, ?int $excludeUserId, array $adminSet): int {
                $this->assertContains($tenantId, [1, 2]);
                $this->assertSame('user.created', $message->type());
                $this->assertStringContainsString('Alice Smith', $message->title());
                $this->assertSame('admin/users/edit/abc-uuid', $message->link());
                $this->assertSame(5, $excludeUserId);
                $this->assertArrayHasKey(20, $adminSet);
                $this->assertArrayHasKey(30, $adminSet);
                $this->assertSame('New user: %s', $message->titleKey());
                return 2;
            });

        $this->listener->handle('user.created', [
            'user_id' => 10,
            'uuid' => 'abc-uuid',
            'actor_user_id' => 5,
        ]);
    }

    public function testHandleExcludesActorFromRecipients(): void
    {
        $this->userRepo->expects($this->any())->method('find')->with(10)->willReturn([
            'id' => 10,
            'display_name' => 'Bob',
        ]);
        $this->utRepo->expects($this->any())->method('listTenantIdsByUserId')->with(10)->willReturn([1]);
        $this->userRepo->expects($this->any())->method('listPrivilegedUserIdsByPermissionKeys')->willReturn([20]);

        $this->notifService->expects($this->once())
            ->method('createForTenantAdminUsersFromMessage')
            ->with(
                1,
                $this->anything(),
                7, // actor excluded
                $this->anything()
            )
            ->willReturn(1);

        $this->listener->handle('user.created', [
            'user_id' => 10,
            'uuid' => 'xyz',
            'actor_user_id' => 7,
        ]);
    }

    public function testHandleSkipsInvalidPayload(): void
    {
        $this->notifService->expects($this->never())->method('createForTenantAdminUsersFromMessage');

        $this->listener->handle('user.created', ['uuid' => 'abc']);
        $this->listener->handle('user.created', ['user_id' => 0, 'uuid' => 'abc']);
        $this->listener->handle('user.created', ['user_id' => 10, 'uuid' => '']);
    }

    public function testHandleSkipsWhenUserNotFound(): void
    {
        $this->userRepo->expects($this->any())->method('find')->with(99)->willReturn(null);
        $this->notifService->expects($this->never())->method('createForTenantAdminUsersFromMessage');

        $this->listener->handle('user.created', [
            'user_id' => 99,
            'uuid' => 'abc',
            'actor_user_id' => 1,
        ]);
    }

    public function testHandleSkipsWhenNoTenants(): void
    {
        $this->userRepo->expects($this->any())->method('find')->with(10)->willReturn([
            'id' => 10,
            'display_name' => 'Alice',
        ]);
        $this->utRepo->expects($this->any())->method('listTenantIdsByUserId')->with(10)->willReturn([]);
        $this->notifService->expects($this->never())->method('createForTenantAdminUsersFromMessage');

        $this->listener->handle('user.created', [
            'user_id' => 10,
            'uuid' => 'abc',
            'actor_user_id' => 1,
        ]);
    }

    public function testHandleSkipsWhenNoAdminUsers(): void
    {
        $this->userRepo->expects($this->any())->method('find')->with(10)->willReturn([
            'id' => 10,
            'display_name' => 'Alice',
        ]);
        $this->utRepo->expects($this->any())->method('listTenantIdsByUserId')->with(10)->willReturn([1]);
        $this->userRepo->expects($this->any())->method('listPrivilegedUserIdsByPermissionKeys')->willReturn([]);

        $this->notifService->expects($this->never())->method('createForTenantAdminUsersFromMessage');

        $this->listener->handle('user.created', [
            'user_id' => 10,
            'uuid' => 'abc',
            'actor_user_id' => 1,
        ]);
    }
}
