<?php

namespace MintyPHP\Service\User;

use MintyPHP\DB;
use MintyPHP\Repository\Support\RepoQuery;
use MintyPHP\Repository\User\UserReadRepository;
use MintyPHP\Repository\User\UserWriteRepository;
use MintyPHP\Service\Access\PermissionService;
use MintyPHP\Service\Audit\UserLifecycleAuditService;

class UserLifecycleService
{
    private const LOCK_NAME = 'user_lifecycle_maintenance';
    private const BATCH_SIZE = 500;

    public function __construct(
        private readonly UserReadRepository $userReadRepository,
        private readonly UserWriteRepository $userWriteRepository,
        private readonly UserSettingsGateway $settingsGateway,
        private readonly UserLifecycleAuditService $userLifecycleAuditService
    ) {
    }

    public function run(?int $actorUserId = null): array
    {
        $startedAt = microtime(true);
        $runUuid = RepoQuery::uuidV4();
        $triggerType = ($actorUserId ?? 0) > 0 ? 'manual' : 'cron';
        $deactivateDays = $this->settingsGateway->getUserInactivityDeactivateDays();
        $deleteDays = $this->settingsGateway->getUserInactivityDeleteDays();
        if ($deactivateDays <= 0) {
            $deleteDays = 0;
        }

        $privilegedUserIds = $this->userReadRepository->listPrivilegedUserIdsByPermissionKeys([
            PermissionService::SETTINGS_UPDATE,
            PermissionService::TENANTS_UPDATE,
        ]);

        $result = [
            'ok' => true,
            'locked' => false,
            'error' => null,
            'run_uuid' => $runUuid,
            'deactivated_count' => 0,
            'deleted_count' => 0,
            'skipped_count' => 0,
            'skipped_privileged_count' => count($privilegedUserIds),
            'policy' => [
                'deactivate_days' => $deactivateDays,
                'delete_days' => $deleteDays,
            ],
            'duration_ms' => 0,
        ];

        if (!$this->acquireLock()) {
            $result['ok'] = false;
            $result['locked'] = true;
            $result['error'] = 'lock_not_acquired';
            $result['duration_ms'] = $this->durationMs($startedAt);
            return $result;
        }

        try {
            if ($deactivateDays > 0) {
                while (true) {
                    $ids = $this->userReadRepository->listIdsForAutoDeactivate($deactivateDays, $privilegedUserIds, self::BATCH_SIZE);
                    if (!$ids) {
                        break;
                    }

                    $usersById = [];
                    foreach ($ids as $id) {
                        $user = $this->userReadRepository->find((int) $id);
                        if ($user) {
                            $usersById[(int) $id] = $user;
                        }
                    }
                    $updated = $this->userWriteRepository->setInactiveByIds(
                        $ids,
                        $actorUserId && $actorUserId > 0 ? $actorUserId : null
                    );
                    if ($updated <= 0) {
                        $result['ok'] = false;
                        $result['error'] = 'deactivate_failed';
                        break;
                    }
                    $result['deactivated_count'] += $updated;
                    $this->userWriteRepository->bumpAuthzVersionByUserIds($ids);
                    foreach ($ids as $id) {
                        if (!isset($usersById[(int) $id])) {
                            continue;
                        }
                        $this->userLifecycleAuditService->logDeactivate(
                            $runUuid,
                            $triggerType,
                            $result['policy'],
                            $actorUserId,
                            $usersById[(int) $id],
                            'success',
                            null
                        );
                    }
                    if (count($ids) < self::BATCH_SIZE) {
                        break;
                    }
                }
            }

            if ($deleteDays > 0) {
                while (true) {
                    $ids = $this->userReadRepository->listIdsForAutoDelete($deleteDays, $privilegedUserIds, self::BATCH_SIZE);
                    if (!$ids) {
                        break;
                    }

                    foreach ($ids as $id) {
                        $user = $this->userReadRepository->find((int) $id);
                        if (!$user) {
                            continue;
                        }

                        $auditId = $this->userLifecycleAuditService->logDeleteWithSnapshot(
                            $runUuid,
                            $triggerType,
                            $result['policy'],
                            $actorUserId,
                            $user
                        );
                        if (!$auditId) {
                            $result['skipped_count']++;
                            $this->userLifecycleAuditService->logDeleteFailure(
                                $runUuid,
                                $triggerType,
                                $result['policy'],
                                $actorUserId,
                                $user,
                                'audit_snapshot_failed'
                            );
                            continue;
                        }

                        $deleted = $this->userWriteRepository->deleteByIds([(int) $id]);
                        if ($deleted <= 0) {
                            $result['skipped_count']++;
                            $this->userLifecycleAuditService->updateStatus((int) $auditId, 'failed', 'delete_failed');
                            continue;
                        }
                        $result['deleted_count'] += $deleted;
                    }
                    if (count($ids) < self::BATCH_SIZE) {
                        break;
                    }
                }
            }
        } catch (\Throwable $exception) {
            $result['ok'] = false;
            $result['error'] = 'unexpected_error';
        } finally {
            $this->releaseLock();
            $result['duration_ms'] = $this->durationMs($startedAt);
        }

        return $result;
    }

    private function acquireLock(): bool
    {
        $gotLock = DB::selectValue('select GET_LOCK(?, 0) as got_lock', self::LOCK_NAME);
        return (int) $gotLock === 1;
    }

    private function releaseLock(): void
    {
        try {
            DB::selectValue('select RELEASE_LOCK(?) as released_lock', self::LOCK_NAME);
        } catch (\Throwable $exception) {
            // no-op
        }
    }

    private function durationMs(float $startedAt): int
    {
        return (int) max(0, round((microtime(true) - $startedAt) * 1000));
    }
}
