<?php

namespace MintyPHP\Service\AddressBook;

use MintyPHP\Repository\CustomField\TenantCustomFieldOptionRepository;
use MintyPHP\Service\Access\RoleService;
use MintyPHP\Service\CustomField\UserCustomFieldValueService;
use MintyPHP\Service\Org\DepartmentService;
use MintyPHP\Service\Tenant\TenantScopeService;
use MintyPHP\Service\Tenant\TenantService;
use MintyPHP\Service\User\UserAccountService;
use MintyPHP\Service\User\UserAssignmentService;
use MintyPHP\Service\User\UserAvatarService;

class AddressBookService
{
    public function __construct(
        private readonly UserAccountService $userAccountService,
        private readonly UserAssignmentService $userAssignmentService,
        private readonly TenantService $tenantService,
        private readonly DepartmentService $departmentService,
        private readonly RoleService $roleService,
        private readonly TenantScopeService $scopeGateway,
        private readonly UserCustomFieldValueService $customFieldValueService,
        private readonly UserAvatarService $avatarService
    ) {
    }

    public function buildIndexContext(int $currentUserId, array $query): array
    {
        $activeTenants = $this->splitCommaValues($query['tenants'] ?? '');
        $activeRoles = $this->splitCommaValues($query['roles'] ?? '');
        $activeDepartments = $this->splitCommaValues($query['departments'] ?? '');

        $tenantIds = $this->normalizeIds($this->scopeGateway->getUserTenantIds($currentUserId));
        $tenants = $this->tenantService->list();
        if ($tenantIds) {
            $allowedTenantMap = array_fill_keys($tenantIds, true);
            $tenants = array_values(array_filter(
                $tenants,
                static function (array $tenant) use ($allowedTenantMap): bool {
                    $tenantId = (int) ($tenant['id'] ?? 0);
                    return $tenantId > 0 && isset($allowedTenantMap[$tenantId]);
                }
            ));
        } elseif ($this->scopeGateway->isStrict()) {
            $tenants = [];
        }

        $tenantItems = array_map(
            static fn (array $tenant): array => [
                'id' => (string) ($tenant['uuid'] ?? ''),
                'description' => (string) ($tenant['description'] ?? ''),
            ],
            $tenants
        );

        $departments = $tenantIds
            ? $this->departmentService->listByTenantIds($tenantIds)
            : ($this->scopeGateway->isStrict() ? [] : $this->departmentService->list());

        $roles = $this->roleService->listActive();

        $customFieldFilterSpec = $this->customFieldValueService->extractAddressBookFilterSpec($query, $currentUserId);
        $customFieldFilterDefinitions = $this->normalizeDefinitions(
            $customFieldFilterSpec['definitions'] ?? []
        );
        $customFieldFilterQuery = is_array($customFieldFilterSpec['query'] ?? null)
            ? $customFieldFilterSpec['query']
            : [];

        $customFieldDefinitionIds = [];
        foreach ($customFieldFilterDefinitions as $definition) {
            $definitionId = (int) ($definition['id'] ?? 0);
            if ($definitionId > 0) {
                $customFieldDefinitionIds[] = $definitionId;
            }
        }
        $customFieldDefinitionIds = array_values(array_unique($customFieldDefinitionIds));

        $customFieldOptions = TenantCustomFieldOptionRepository::listByDefinitionIds(
            $customFieldDefinitionIds,
            true
        );

        $customFieldOptionsByDefinition = [];
        foreach ($customFieldOptions as $option) {
            $definitionId = (int) ($option['definition_id'] ?? 0);
            if ($definitionId <= 0) {
                continue;
            }
            $customFieldOptionsByDefinition[$definitionId] ??= [];
            $customFieldOptionsByDefinition[$definitionId][] = [
                'id' => (string) ((int) ($option['id'] ?? 0)),
                'description' => (string) ($option['label'] ?? ''),
            ];
        }

        $tenantLabelById = [];
        foreach ($tenants as $tenant) {
            $tenantId = (int) ($tenant['id'] ?? 0);
            if ($tenantId <= 0) {
                continue;
            }
            $tenantLabelById[$tenantId] = (string) ($tenant['description'] ?? '');
        }

        foreach ($customFieldFilterDefinitions as &$definition) {
            $definitionId = (int) ($definition['id'] ?? 0);
            $tenantId = (int) ($definition['tenant_id'] ?? 0);
            $definition['options'] = $customFieldOptionsByDefinition[$definitionId] ?? [];
            $definition['tenant_label'] = $tenantLabelById[$tenantId] ?? '';
        }
        unset($definition);

        $tenantDepartmentMap = [];
        $departmentMapByTenantId = [];
        foreach ($departments as $department) {
            $departmentId = (int) ($department['id'] ?? 0);
            $departmentTenantId = (int) ($department['tenant_id'] ?? 0);
            if ($departmentId <= 0 || $departmentTenantId <= 0) {
                continue;
            }
            $departmentMapByTenantId[$departmentTenantId] ??= [];
            $departmentMapByTenantId[$departmentTenantId][] = $departmentId;
        }

        foreach ($tenants as $tenant) {
            $tenantId = (int) ($tenant['id'] ?? 0);
            $tenantUuid = (string) ($tenant['uuid'] ?? '');
            if ($tenantId <= 0 || $tenantUuid === '') {
                continue;
            }
            $tenantDepartmentMap[$tenantUuid] = array_map(
                'strval',
                $departmentMapByTenantId[$tenantId] ?? []
            );
        }

        return [
            'activeTenants' => $activeTenants,
            'activeRoles' => $activeRoles,
            'activeDepartments' => $activeDepartments,
            'tenantItems' => $tenantItems,
            'departments' => $departments,
            'roles' => $roles,
            'customFieldFilterDefinitions' => $customFieldFilterDefinitions,
            'customFieldFilterQuery' => $customFieldFilterQuery,
            'tenantDepartmentMap' => $tenantDepartmentMap,
        ];
    }

    public function buildGridResult(int $currentUserId, array $query): array
    {
        $limit = (int) ($query['limit'] ?? 10);
        $offset = (int) ($query['offset'] ?? 0);
        $search = trim((string) ($query['search'] ?? ''));
        $order = (string) ($query['order'] ?? 'last_name');
        $dir = (string) ($query['dir'] ?? 'asc');
        $tenant = trim((string) ($query['tenant'] ?? ''));
        $tenants = $query['tenants'] ?? '';
        $departments = $query['departments'] ?? '';
        $roles = $query['roles'] ?? '';
        $customFieldFilterSpec = $this->customFieldValueService->extractAddressBookFilterSpec($query, $currentUserId);

        $result = $this->userAccountService->listPaged([
            'limit' => $limit,
            'offset' => $offset,
            'search' => $search,
            'order' => $order,
            'dir' => $dir,
            'tenant' => $tenant,
            'tenants' => $tenants,
            'departments' => $departments,
            'roles' => $roles,
            'customFieldFilterSpec' => $customFieldFilterSpec,
            'tenantUserId' => $currentUserId,
            'active' => 'active',
        ]);

        $rows = [];
        foreach (($result['rows'] ?? []) as $row) {
            $tenantList = $this->normalizeLabelList($row['tenant_labels'] ?? []);
            $departmentList = $this->normalizeLabelList($row['department_labels'] ?? []);
            $roleList = $this->normalizeLabelList($row['role_labels'] ?? []);

            $uuid = (string) ($row['uuid'] ?? '');
            $displayName = trim((string) ($row['display_name'] ?? ''));
            if ($displayName === '') {
                $displayName = trim((string) ($row['first_name'] ?? '') . ' ' . (string) ($row['last_name'] ?? ''));
            }
            if ($displayName === '') {
                $displayName = (string) ($row['email'] ?? '');
            }

            $rows[] = [
                'uuid' => $uuid,
                'display_name' => $displayName,
                'first_name' => (string) ($row['first_name'] ?? ''),
                'last_name' => (string) ($row['last_name'] ?? ''),
                'email' => (string) ($row['email'] ?? ''),
                'phone' => (string) ($row['phone'] ?? ''),
                'mobile' => (string) ($row['mobile'] ?? ''),
                'short_dial' => (string) ($row['short_dial'] ?? ''),
                'tenants' => $tenantList,
                'departments' => $departmentList,
                'roles' => $roleList,
                'has_avatar' => $uuid !== '' && $this->avatarService->hasAvatar($uuid),
            ];
        }

        return [
            'data' => $rows,
            'total' => (int) ($result['total'] ?? 0),
        ];
    }

    public function buildViewContext(int $currentUserId, string $uuid): array
    {
        $uuid = trim($uuid);
        if ($uuid === '') {
            return ['status' => 'invalid_uuid'];
        }

        $user = $this->userAccountService->findByUuid($uuid);
        if (!$user || !isset($user['id'])) {
            return ['status' => 'not_found'];
        }

        $targetUserId = (int) $user['id'];
        if ($targetUserId <= 0) {
            return ['status' => 'not_found'];
        }

        // Users can always view their own profile; others require scope access.
        if (
            $currentUserId !== $targetUserId
            && !$this->scopeGateway->canAccess('users', $targetUserId, $currentUserId)
        ) {
            return ['status' => 'forbidden'];
        }

        $viewerTenantIds = $this->normalizeIds($this->scopeGateway->getUserTenantIds($currentUserId));
        $assignments = $this->userAssignmentService->buildAssignmentsForUser($targetUserId);

        $departmentMap = [];
        foreach (($assignments['departments'] ?? []) as $department) {
            if (empty($department['active'])) {
                continue;
            }
            $tenantId = (int) ($department['tenant_id'] ?? 0);
            $label = trim((string) ($department['description'] ?? ''));
            // Only show departments in tenants the viewer also belongs to — prevents leaking org structure.
            if ($tenantId <= 0 || $label === '' || !in_array($tenantId, $viewerTenantIds, true)) {
                continue;
            }
            $departmentMap[$tenantId] ??= [];
            $departmentMap[$tenantId][] = $label;
        }
        foreach ($departmentMap as $tenantId => $labels) {
            $labels = array_values(array_unique($labels));
            natcasesort($labels);
            $departmentMap[$tenantId] = array_values($labels);
        }

        $primaryTenantId = (int) ($user['primary_tenant_id'] ?? 0);
        $tenantGroups = [];
        foreach (($assignments['tenants'] ?? []) as $tenant) {
            $tenantId = (int) ($tenant['id'] ?? 0);
            $tenantLabel = trim((string) ($tenant['description'] ?? ''));
            $status = strtolower(trim((string) ($tenant['status'] ?? '')));
            if (
                $tenantId <= 0
                || $tenantLabel === ''
                || $status !== 'active'
                || !in_array($tenantId, $viewerTenantIds, true)
            ) {
                continue;
            }
            $tenantGroups[] = [
                'label' => $tenantLabel,
                'is_primary' => $primaryTenantId > 0 && $tenantId === $primaryTenantId,
                'departments' => $departmentMap[$tenantId] ?? [],
            ];
        }

        usort($tenantGroups, static fn (array $a, array $b): int => strnatcasecmp(
            (string) $a['label'],
            (string) $b['label']
        ));

        $roleLabels = [];
        foreach (($assignments['roles'] ?? []) as $role) {
            if (empty($role['active'])) {
                continue;
            }
            $label = trim((string) ($role['description'] ?? ''));
            if ($label !== '') {
                $roleLabels[] = $label;
            }
        }
        $roleLabels = array_values(array_unique($roleLabels));
        natcasesort($roleLabels);
        $roleLabels = array_values($roleLabels);

        $avatarUuid = (string) ($user['uuid'] ?? '');
        $user['has_avatar'] = $avatarUuid !== '' && $this->avatarService->hasAvatar($avatarUuid);
        $user['tenant_groups'] = $tenantGroups;
        $user['role_labels'] = $roleLabels;

        return [
            'status' => 'ok',
            'user' => $user,
        ];
    }

    private function splitCommaValues($value): array
    {
        return array_values(array_filter(
            array_map('trim', explode(',', (string) $value)),
            static fn (string $item): bool => $item !== ''
        ));
    }

    private function normalizeDefinitions($definitions): array
    {
        if (!is_array($definitions)) {
            return [];
        }
        return array_values(array_filter(
            $definitions,
            static fn ($definition): bool => is_array($definition)
        ));
    }

    private function normalizeIds(array $values): array
    {
        $ids = array_values(array_unique(array_map('intval', $values)));
        return array_values(array_filter($ids, static fn (int $id): bool => $id > 0));
    }

    // Accepts either a '||'-delimited DB aggregate string (from GROUP_CONCAT) or a plain array.
    private function normalizeLabelList($value): array
    {
        if (is_string($value)) {
            if ($value === '') {
                return [];
            }
            $items = explode('||', $value);
        } elseif (is_array($value)) {
            $items = $value;
        } else {
            return [];
        }

        return array_values(array_filter(array_map(
            static fn ($item): string => trim((string) $item),
            $items
        ), static fn (string $item): bool => $item !== ''));
    }
}
