<?php

namespace MintyPHP\Tests\Service\Org;

use MintyPHP\Repository\Org\DepartmentRepositoryInterface;
use MintyPHP\Repository\Org\UserDepartmentRepositoryInterface;
use MintyPHP\Service\Audit\AuditRecorderInterface;
use MintyPHP\Service\Directory\DirectorySettingsGateway;
use MintyPHP\Service\Org\DepartmentService;
use MintyPHP\Service\Tenant\TenantScopeService;
use MintyPHP\Service\User\UserServicesFactory;
use PHPUnit\Framework\Attributes\DataProvider;
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 AuditRecorderInterface&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(AuditRecorderInterface::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']);
    }

    #[DataProvider('groupActiveByTenantIdsInputProvider')]
    public function testGroupActiveByTenantIdsNormalizesInput(array $tenantIds, array $expectedRepositoryIds): void
    {
        $this->departmentRepository
            ->expects($expectedRepositoryIds ? $this->once() : $this->never())
            ->method('listByTenantIds')
            ->with($expectedRepositoryIds)
            ->willReturn([]);

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

    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']);
    }

    #[DataProvider('invalidDepartmentInputProvider')]
    public function testCreateFromAdminRejectsInvalidInput(array $input): void
    {
        $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);

        $userDeptRepo = $this->createMock(UserDepartmentRepositoryInterface::class);
        $userDeptRepo->expects($this->any())->method('countUsersByDepartmentIds')->with([5])->willReturn([]);
        $this->userServicesFactory->expects($this->any())->method('createUserDepartmentRepository')->willReturn($userDeptRepo);

        $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 testDeleteByUuidReturnsDeleteFailedOnRepositoryFailure(): void
    {
        $this->departmentRepository
            ->expects($this->once())
            ->method('findByUuid')
            ->with('abc-999')
            ->willReturn(['id' => 9, 'uuid' => 'abc-999', 'description' => 'Ops']);

        $userDeptRepo = $this->createMock(UserDepartmentRepositoryInterface::class);
        $userDeptRepo->expects($this->any())->method('countUsersByDepartmentIds')->with([9])->willReturn([]);
        $this->userServicesFactory->expects($this->any())->method('createUserDepartmentRepository')->willReturn($userDeptRepo);

        $this->departmentRepository
            ->expects($this->once())
            ->method('delete')
            ->with(9)
            ->willReturn(false);

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

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

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

    public function testDeleteByUuidReturns409WhenUsersAssigned(): void
    {
        $this->departmentRepository
            ->expects($this->once())
            ->method('findByUuid')
            ->with('abc-dept')
            ->willReturn(['id' => 5, 'uuid' => 'abc-dept', 'description' => 'Sales']);

        $userDeptRepo = $this->createMock(UserDepartmentRepositoryInterface::class);
        $userDeptRepo->expects($this->once())
            ->method('countUsersByDepartmentIds')
            ->with([5])
            ->willReturn([5 => 3]);
        $this->userServicesFactory->expects($this->once())
            ->method('createUserDepartmentRepository')
            ->willReturn($userDeptRepo);

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

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

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

        $this->assertFalse($result['ok']);
        $this->assertSame(409, $result['status']);
        $this->assertSame('department_has_users', $result['error']);
        $this->assertSame(3, $result['user_count']);
    }

    public function testDeleteByUuidDeletesWhenNoUsersAssigned(): void
    {
        $this->departmentRepository
            ->expects($this->once())
            ->method('findByUuid')
            ->with('abc-empty')
            ->willReturn(['id' => 7, 'uuid' => 'abc-empty', 'description' => 'Empty Dept']);

        $userDeptRepo = $this->createMock(UserDepartmentRepositoryInterface::class);
        $userDeptRepo->expects($this->once())
            ->method('countUsersByDepartmentIds')
            ->with([7])
            ->willReturn([]);
        $this->userServicesFactory->expects($this->once())
            ->method('createUserDepartmentRepository')
            ->willReturn($userDeptRepo);

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

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

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

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

    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);
    }

    public static function groupActiveByTenantIdsInputProvider(): array
    {
        return [
            'invalid ids are ignored completely' => [[0, -1, -5], []],
            'duplicate ids are reduced before querying' => [[1, 1, 1], [1]],
        ];
    }

    public static function invalidDepartmentInputProvider(): array
    {
        return [
            'missing description' => [[
                'description' => '',
                'tenant_id' => 1,
                'code' => '',
                'cost_center' => '',
                'active' => 1,
            ]],
            'missing tenant id' => [[
                'description' => 'Sales',
                'tenant_id' => 0,
                'code' => '',
                'cost_center' => '',
                'active' => 1,
            ]],
        ];
    }
}
