<?php

namespace MintyPHP\Service\User;

use MintyPHP\Repository\Access\UserRoleRepositoryInterface;
use MintyPHP\Repository\Org\UserDepartmentRepositoryInterface;
use MintyPHP\Repository\Support\DatabaseSessionRepository;
use MintyPHP\Repository\Tenant\UserTenantRepositoryInterface;
use MintyPHP\Repository\User\UserWriteRepositoryInterface;
use MintyPHP\Service\Access\PermissionService;

class UserAssignmentService
{
    public function __construct(
        private readonly UserWriteRepositoryInterface $userWriteRepository,
        private readonly UserTenantRepositoryInterface $userTenantRepository,
        private readonly UserRoleRepositoryInterface $userRoleRepository,
        private readonly UserDepartmentRepositoryInterface $userDepartmentRepository,
        private readonly UserDirectoryGateway $directoryGateway,
        private readonly PermissionService $permissionService,
        private readonly DatabaseSessionRepository $databaseSessionRepository
    ) {
    }

    public function buildAssignmentsForUser(int $userId): array
    {
        if ($userId <= 0) {
            return [
                'tenants' => [],
                'departments' => [],
                'roles' => [],
            ];
        }

        $tenantIds = $this->userTenantRepository->listTenantIdsByUserId($userId);
        $departmentIds = $this->userDepartmentRepository->listDepartmentIdsByUserId($userId);
        $roleIds = $this->userRoleRepository->listRoleIdsByUserId($userId);

        $tenantsById = [];
        foreach ($this->directoryGateway->listTenantsByIds($tenantIds) as $tenant) {
            $tenantId = (int) ($tenant['id'] ?? 0);
            if ($tenantId <= 0) {
                continue;
            }
            $tenantsById[$tenantId] = [
                'id' => $tenantId,
                'uuid' => (string) ($tenant['uuid'] ?? ''),
                'description' => (string) ($tenant['description'] ?? ''),
                'status' => (string) ($tenant['status'] ?? ''),
            ];
        }

        $departmentsById = [];
        foreach ($this->directoryGateway->listDepartmentsByIds($departmentIds, true) as $department) {
            $departmentId = (int) ($department['id'] ?? 0);
            if ($departmentId <= 0) {
                continue;
            }
            $departmentTenantId = (int) ($department['tenant_id'] ?? 0);
            $departmentsById[$departmentId] = [
                'id' => $departmentId,
                'uuid' => (string) ($department['uuid'] ?? ''),
                'description' => (string) ($department['description'] ?? ''),
                'active' => (bool) ($department['active'] ?? 0),
                'tenant_id' => $departmentTenantId,
                'tenant_uuid' => (string) (($tenantsById[$departmentTenantId]['uuid'] ?? '')),
            ];
        }

        $rolesById = [];
        foreach ($this->directoryGateway->listRolesByIds($roleIds) as $role) {
            $roleId = (int) ($role['id'] ?? 0);
            if ($roleId <= 0) {
                continue;
            }
            $rolesById[$roleId] = [
                'id' => $roleId,
                'uuid' => (string) ($role['uuid'] ?? ''),
                'description' => (string) ($role['description'] ?? ''),
                'active' => (bool) ($role['active'] ?? 0),
            ];
        }

        $tenants = [];
        foreach ($tenantIds as $tenantId) {
            if (isset($tenantsById[$tenantId])) {
                $tenants[] = $tenantsById[$tenantId];
            }
        }

        $departments = [];
        foreach ($departmentIds as $departmentId) {
            if (isset($departmentsById[$departmentId])) {
                $departments[] = $departmentsById[$departmentId];
            }
        }

        $roles = [];
        foreach ($roleIds as $roleId) {
            if (isset($rolesById[$roleId])) {
                $roles[] = $rolesById[$roleId];
            }
        }

        return [
            'tenants' => $tenants,
            'departments' => $departments,
            'roles' => $roles,
        ];
    }

    public function findTenantSummaryInAssignments(array $assignments, int $tenantId): ?array
    {
        if ($tenantId <= 0) {
            return null;
        }

        foreach (($assignments['tenants'] ?? []) as $tenant) {
            $currentTenantId = (int) ($tenant['id'] ?? 0);
            if ($currentTenantId !== $tenantId) {
                continue;
            }

            return [
                'uuid' => (string) ($tenant['uuid'] ?? ''),
                'description' => (string) ($tenant['description'] ?? ''),
                'status' => (string) ($tenant['status'] ?? ''),
            ];
        }

        return null;
    }

    public function mapAssignmentsToPublic(array $assignments): array
    {
        return [
            'tenants' => array_values(array_map(
                static fn (array $tenant): array => [
                    'uuid' => (string) ($tenant['uuid'] ?? ''),
                    'description' => (string) ($tenant['description'] ?? ''),
                    'status' => (string) ($tenant['status'] ?? ''),
                ],
                $assignments['tenants'] ?? []
            )),
            'departments' => array_values(array_map(
                static fn (array $department): array => [
                    'uuid' => (string) ($department['uuid'] ?? ''),
                    'description' => (string) ($department['description'] ?? ''),
                    'active' => (bool) ($department['active'] ?? 0),
                    'tenant_uuid' => (string) ($department['tenant_uuid'] ?? ''),
                ],
                $assignments['departments'] ?? []
            )),
            'roles' => array_values(array_map(
                static fn (array $role): array => [
                    'uuid' => (string) ($role['uuid'] ?? ''),
                    'description' => (string) ($role['description'] ?? ''),
                    'active' => (bool) ($role['active'] ?? 0),
                ],
                $assignments['roles'] ?? []
            )),
        ];
    }

    public function syncTenants(int $userId, array $tenantIds, bool $bumpAuthz = true): bool
    {
        $ids = $this->normalizeTenantIds($tenantIds);
        $result = $this->userTenantRepository->replaceForUser($userId, $ids);
        if ($result && $bumpAuthz) {
            $this->bumpAuthzVersion($userId);
        }
        return $result;
    }

    public function syncRoles(int $userId, array $roleIds, bool $bumpAuthz = true): bool
    {
        $ids = array_values(array_unique(array_map('intval', $roleIds)));
        $ids = array_values(array_filter($ids, static fn ($id) => $id > 0));
        $validIds = $this->directoryGateway->listActiveRoleIds();
        if ($validIds) {
            $validMap = array_fill_keys($validIds, true);
            $ids = array_values(array_filter($ids, static fn ($id) => isset($validMap[$id])));
        }
        $result = $this->userRoleRepository->replaceForUser($userId, $ids);
        $this->permissionService->clearUserCache($userId);
        if ($result && $bumpAuthz) {
            $this->bumpAuthzVersion($userId);
        }
        return $result;
    }

    public function syncDepartments(int $userId, array $departmentIds, bool $bumpAuthz = true): bool
    {
        $ids = array_values(array_unique(array_map('intval', $departmentIds)));
        $ids = array_values(array_filter($ids, static fn ($id) => $id > 0));
        // Important: use the user's assigned tenants directly (no permission bypass),
        // otherwise privileged users (e.g. admins) would incorrectly allow departments
        // from tenants that were just removed from their assignments.
        $userTenantIds = array_values(array_unique(array_map(
            'intval',
            $this->userTenantRepository->listTenantIdsByUserId($userId)
        )));
        $userTenantIds = array_values(array_filter($userTenantIds, static fn ($id) => $id > 0));
        if (!$userTenantIds) {
            $ids = [];
        } else {
            $allowedIds = $this->directoryGateway->listActiveDepartmentIdsByTenantIds($userTenantIds);
            if ($allowedIds) {
                $allowedMap = array_fill_keys($allowedIds, true);
                $ids = array_values(array_filter($ids, static fn ($id) => isset($allowedMap[$id])));
            } else {
                $ids = [];
            }
        }
        $result = $this->userDepartmentRepository->replaceForUser($userId, $ids);
        if ($result && $bumpAuthz) {
            $this->bumpAuthzVersion($userId);
        }
        return $result;
    }

    // Incrementing authz_version causes the session to be re-checked on the user's next request.
    public function bumpAuthzVersion(int $userId): void
    {
        if ($userId <= 0) {
            return;
        }
        $this->userWriteRepository->bumpAuthzVersion($userId);
    }

    public function syncAllAssignments(int $userId, array $tenantIds, array $roleIds, array $departmentIds): bool
    {
        if ($userId <= 0) {
            return false;
        }

        $transactionStarted = false;
        try {
            $this->databaseSessionRepository->beginTransaction();
            $transactionStarted = true;

            // bumpAuthz=false on each sync — bump once at the end to avoid redundant DB writes.
            $ok = $this->syncTenants($userId, $tenantIds, false)
                && $this->syncRoles($userId, $roleIds, false)
                && $this->syncDepartments($userId, $departmentIds, false);
            if (!$ok) {
                $this->rollbackQuietly();
                return false;
            }

            $this->bumpAuthzVersion($userId);
            $this->databaseSessionRepository->commitTransaction();
            $transactionStarted = false;

            return true;
        } catch (\Throwable $exception) {
            if ($transactionStarted) {
                $this->rollbackQuietly();
            }
            return false;
        }
    }

    // Accepts arrays, comma-separated strings, and nested arrays — normalizes to a flat list of IDs.
    public function normalizeIdInput($value): array
    {
        $raw = is_array($value) ? $value : [$value];
        $flat = [];

        $collect = static function ($item) use (&$collect, &$flat): void {
            if (is_array($item)) {
                foreach ($item as $nested) {
                    $collect($nested);
                }
                return;
            }
            $text = trim((string) $item);
            if ($text === '') {
                return;
            }
            if (str_contains($text, ',')) {
                foreach (explode(',', $text) as $part) {
                    $part = trim($part);
                    if ($part !== '') {
                        $flat[] = $part;
                    }
                }
                return;
            }
            $flat[] = $text;
        };

        foreach ($raw as $item) {
            $collect($item);
        }

        $ids = array_values(array_unique(array_map('intval', $flat)));
        return array_values(array_filter($ids, static fn ($id) => $id > 0));
    }

    public function normalizeTenantIds(array $tenantIds): array
    {
        $ids = array_values(array_unique(array_map('intval', $tenantIds)));
        $ids = array_filter($ids, static fn ($id) => $id > 0);
        $validIds = $this->directoryGateway->listTenantIds();
        if ($validIds) {
            $validMap = array_fill_keys($validIds, true);
            $ids = array_values(array_filter($ids, static fn ($id) => isset($validMap[$id])));
        }
        return $ids;
    }

    private function rollbackQuietly(): void
    {
        try {
            $this->databaseSessionRepository->rollbackTransaction();
        } catch (\Throwable $exception) {
            // no-op
        }
    }
}
