<?php

namespace MintyPHP\Tests\Service\Access;

use MintyPHP\Repository\Access\RoleRepositoryInterface;
use MintyPHP\Service\Access\RoleService;
use MintyPHP\Service\Audit\SystemAuditService;
use MintyPHP\Service\Directory\DirectorySettingsGateway;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;

class RoleServiceTest extends TestCase
{
    private RoleRepositoryInterface&MockObject $roleRepo;
    private DirectorySettingsGateway&MockObject $settingsGateway;
    private SystemAuditService&MockObject $auditService;
    private RoleService $service;

    protected function setUp(): void
    {
        $this->roleRepo = $this->createMock(RoleRepositoryInterface::class);
        $this->settingsGateway = $this->createMock(DirectorySettingsGateway::class);
        $this->auditService = $this->createMock(SystemAuditService::class);

        $this->service = new RoleService(
            $this->roleRepo,
            $this->settingsGateway,
            $this->auditService
        );
    }

    public function testCreateFromAdminSucceeds(): void
    {
        $this->roleRepo
            ->expects($this->once())
            ->method('existsByCode')
            ->with('EDITOR', 0)
            ->willReturn(false);

        $this->roleRepo
            ->expects($this->once())
            ->method('create')
            ->willReturn(42);

        $this->roleRepo
            ->expects($this->once())
            ->method('find')
            ->with(42)
            ->willReturn(['id' => 42, 'uuid' => 'uuid-42', 'description' => 'Editor']);

        $this->settingsGateway
            ->expects($this->never())
            ->method('setDefaultRoleId');

        $this->auditService
            ->expects($this->once())
            ->method('record')
            ->with('admin.roles.create', 'success', $this->anything());

        $result = $this->service->createFromAdmin([
            'description' => 'Editor',
            'code' => 'EDITOR',
            'active' => 1,
        ], 5);

        $this->assertTrue($result['ok']);
        $this->assertSame('uuid-42', $result['uuid']);
        $this->assertSame(42, $result['id']);
    }

    public function testCreateFromAdminRejectsEmptyDescription(): void
    {
        $this->roleRepo
            ->expects($this->never())
            ->method('create');

        $result = $this->service->createFromAdmin([
            'description' => '   ',
            'code' => '',
            'active' => 1,
        ]);

        $this->assertFalse($result['ok']);
        $this->assertNotEmpty($result['errors']);
    }

    public function testCreateFromAdminSetsDefaultRoleWhenRequested(): void
    {
        $this->roleRepo
            ->method('existsByCode')
            ->willReturn(false);

        $this->roleRepo
            ->method('create')
            ->willReturn(99);

        $this->roleRepo
            ->expects($this->any())
            ->method('find')
            ->with(99)
            ->willReturn(['id' => 99, 'uuid' => 'uuid-99', 'description' => 'Default']);

        $this->settingsGateway
            ->expects($this->once())
            ->method('setDefaultRoleId')
            ->with(99);

        $this->auditService
            ->expects($this->once())
            ->method('record');

        $result = $this->service->createFromAdmin([
            'description' => 'Default',
            'code' => '',
            'active' => 1,
            'is_default' => 1,
        ], 3);

        $this->assertTrue($result['ok']);
    }

    public function testUpdateFromAdminSucceeds(): void
    {
        $this->roleRepo
            ->expects($this->once())
            ->method('existsByCode')
            ->with('MGR', 10)
            ->willReturn(false);

        $this->roleRepo
            ->expects($this->once())
            ->method('find')
            ->with(10)
            ->willReturn(['id' => 10, 'uuid' => 'uuid-10', 'active' => 1]);

        $this->roleRepo
            ->expects($this->once())
            ->method('update')
            ->with(10, $this->anything())
            ->willReturn(true);

        $this->auditService
            ->expects($this->once())
            ->method('record')
            ->with('admin.roles.update', 'success', $this->anything());

        $result = $this->service->updateFromAdmin(10, [
            'description' => 'Manager',
            'code' => 'MGR',
            'active' => 1,
        ], 5);

        $this->assertTrue($result['ok']);
    }

    public function testUpdateFromAdminRejectsDuplicateCode(): void
    {
        $this->roleRepo
            ->expects($this->once())
            ->method('existsByCode')
            ->with('ADMIN', 10)
            ->willReturn(true);

        $this->roleRepo
            ->expects($this->never())
            ->method('update');

        $result = $this->service->updateFromAdmin(10, [
            'description' => 'Manager',
            'code' => 'ADMIN',
            'active' => 1,
        ]);

        $this->assertFalse($result['ok']);
        $this->assertNotEmpty($result['errors']);
    }

    public function testDeleteByUuidSucceeds(): void
    {
        $role = ['id' => 7, 'uuid' => 'uuid-7', 'description' => 'Guest'];

        $this->roleRepo
            ->expects($this->once())
            ->method('findByUuid')
            ->with('uuid-7')
            ->willReturn($role);

        $this->roleRepo
            ->expects($this->once())
            ->method('delete')
            ->with(7)
            ->willReturn(true);

        $this->auditService
            ->expects($this->once())
            ->method('record')
            ->with('admin.roles.delete', 'success', $this->anything());

        $result = $this->service->deleteByUuid('uuid-7');

        $this->assertTrue($result['ok']);
        $this->assertSame($role, $result['role']);
    }

    public function testDeleteByUuidProtectsAdminRole(): void
    {
        $this->roleRepo
            ->expects($this->once())
            ->method('findByUuid')
            ->with('uuid-admin')
            ->willReturn(['id' => 1, 'uuid' => 'uuid-admin', 'description' => 'Admin']);

        $this->roleRepo
            ->expects($this->never())
            ->method('delete');

        $result = $this->service->deleteByUuid('uuid-admin');

        $this->assertFalse($result['ok']);
        $this->assertSame(403, $result['status']);
        $this->assertSame('admin_role_protected', $result['error']);
    }

    public function testDeleteByUuidReturnsNotFoundForEmptyUuid(): void
    {
        $this->roleRepo
            ->expects($this->never())
            ->method('findByUuid');

        $result = $this->service->deleteByUuid('   ');

        $this->assertFalse($result['ok']);
        $this->assertSame(404, $result['status']);
        $this->assertSame('not_found', $result['error']);
    }

    public function testDeleteByUuidReturnsDeleteFailedOnRepoFailure(): void
    {
        $this->roleRepo
            ->expects($this->once())
            ->method('findByUuid')
            ->with('uuid-8')
            ->willReturn(['id' => 8, 'uuid' => 'uuid-8', 'description' => 'Viewer']);

        $this->roleRepo
            ->expects($this->once())
            ->method('delete')
            ->with(8)
            ->willReturn(false);

        $this->auditService
            ->expects($this->never())
            ->method('record');

        $result = $this->service->deleteByUuid('uuid-8');

        $this->assertFalse($result['ok']);
        $this->assertSame(500, $result['status']);
        $this->assertSame('delete_failed', $result['error']);
    }
}
