<?php

namespace MintyPHP\Tests\Service\Org;

use MintyPHP\Repository\Org\DepartmentRepositoryInterface;
use MintyPHP\Repository\Org\UserDepartmentRepositoryInterface;
use MintyPHP\Service\Audit\SystemAuditService;
use MintyPHP\Service\Directory\DirectorySettingsGateway;
use MintyPHP\Service\Org\DepartmentService;
use MintyPHP\Service\Tenant\TenantScopeService;
use MintyPHP\Service\User\UserServicesFactory;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;

class DepartmentServiceTest extends TestCase
{
    private UserServicesFactory&MockObject $userServicesFactory;
    private DepartmentRepositoryInterface&MockObject $departmentRepository;
    private DirectorySettingsGateway&MockObject $settingsGateway;
    private TenantScopeService&MockObject $scopeGateway;
    private SystemAuditService&MockObject $systemAuditService;
    private DepartmentService $service;

    protected function setUp(): void
    {
        $this->userServicesFactory = $this->createMock(UserServicesFactory::class);
        $this->departmentRepository = $this->createMock(DepartmentRepositoryInterface::class);
        $this->settingsGateway = $this->createMock(DirectorySettingsGateway::class);
        $this->scopeGateway = $this->createMock(TenantScopeService::class);
        $this->systemAuditService = $this->createMock(SystemAuditService::class);

        $this->service = new DepartmentService(
            $this->userServicesFactory,
            $this->departmentRepository,
            $this->settingsGateway,
            $this->scopeGateway,
            $this->systemAuditService
        );
    }

    public function testListPagedRemovesTenantFilterForGlobalAccessUser(): void
    {
        $this->scopeGateway
            ->expects($this->once())
            ->method('hasGlobalAccess')
            ->with(5)
            ->willReturn(true);

        $this->departmentRepository
            ->expects($this->once())
            ->method('listPaged')
            ->with($this->callback(function (array $options): bool {
                return !isset($options['tenantUserId']) && !isset($options['tenantIds']);
            }))
            ->willReturn([['id' => 1]]);

        $result = $this->service->listPaged([
            'tenantUserId' => 5,
            'tenantIds' => [1, 2],
            'page' => 1,
        ]);

        $this->assertSame([['id' => 1]], $result);
    }

    public function testListPagedKeepsTenantFilterForNonGlobalUser(): void
    {
        $this->scopeGateway
            ->expects($this->once())
            ->method('hasGlobalAccess')
            ->with(5)
            ->willReturn(false);

        $this->departmentRepository
            ->expects($this->once())
            ->method('listPaged')
            ->with($this->callback(function (array $options): bool {
                return isset($options['tenantUserId']) && $options['tenantUserId'] === 5;
            }))
            ->willReturn([]);

        $this->service->listPaged(['tenantUserId' => 5, 'tenantIds' => [1]]);
    }

    public function testGroupActiveByTenantIdsGroupsAndSorts(): void
    {
        $this->departmentRepository
            ->expects($this->once())
            ->method('listByTenantIds')
            ->with([1, 2])
            ->willReturn([
                ['id' => 10, 'tenant_id' => 1, 'description' => 'Zebra'],
                ['id' => 11, 'tenant_id' => 1, 'description' => 'Alpha'],
                ['id' => 20, 'tenant_id' => 2, 'description' => 'Beta'],
            ]);

        $result = $this->service->groupActiveByTenantIds([1, 2]);

        $this->assertCount(2, $result);
        $this->assertArrayHasKey(1, $result);
        $this->assertArrayHasKey(2, $result);

        // Tenant 1 departments sorted by description: Alpha before Zebra
        $this->assertSame('Alpha', $result[1][0]['description']);
        $this->assertSame('Zebra', $result[1][1]['description']);

        // Tenant 2 has one department
        $this->assertCount(1, $result[2]);
        $this->assertSame('Beta', $result[2][0]['description']);
    }

    public function testGroupActiveByTenantIdsReturnsEmptyForInvalidIds(): void
    {
        $this->departmentRepository
            ->expects($this->never())
            ->method('listByTenantIds');

        $result = $this->service->groupActiveByTenantIds([0, -1, -5]);

        $this->assertSame([], $result);
    }

    public function testGroupActiveByTenantIdsDeduplicatesIds(): void
    {
        $this->departmentRepository
            ->expects($this->once())
            ->method('listByTenantIds')
            ->with([1])
            ->willReturn([]);

        $this->service->groupActiveByTenantIds([1, 1, 1]);
    }

    public function testCreateFromAdminSucceeds(): void
    {
        $input = [
            'description' => 'Sales',
            'tenant_id' => 1,
            'code' => '',
            'cost_center' => '',
            'active' => 1,
        ];

        $this->departmentRepository
            ->method('existsByCode')
            ->willReturn(false);

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

        $this->departmentRepository
            ->expects($this->once())
            ->method('find')
            ->with(42)
            ->willReturn(['id' => 42, 'uuid' => 'dept-uuid-123', 'description' => 'Sales']);

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

        $result = $this->service->createFromAdmin($input, 7);

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

    public function testCreateFromAdminSetsDefaultDepartmentWhenRequested(): void
    {
        $input = [
            'description' => 'Default Dept',
            'tenant_id' => 1,
            'code' => '',
            'cost_center' => '',
            'active' => 1,
            'is_default' => true,
        ];

        $this->departmentRepository->method('existsByCode')->willReturn(false);
        $this->departmentRepository->method('create')->willReturn(10);
        $this->departmentRepository->method('find')->willReturn(['id' => 10, 'uuid' => 'u1']);

        $this->settingsGateway
            ->expects($this->once())
            ->method('setDefaultDepartmentId')
            ->with(10);

        $result = $this->service->createFromAdmin($input, 1);

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

    public function testCreateFromAdminRejectsEmptyDescription(): void
    {
        $input = [
            'description' => '',
            'tenant_id' => 1,
            'code' => '',
            'cost_center' => '',
            'active' => 1,
        ];

        $this->departmentRepository
            ->expects($this->never())
            ->method('create');

        $result = $this->service->createFromAdmin($input);

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

    public function testCreateFromAdminRejectsMissingTenantId(): void
    {
        $input = [
            'description' => 'Sales',
            'tenant_id' => 0,
            'code' => '',
            'cost_center' => '',
            'active' => 1,
        ];

        $this->departmentRepository
            ->expects($this->never())
            ->method('create');

        $result = $this->service->createFromAdmin($input);

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

    public function testDeleteByUuidSucceeds(): void
    {
        $department = ['id' => 5, 'uuid' => 'abc-123', 'description' => 'HR'];

        $this->departmentRepository
            ->expects($this->once())
            ->method('findByUuid')
            ->with('abc-123')
            ->willReturn($department);

        $this->departmentRepository
            ->expects($this->once())
            ->method('delete')
            ->with(5)
            ->willReturn(true);

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

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

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

    public function testDeleteByUuidReturnsNotFoundForMissing(): void
    {
        $this->departmentRepository
            ->expects($this->once())
            ->method('findByUuid')
            ->with('nonexistent')
            ->willReturn(null);

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

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

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

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

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

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

    public function testSyncTenantsUsesFirstId(): void
    {
        $this->departmentRepository
            ->expects($this->once())
            ->method('setTenant')
            ->with(10, 3)
            ->willReturn(true);

        $userDeptRepo = $this->createMock(UserDepartmentRepositoryInterface::class);
        $userDeptRepo->expects($this->any())->method('removeInvalidForDepartment')->with(10)->willReturn(2);

        $this->userServicesFactory
            ->expects($this->once())
            ->method('createUserDepartmentRepository')
            ->willReturn($userDeptRepo);

        $result = $this->service->syncTenants(10, [3, 5, 7]);

        $this->assertSame(2, $result);
    }

    public function testSyncTenantsRejectsEmptyArray(): void
    {
        $this->departmentRepository
            ->expects($this->never())
            ->method('setTenant');

        $result = $this->service->syncTenants(10, []);

        $this->assertSame(-1, $result);
    }
}
