<?php

namespace MintyPHP\Tests\Module\Notifications\Listeners;

use MintyPHP\Module\Notifications\Listeners\SchedulerJobFailedNotificationListener;
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 SchedulerJobFailedNotificationListenerTest extends TestCase
{
    private NotificationService&MockObject $notifService;
    private UserReadRepositoryInterface&MockObject $userRepo;
    private UserTenantRepositoryInterface&MockObject $utRepo;
    private SchedulerJobFailedNotificationListener $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 SchedulerJobFailedNotificationListener($this->notifService, $this->userRepo, $this->utRepo);
    }

    public function testHandleCreatesNotificationsPerAdminPerTenant(): void
    {
        $this->userRepo->expects($this->any())->method('listPrivilegedUserIdsByPermissionKeys')
            ->with([PermissionService::JOBS_MANAGE])
            ->willReturn([20, 30]);

        $this->utRepo->expects($this->exactly(2))
            ->method('listTenantIdsByUserId')
            ->willReturnMap([
                [20, [1, 2]],
                [30, [2, 3]],
            ]);

        /** @var list<array{user: int, tenant: int|null}> $calls */
        $calls = [];
        $this->notifService->expects($this->exactly(4))
            ->method('createFromMessage')
            ->willReturnCallback(function (int $userId, ?int $tenantId, NotificationMessage $message) use (&$calls): int {
                $calls[] = ['user' => $userId, 'tenant' => $tenantId];
                $this->assertSame('scheduler.job_failed', $message->type());
                $this->assertStringContainsString('Test Job', $message->title());
                $this->assertSame('admin/scheduled-jobs', $message->link());
                $this->assertSame('Scheduled job failed: %s', $message->titleKey());
                $data = $message->data();
                $this->assertSame('job:test_fail_job', $data['dedupe_target']);
                return 1;
            });

        $this->listener->handle('scheduler.job_failed', [
            'job_id' => 5,
            'job_key' => 'test_fail_job',
            'label' => 'Test Job',
            'error_code' => 'job_not_registered',
            'error_message' => null,
            'trigger_type' => 'scheduler',
        ]);

        // User 20: tenant 1, tenant 2. User 30: tenant 2, tenant 3. = 4 calls total.
        $this->assertCount(4, $calls);
        $this->assertSame(['user' => 20, 'tenant' => 1], $calls[0]);
        $this->assertSame(['user' => 20, 'tenant' => 2], $calls[1]);
        $this->assertSame(['user' => 30, 'tenant' => 2], $calls[2]);
        $this->assertSame(['user' => 30, 'tenant' => 3], $calls[3]);
    }

    public function testHandleCreatesGlobalNotificationWhenNoTenants(): void
    {
        $this->userRepo->expects($this->any())->method('listPrivilegedUserIdsByPermissionKeys')->willReturn([10]);
        $this->utRepo->expects($this->any())->method('listTenantIdsByUserId')->willReturn([]);

        $this->notifService->expects($this->once())
            ->method('createFromMessage')
            ->with(10, null, $this->anything())
            ->willReturn(1);

        $this->listener->handle('scheduler.job_failed', [
            'job_id' => 1,
            'job_key' => 'some_job',
            'label' => 'Some Job',
            'error_code' => 'unexpected_error',
        ]);
    }

    public function testHandleUsesJobKeyAsLabelWhenLabelEmpty(): void
    {
        $this->userRepo->expects($this->any())->method('listPrivilegedUserIdsByPermissionKeys')->willReturn([10]);
        $this->utRepo->expects($this->any())->method('listTenantIdsByUserId')->willReturn([1]);

        $this->notifService->expects($this->once())
            ->method('createFromMessage')
            ->willReturnCallback(function (int $userId, ?int $tenantId, NotificationMessage $message): int {
                $this->assertStringContainsString('my_job_key', $message->title());
                return 1;
            });

        $this->listener->handle('scheduler.job_failed', [
            'job_id' => 1,
            'job_key' => 'my_job_key',
            'label' => '',
            'error_code' => 'unexpected_error',
        ]);
    }

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

        $this->listener->handle('scheduler.job_failed', []);
        $this->listener->handle('scheduler.job_failed', ['job_key' => '', 'job_id' => 0]);
        $this->listener->handle('scheduler.job_failed', ['job_key' => 'test', 'job_id' => 0]);
    }

    public function testHandleSkipsWhenNoAdmins(): void
    {
        $this->userRepo->expects($this->any())->method('listPrivilegedUserIdsByPermissionKeys')->willReturn([]);
        $this->notifService->expects($this->never())->method('createFromMessage');

        $this->listener->handle('scheduler.job_failed', [
            'job_id' => 5,
            'job_key' => 'some_job',
            'label' => 'Some Job',
            'error_code' => 'unexpected_error',
        ]);
    }

    public function testHandleOmitsBodyWhenNoErrorCode(): void
    {
        $this->userRepo->expects($this->any())->method('listPrivilegedUserIdsByPermissionKeys')->willReturn([10]);
        $this->utRepo->expects($this->any())->method('listTenantIdsByUserId')->willReturn([1]);

        $this->notifService->expects($this->once())
            ->method('createFromMessage')
            ->willReturnCallback(function (int $userId, ?int $tenantId, NotificationMessage $message): int {
                $this->assertNull($message->body());
                return 1;
            });

        $this->listener->handle('scheduler.job_failed', [
            'job_id' => 1,
            'job_key' => 'test_job',
            'label' => 'Test',
            'error_code' => '',
        ]);
    }
}
