<?php

namespace App\Game\Service;

use App\Game\Enum\DecodeMessage;
use App\Game\Enum\SessionSettingType;
use App\Game\Enum\SessionStatus;
use App\Game\Entity\Player;
use App\Game\Entity\Session;
use App\Game\Entity\SessionSetting;
use App\Game\Repository\SessionSettingRepository;
use App\Tech\Entity\User;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\Mercure\HubInterface;
use Symfony\Component\Mercure\Update;

class GameResponseService
{
    private const LOCK_REVEAL_AFTER_SECONDS = 30;
    private const LOCK_DURATION_SECONDS = 45;
    private const LOCK_PASSCODE_LENGTH = 12;

    public function __construct(
        private Security $security,
        private PlayerService $playerService,
        private SessionSettingRepository $sessionSettingRepository,
        private HubInterface $hub,
        private EntityManagerInterface $entityManager,
        private string $projectDir,
    ) {
    }

    /**
     * Public, code-free view of a player's current lock state, safe to expose on page load
     * (e.g. so the UI can restore the lock banner/countdown after a refresh).
     */
    public function getPublicLockState(Player $player): ?array
    {
        $lock = $this->getLockState($player);

        if ($lock === null || time() >= $lock['unlockAt']) {
            return null;
        }

        return [
            'lockedAt' => $lock['lockedAt'],
            'revealAt' => $lock['revealAt'],
            'unlockAt' => $lock['unlockAt'],
        ];
    }

    public function getGameResponse(string $raw) : array
    {
        $info = json_decode($raw, true);

        $message = $info['message'] ?? '';
        $ts = $info['ts'] ?? '';

        if(!is_string($message))
            return ['error' => 'Invalid message.'];

        $user = $this->security->getUser();

        if(!$user instanceof User)
            return ['error' => 'You are not logged in.'];

        $player = $this->playerService->GetCurrentlyActiveAsPlayer($user);

        if(!$player)
            return ['error' => 'You are not in a game.'];

        $this->enforceLockedFilesRemovalDeadline($player->getSession());

        $this->logSessionActivity($player, 'PLAYER: ' . $message);

        $data = $this->handleLockedPlayer($message, $player);

        if ($data === null) {
            if (str_starts_with($message, '/')) {
                $data = $this->checkGameCommando($message, $player);
            } else {
                $data = $this->checkConsoleCommando($message, $player);
            }
        }

        $responseLog = '';
        if (isset($data['result']) && is_array($data['result'])) {
            foreach ($data['result'] as $line) {
                if (is_array($line)) {
                    $responseLog .= json_encode($line) . "\n";
                } elseif (is_string($line) || is_numeric($line)) {
                    $responseLog .= (string)$line . "\n";
                }
            }
        } elseif (isset($data['error'])) {
            $responseLog = 'ERROR: ' . $data['error'];
        }

        if ($responseLog !== '') {
            $this->logSessionActivity($player, 'SERVER: ' . trim($responseLog));
        }

        return $data;
    }

    private function logSessionActivity(Player $player, string $content): void
    {
        $sessionId = $player->getSession()->getId();
        $username = $player->getUser()->getUsername();
        $logDir = $this->projectDir . '/var/log/sessions/' . $sessionId;

        if (!is_dir($logDir)) {
            mkdir($logDir, 0777, true);
        }

        $logFile = $logDir . '/' . $username . '.txt';
        $timestamp = date('Y-m-d H:i:s');
        $logMessage = sprintf("[%s] %s\n", $timestamp, $content);

        file_put_contents($logFile, $logMessage, FILE_APPEND);
    }

    private function getRechten(Player $player): array
    {
        $settingName = SessionSettingType::tryFrom('RightsForPlayer' . $player->getScreen());

        if (!$settingName) {
            return [];
        }

        $setting = $this->sessionSettingRepository->getSetting($player->getSession(), $settingName, $player);
        if (!$setting || !$setting->getValue()) {
            return [];
        }

        return json_decode($setting->getValue(), true) ?? [];
    }

    private function checkGameCommando(string $message, Player $player) : array
    {
        $messagePart = explode(' ', $message);

        $rechten = $this->getRechten($player);

        switch($messagePart[0]) {
            case '/chat':
                if(!in_array('chat', $rechten))
                    return ['result' => ['Unknown command']];

                if($this->handleChatMessage($message, $player))
                    return ['result' => ['succesfully send']];
                else
                    return ['result' => ['Error sending']];
            case '/help':
                return ['result' => $this->getHelpCommand($rechten)];
            case '/decode':
                if(!in_array('decode', $rechten))
                    return ['result' => ['Unknown command']];

                return ['result' => [$this->handleDecodeMessage($messagePart[1], $player)]];
            case '/verify':
                if(!in_array('verify', $rechten))
                    return ['result' => ['Unknown command']];

                $result = $this->handleVerifyMessage($message, $player);
                return ['result' => [$result]];
            default:
                return ['result' => ['Unknown command']];
        }
    }

    private function checkConsoleCommando(string $message, Player $player, bool $sudo = false) : array
    {
        $messagePart = explode(' ', $message);
        $rechten = $this->getRechten($player);
        switch($messagePart[0]) {
            case 'help':
                return ['result' => $this->getHelpCommand($rechten)];
            case 'ls':
                if(!in_array('ls', $rechten))
                    return ['result' => ['Unknown command']];

                $files = $this->getAllCurrentFilesInDirectory($player);
                return ['result' => $files];
            case 'cd':
                if(!in_array('cd', $rechten))
                    return ['result' => ['Unknown command']];

                $pwd = $this->playerService->getCurrentPwdOfPlayer($player);
                if(!$pwd)
                    return ['result' => ['Unknown command']];
                $newLocation = $this->goToNewDir($pwd, $messagePart[1], $player);

                if($newLocation === false)
                    return ['result' => ['Unknown path']];

                $this->playerService->saveCurrentPwdOfPlayer($player, $newLocation);
                return ['result' => ['Path: ' . $newLocation]];
            case 'cat':
                if(!in_array('cat', $rechten))
                    return ['result' => ['Unknown command']];

                $pwd = $this->playerService->getCurrentPwdOfPlayer($player);

                $fileContent = $this->getFileContent($player, $pwd.'/'.$messagePart[1]);

                return ['result' => $fileContent];
            case 'pwd':
                if(!in_array('pwd', $rechten))
                    return ['result' => ['Unknown command']];

                $pwd = $this->playerService->getCurrentPwdOfPlayer($player);
                return ['result' => ['Path: ' . $pwd]];
            case 'rm':
                if(!in_array('rm', $rechten))
                    return ['result' => ['Unknown command']];

                $pwd = $this->playerService->getCurrentPwdOfPlayer($player);
                if(!$pwd)
                    return ['result' => ['Unknown command']];

                if (!isset($messagePart[1])) {
                    return ['result' => ['Usage: rm {filename}']];
                }

                $filename = $messagePart[1];
                $fullPath = ($pwd === '/' ? '' : $pwd) . '/' . $filename;

                if(!$this->isAllowedToRemove($fullPath, $player, $sudo))
                    return ['result' => ['You are not allowed to remove this file.']];

                $this->playerService->addDeletedFileToSession($player, $fullPath);

                if ($this->checkFilesRemovalWin($player)) {
                    return [
                        'result' => [
                            'File removed: ' . $filename,
                            'MAINFRAME: All protected files purged. The AI virus has been contained. Well done, agents.',
                        ],
                        'messageType' => 'mainframe',
                        'gameWon' => true,
                    ];
                }

                $filesRemovalDeadline = $this->startLockedFilesRemovalDeadline($player->getSession(), $fullPath);
                $lock = $this->triggerLock($player);

                return [
                    'result' => [
                        'File removed: ' . $filename,
                        'AI VIRUS: Intrusion detected. Locking down your terminal...',
                    ],
                    'messageType' => 'virus',
                    'locked' => true,
                    'lockedAt' => $lock['lockedAt'],
                    'revealAt' => $lock['revealAt'],
                    'unlockAt' => $lock['unlockAt'],
                    'filesRemovalDeadline' => $filesRemovalDeadline,
                ];
            case 'sudo':
                if(!in_array('sudo', $rechten))
                    return ['result' => ['Unknown command']];

                $sudo = array_shift($messagePart);
                $message = implode(' ', $messagePart);

                return $this->checkConsoleCommando($message, $player, true);
            case 'scan':
                if(!in_array('scan', $rechten))
                    return ['result' => ['Unknown command']];

                $result = ['Running quarantine scan...', 'Locked files detected:'];
                foreach ($this->getLockedFiles() as $lockedFile) {
                    $result[] = '  ' . $lockedFile;
                }
                $result[] = 'These files are protected by the AI virus. Use sudo rm {file} to remove them.';

                return ['result' => $result];
            default:
                return ['result' => ['Unknown command']];
        }
    }

    private function getHelpCommand(mixed $rechten) : array
    {
        $messages = [];

        foreach($rechten as $recht) {
            switch($recht) {
                case 'chat':
                    $messages[] = '/chat';
                    $messages[] = '   Use /chat {message} to send the message to the other agents.';
                    $messages[] = '   If you want to send a message specifically to one other agent, use the id of the agent after /chat, like /chat 6 {message}';
                    $messages[] = '   This will send the message only to agent with id 6.';
                    $messages[] = '   USAGE: /chat {message}';
                    $messages[] = '   USAGE: /chat 6 {message}';
                    $messages[] = '';
                    break;
                case 'help':
                    $messages[] = '/help';
                    $messages[] = '   This shows this help.';
                    $messages[] = '   USAGE: /help';
                    $messages[] = '';
                    break;
                case 'decode':
                    $messages[] = '/decode';
                    $messages[] = '   This message will decode the message followed by it.';
                    $messages[] = '   Every agent has a different way to decode messages. This is a security measure. The AI Virus has no access to all decoders.';
                    $messages[] = '   USAGE: /decode {message}';
                    $messages[] = '';
                    break;
                case 'pwd':
                    $messages[] = 'pwd';
                    $messages[] = '   This message will let you know what your current location is.';
                    $messages[] = '   It will show you the folder you are in so you can continue navigating the server.';
                    $messages[] = '   USAGE: pwd';
                    $messages[] = '';
                    break;
                case 'cat':
                    $messages[] = 'cat';
                    $messages[] = '   To read a file, use cat {filename}.';
                    $messages[] = '   This will print the full content of the file on the screen.';
                    $messages[] = '   USAGE: cat {filename}';
                    $messages[] = '';
                    break;
                case 'ls':
                    $messages[] = 'ls';
                    $messages[] = '   To show all the files in the current directory, use ls.';
                    $messages[] = '   This will print the full list of directories and files of the current location on your screen.';
                    $messages[] = '   USAGE: ls';
                    $messages[] = '';
                    break;
                case 'rm':
                    $messages[] = 'rm';
                    $messages[] = '   Use rm to delete a file.';
                    $messages[] = '   Be careful with this command. It can not be undone and we do not want to lose any valuable data.';
                    $messages[] = '   USAGE: rm {filename}';
                    $messages[] = '';
                    break;
                case 'cd':
                    $messages[] = 'cd';
                    $messages[] = '   Use cd to move to a different directory.';
                    $messages[] = '   You can go into a folder by using cd {foldername}, or a folder up by using "cd ..".';
                    $messages[] = '   Using cd / moves you to the root directory.';
                    $messages[] = '   USAGE: cd {directory}';
                    $messages[] = '';
                    break;
                case 'sudo':
                    $messages[] = 'sudo';
                    $messages[] = '   If you do not have enough rights to execute a command, you can use sudo to execute it as root.';
                    $messages[] = '   This is only possible for verified users. To verify yourself, use the /verify command.';
                    $messages[] = '   USAGE: sudo {command}';
                    $messages[] = '';
                    break;
                case 'scan':
                    $messages[] = 'scan';
                    $messages[] = '   Runs a quarantine scan that reveals which files are locked by the AI virus.';
                    $messages[] = '   USAGE: scan';
                    $messages[] = '';
                    break;
                case 'verify':
                    $messages[] = '/verify';
                    $messages[] = '   You can verify yourself by using this command.';
                    $messages[] = '   Use this command and follow instructions to verify yourself.';
                    $messages[] = '   USAGE: /verify';
                    $messages[] = '';
                    break;
            }
        }

        return $messages;
    }

    private function handleChatMessage(string $message, Player $player) : bool
    {
        $messageParts = explode(' ', $message);
        $toSingle = false;

        if (isset($messageParts[1]) &&
                is_numeric($messageParts[1]) &&
                $messageParts[1] >= 1 &&
                $messageParts[1] <= $player->getSession()->getGame()->getNumberOfPlayers()) {

            $toSingle = true;
        }

        $chatMessage = array_shift($messageParts);
        $sendTo = 0;

        if ($toSingle) {
            $sendTo = array_shift($messageParts);
            $chatMessage = array_shift($messageParts);
        }

        $message = $player->getUser()->getUsername() . ': ' . $chatMessage . ' ';
        foreach($messageParts as $messagePart) {
            $message .= $messagePart . ' ';
        }

        $message = trim($message);

        $activeGame = $player->getSession()?->getId();

        if(is_null($activeGame))
            return false;

        $topic = '/game/hub/' . $activeGame;
        try {
            $this->hub->publish(new Update($topic, json_encode([$sendTo, $message])));
        } catch (\Exception $e) {
            // Mercure might be down
        }

        $this->updateChatTracking($player, (int)$sendTo);

        $this->checkAndRegenerateVerifyCodes($player, $chatMessage . ' ' . implode(' ', $messageParts));

        return true;
    }

    private function checkAndRegenerateVerifyCodes(Player $player, string $messageContent): void
    {
        $screen = $player->getScreen();
        $session = $player->getSession();

        $verifyCodesSettingName = SessionSettingType::tryFrom('VerifyCodesForPlayer' . $screen);

        if (!$verifyCodesSettingName) {
            return;
        }

        $setting = $this->sessionSettingRepository->getSetting($session, $verifyCodesSettingName, $player);
        if (!$setting) {
            return;
        }

        $codes = json_decode($setting->getValue() ?? '[]', true) ?? [];
        $regenerated = false;

        foreach ($codes as $targetPlayerScreen => $code) {
            if (str_contains($messageContent, (string)$code)) {
                $codes[$targetPlayerScreen] = bin2hex(random_bytes(3));
                $regenerated = true;
            }
        }

        if ($regenerated) {
            $setting->setValue(json_encode($codes));
            $this->entityManager->persist($setting);
            $this->entityManager->flush();

            // Notify the player that their codes have changed
            $topic = '/game/hub/' . $session->getId();
            $notification = "Security Alert: One of your verify codes was shared and has been regenerated.";
            // We send it only to this player (screen)
            try {
                $this->hub->publish(new Update($topic, json_encode([$screen, $notification])));
            } catch (\Exception $e) {
                // Mercure might be down
            }
        }
    }

    private function updateChatTracking(Player $player, int $sendTo): void
    {
        $rights = $this->getRechten($player);
        if(in_array('verify', $rights))
            return;

        $trackingSettingName = SessionSettingType::tryFrom('ChatTrackingForPlayer' . $player->getScreen());

        if (!$trackingSettingName) {
            return;
        }

        $setting = $this->sessionSettingRepository->getSetting($player->getSession(), $trackingSettingName, $player);
        if (!$setting) {
            $setting = new SessionSetting();
            $setting->setSession($player->getSession());
            $setting->setPlayer($player);
            $setting->setName($trackingSettingName);
            $setting->setValue(json_encode([]));
        }

        $tracking = json_decode($setting->getValue() ?? '[]', true) ?? [];
        if (!in_array($sendTo, $tracking)) {
            $tracking[] = $sendTo;
            $setting->setValue(json_encode($tracking));
            $this->entityManager->persist($setting);
            $this->entityManager->flush();

            $this->checkAndGrantVerifyRight($player, $tracking);
        }
    }

    private function checkAndGrantVerifyRight(Player $player, array $tracking): void
    {
        $screen = $player->getScreen();
        $requiredTargets = [0]; // Everyone
        $numPlayers = $player->getSession()->getGame()->getNumberOfPlayers();
        for ($i = 1; $i <= $numPlayers; $i++) {
            if ($i !== $screen) {
                $requiredTargets[] = $i;
            }
        }

        // Check if all required targets are in tracking
        foreach ($requiredTargets as $target) {
            if (!in_array($target, $tracking)) {
                return;
            }
        }

        // Grant verify right
        $rightsSettingName = SessionSettingType::tryFrom('RightsForPlayer' . $screen);

        if (!$rightsSettingName) {
            return;
        }

        $setting = $this->sessionSettingRepository->getSetting($player->getSession(), $rightsSettingName, $player);
        if (!$setting) {
            return; // Should have been initialized
        }

        $rights = json_decode($setting->getValue() ?? '[]', true) ?? [];
        $newRights = ['verify', 'cat'];

        $updated = false;
        foreach ($newRights as $newRight) {
            if (!in_array($newRight, $rights)) {
                $rights[] = $newRight;
                $updated = true;
            }
        }

        if ($updated) {
            $setting->setValue(json_encode($rights));
            $this->entityManager->persist($setting);
            $this->entityManager->flush();
        }
    }

    private function handleDecodeMessage(string $message, Player $player): string
    {
        $userNumber = $player->getScreen();

        preg_match('/\d/', $message, $matches);
        $num = $matches[0] ?? null;
        $randomString = $this->generateRandomString(250, 500);

        if (is_null($num) || (int)$num !== $userNumber) {
            return $randomString;
        }

        $decodeMessage = match ($userNumber) {
            1 => DecodeMessage::PLAYER_1,
            2 => DecodeMessage::PLAYER_2,
            3 => DecodeMessage::PLAYER_3,
            default => null,
        };

        if ($decodeMessage === null) {
            return $randomString;
        }

        if ($decodeMessage === DecodeMessage::PLAYER_1) {
            $this->grantRightToAllPlayers($player->getSession(), 'sudo');
            $this->grantRightToAllPlayers($player->getSession(), 'rm');
        }

        if ($decodeMessage === DecodeMessage::PLAYER_2) {
            $this->grantRightToAllPlayers($player->getSession(), 'scan');
        }

        return $decodeMessage->value;
    }

    private function grantRightToAllPlayers(Session $session, string $right): void
    {
        $updated = false;

        foreach ($session->getPlayers() as $sessionPlayer) {
            $rightsSettingName = SessionSettingType::tryFrom('RightsForPlayer' . $sessionPlayer->getScreen());
            if (!$rightsSettingName) {
                continue;
            }

            $setting = $this->sessionSettingRepository->getSetting($session, $rightsSettingName, $sessionPlayer);
            if (!$setting) {
                continue;
            }

            $rights = json_decode($setting->getValue() ?? '[]', true) ?? [];
            if (!in_array($right, $rights)) {
                $rights[] = $right;
                $setting->setValue(json_encode($rights));
                $this->entityManager->persist($setting);
                $updated = true;
            }
        }

        if ($updated) {
            $this->entityManager->flush();
        }
    }

    /**
     * Returns null if the player isn't locked (or their lock already expired), otherwise
     * a result array to short-circuit normal command handling.
     */
    private function handleLockedPlayer(string $message, Player $player): ?array
    {
        $lock = $this->getLockState($player);

        if ($lock === null) {
            return null;
        }

        $now = time();

        if ($now >= $lock['unlockAt']) {
            return null;
        }

        $trimmed = trim($message);

        if (stripos($trimmed, '/unlock ') === 0) {
            $attempt = trim(substr($trimmed, 8));

            if ($now >= $lock['revealAt'] && hash_equals($lock['code'], $attempt)) {
                $this->setLockUnlockAt($player, $now);

                return [
                    'result' => ['ACCESS RESTORED. Welcome back, agent.'],
                    'messageType' => 'mainframe',
                    'locked' => false,
                ];
            }

            return $this->lockStatusResponse('ACCESS DENIED: Incorrect passcode.', 'virus', $lock);
        }

        if ($now >= $lock['revealAt']) {
            return $this->lockStatusResponse(
                'MAINFRAME: Recovery code acquired: ' . $lock['code'] . '. Use /unlock ' . $lock['code'] . ' to restore access.',
                'mainframe',
                $lock
            );
        }

        return $this->lockStatusResponse(
            'AI VIRUS: SYSTEM LOCKED. Countermeasures engaged. Stand by for mainframe recovery protocol.',
            'virus',
            $lock
        );
    }

    private function lockStatusResponse(string $text, string $messageType, array $lock): array
    {
        return [
            'result' => [$text],
            'messageType' => $messageType,
            'locked' => true,
            'lockedAt' => $lock['lockedAt'],
            'revealAt' => $lock['revealAt'],
            'unlockAt' => $lock['unlockAt'],
        ];
    }

    private function getLockState(Player $player): ?array
    {
        $settingName = SessionSettingType::tryFrom('LockForPlayer' . $player->getScreen());
        if (!$settingName) {
            return null;
        }

        $setting = $this->sessionSettingRepository->getSetting($player->getSession(), $settingName, $player);
        if (!$setting || !$setting->getValue()) {
            return null;
        }

        $state = json_decode($setting->getValue(), true);
        if (!is_array($state) || !isset($state['lockedAt'], $state['revealAt'], $state['unlockAt'], $state['code'])) {
            return null;
        }

        return $state;
    }

    private function triggerLock(Player $player): array
    {
        $now = time();
        $state = [
            'lockedAt' => $now,
            'revealAt' => $now + self::LOCK_REVEAL_AFTER_SECONDS,
            'unlockAt' => $now + self::LOCK_DURATION_SECONDS,
            'code' => $this->generatePasscode(),
        ];

        $settingName = SessionSettingType::tryFrom('LockForPlayer' . $player->getScreen());
        if ($settingName) {
            $setting = $this->sessionSettingRepository->getSetting($player->getSession(), $settingName, $player);
            if (!$setting) {
                $setting = new SessionSetting();
                $setting->setSession($player->getSession());
                $setting->setPlayer($player);
                $setting->setName($settingName);
            }

            $setting->setValue(json_encode($state));
            $this->entityManager->persist($setting);
            $this->entityManager->flush();
        }

        return $state;
    }

    private function setLockUnlockAt(Player $player, int $unlockAt): void
    {
        $settingName = SessionSettingType::tryFrom('LockForPlayer' . $player->getScreen());
        if (!$settingName) {
            return;
        }

        $setting = $this->sessionSettingRepository->getSetting($player->getSession(), $settingName, $player);
        if (!$setting || !$setting->getValue()) {
            return;
        }

        $state = json_decode($setting->getValue(), true);
        if (!is_array($state)) {
            return;
        }

        $state['unlockAt'] = $unlockAt;
        $setting->setValue(json_encode($state));
        $this->entityManager->persist($setting);
        $this->entityManager->flush();
    }

    private function generatePasscode(): string
    {
        $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
        $charactersLength = strlen($characters);
        $code = '';
        for ($i = 0; $i < self::LOCK_PASSCODE_LENGTH; $i++) {
            $code .= $characters[random_int(0, $charactersLength - 1)];
        }
        return $code;
    }

    private function generateRandomString(int $min, int $max): string
    {
        $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ ';
        $charactersLength = strlen($characters);
        $randomString = '';
        $length = random_int($min, $max);
        for ($i = 0; $i < $length; $i++) {
            $randomString .= $characters[random_int(0, $charactersLength - 1)];
        }
        return $randomString;
    }

    private function generateSpecialCode(int $firstDigit, int $min, int $max): string
    {
        $code = $this->generateRandomString($min, $max);

        // Ensure the first numeric digit is the specified $firstDigit
        $found = false;
        $codeArray = str_split($code);
        for ($i = 0; $i < count($codeArray); $i++) {
            if (ctype_digit($codeArray[$i])) {
                $codeArray[$i] = (string)$firstDigit;
                $found = true;
                break;
            }
        }
        $code = implode('', $codeArray);

        // If no digit was found (highly unlikely given the character set), prepend it
        if (!$found) {
            $code = (string)$firstDigit . substr($code, 1);
        }

        return $code;
    }

    private function handleVerifyMessage(string $message, Player $player) : string
    {
        $messageParts = explode(' ', $message);
        if (count($messageParts) < 2) {
            return 'Usage: /verify {code}';
        }

        $code = $messageParts[1];
        $screen = $player->getScreen();
        $session = $player->getSession();

        $progressSettingName = SessionSettingType::tryFrom('VerificationProgressForPlayer' . $screen);

        if (!$progressSettingName) {
            return 'Error: Invalid player screen.';
        }

        $progressSetting = $this->sessionSettingRepository->getSetting($session, $progressSettingName, $player);
        if (!$progressSetting) {
            return 'Error: Verification progress setting not found.';
        }

        $progress = json_decode($progressSetting->getValue() ?? '[]', true) ?? [];

        $verifiedBy = null;
        foreach ($session->getPlayers() as $otherPlayer) {
            if ($otherPlayer->getId() === $player->getId()) {
                continue;
            }

            $otherScreen = $otherPlayer->getScreen();
            $codesSettingName = SessionSettingType::tryFrom('VerifyCodesForPlayer' . $otherScreen);

            if (!$codesSettingName) {
                continue;
            }

            $codesSetting = $this->sessionSettingRepository->getSetting($session, $codesSettingName, $otherPlayer);
            if (!$codesSetting) {
                continue;
            }

            $codes = json_decode($codesSetting->getValue() ?? '[]', true) ?? [];
            if (isset($codes[$screen]) && $codes[$screen] === $code) {
                $verifiedBy = $otherScreen;
                break;
            }
        }

        if ($verifiedBy !== null) {
            if (!in_array($verifiedBy, $progress)) {
                $progress[] = $verifiedBy;
                $progressSetting->setValue(json_encode($progress));
                $this->entityManager->persist($progressSetting);
                $this->entityManager->flush();

                $response = 'You have been successfully verified by Agent ' . $verifiedBy . '.';

                if (count($progress) >= 2) {
                    $this->grantVerificationRights($player);
                    $response .= ' You have received additional rights!';
                }

                return $response;
            } else {
                return 'You were already verified by Agent ' . $verifiedBy . '.';
            }
        }

        return 'Invalid verification code.';
    }

    private function grantVerificationRights(Player $player): void
    {
        $screen = $player->getScreen();
        $rightsSettingName = SessionSettingType::tryFrom('RightsForPlayer' . $screen);

        if (!$rightsSettingName) {
            return;
        }

        $setting = $this->sessionSettingRepository->getSetting($player->getSession(), $rightsSettingName, $player);
        if (!$setting) {
            return;
        }

        $rights = json_decode($setting->getValue() ?? '[]', true) ?? [];
        $newRights = ['cd', 'decode'];

        $updated = false;
        foreach ($newRights as $newRight) {
            if (!in_array($newRight, $rights)) {
                $rights[] = $newRight;
                $updated = true;
            }
        }

        if ($updated) {
            $setting->setValue(json_encode($rights));
            $this->entityManager->persist($setting);
            $this->entityManager->flush();

            $this->checkIfAllPlayersVerified($player);
        }
    }

    private function checkIfAllPlayersVerified(Player $player): void
    {
        $session = $player->getSession();
        $everyoneVerifiedSetting = $this->sessionSettingRepository->getSetting($session, SessionSettingType::EVERYONE_VERIFIED);

        if ($everyoneVerifiedSetting && $everyoneVerifiedSetting->getValue() === 'true') {
            return;
        }

        $allVerified = true;
        foreach ($session->getPlayers() as $otherPlayer) {
            $otherScreen = $otherPlayer->getScreen();
            $progressSettingName = SessionSettingType::tryFrom('VerificationProgressForPlayer' . $otherScreen);

            if (!$progressSettingName) {
                continue;
            }

            $progressSetting = $this->sessionSettingRepository->getSetting($session, $progressSettingName, $otherPlayer);
            $progress = json_decode($progressSetting?->getValue() ?? '[]', true) ?? [];

            if (count($progress) < $session->getGame()->getNumberOfPlayers() - 1) {
                $allVerified = false;
                break;
            }
        }

        if ($allVerified) {
            if (!$everyoneVerifiedSetting) {
                $everyoneVerifiedSetting = new SessionSetting();
                $everyoneVerifiedSetting->setSession($session);
                $everyoneVerifiedSetting->setName(SessionSettingType::EVERYONE_VERIFIED);
            }
            $everyoneVerifiedSetting->setValue('true');
            $this->entityManager->persist($everyoneVerifiedSetting);
            $this->entityManager->flush();

            $topic = '/game/hub/' . $session->getId();
            $message = "Mainframe Help Modus: Agents Doyle, Vega and Lennox rapports have been updated with coded messages.";
            try {
                $this->hub->publish(new Update($topic, json_encode([0, $message])));
            } catch (\Exception $e) {
                // Mercure might be down
            }
        }
    }

    private function goToNewDir(string $pwd, string $newPwd, Player $player) : string|bool
    {
        $allPossiblePaths = $this->getAllPossiblePaths($player);

        $dirParts = explode('/', $newPwd);

        $int = count($dirParts);

        if($dirParts[0] == '') {
            $newDir = '';
            $startPart = 1;
        } else {
            $newDir = $pwd;
            $startPart = 0;
        }

        for($i = $startPart; $i < $int; $i++) {
            if($dirParts[$i] == '..')
                $newDir = $this->getPrevPath($newDir);
            else
                $newDir .= '/' . $dirParts[$i];

            if(!in_array($newDir, $allPossiblePaths))
                return false;
        }

        return $newDir;
    }

    private function getPrevPath(string $pwd) : string
    {
        $pwdParts = explode('/', $pwd);
        array_pop($pwdParts);
        $pwd = implode('/', $pwdParts);
        return $pwd;
    }

    private function getAllPossiblePaths(Player $player) : array
    {
        $paths = [];

        $paths[] = '/';
        $paths[] = '/var';
        $paths[] = '/var/arrest';
        $paths[] = '/var/www';
        $paths[] = '/var/marriage';
        $paths[] = '/var/rapports';
        $paths[] = '/var/linking';

        $paths[] = '/etc';
        $paths[] = '/etc/short';
        $paths[] = '/etc/long';
        $paths[] = '/etc/arrest';
        $paths[] = '/etc/power';
        $paths[] = '/etc/break';
        $paths[] = '/etc/handle';
        $paths[] = '/etc/freak';
        $paths[] = '/etc/host';

        $paths[] = '/var/home';

        $playerNames = ['root', 'Luke', 'Charles', 'William', 'Peter'];

        $players = $player->getSession()->getPlayers();

        foreach($players as $p) {
            $playerNames[] = $p->getUser()->getUsername();
        }

        $playerNames = array_unique($playerNames);

        foreach($playerNames as $name) {
            $paths[] = '/var/home/' . $name;
        }

        return $paths;
    }

    private function isAllowedToRemove(string $file, Player $player, bool $sudo) : bool
    {
        if(!$this->fileExists($file, $player))
            return false;

        if(str_starts_with($file, '/var/rapports/'))
            return false;

        $rights = $this->getRechten($player);

        if(in_array('sudo', $rights) || $sudo)
            return true;

        return !in_array($file, $this->getLockedFiles());
    }

    private function getLockedFiles() : array
    {
        return [
            '/var/arrest/handle.sh',
            '/var/arrest/cell.sh',
            '/var/marriage/divorce.sh',
        ];
    }

    /**
     * Checks whether all locked files are currently removed and, if so, marks the session
     * as won (once) and broadcasts a "game finished" signal to every connected player.
     */
    private function checkFilesRemovalWin(Player $player): bool
    {
        $session = $player->getSession();

        if ($session->getStatus() !== SessionStatus::PLAYING) {
            return $session->getStatus() === SessionStatus::WON;
        }

        $deletedFiles = $this->playerService->getDeletedFilesOfSession($player);
        $lockedFiles = $this->getLockedFiles();

        if (count(array_intersect($lockedFiles, $deletedFiles)) < count($lockedFiles)) {
            return false;
        }

        $session->setStatus(SessionStatus::WON);
        $this->entityManager->persist($session);
        $this->entityManager->flush();

        $topic = '/game/hub/' . $session->getId();
        try {
            $this->hub->publish(new Update($topic, json_encode(['type' => 'game_finished', 'status' => 'won'])));
        } catch (\Exception $e) {
            // Mercure might be down
        }

        return true;
    }

    /**
     * Starts (if not already running) the 60-second window within which all locked files
     * must be removed, or the ones already removed get restored by the virus. Returns the
     * (new or already-running) deadline as a unix timestamp, or null if the removed file
     * wasn't a locked one.
     */
    private function startLockedFilesRemovalDeadline(Session $session, string $removedFile): ?int
    {
        if (!in_array($removedFile, $this->getLockedFiles())) {
            return null;
        }

        $setting = $this->sessionSettingRepository->getSetting($session, SessionSettingType::LOCKED_FILES_REMOVAL_DEADLINE);
        if ($setting && $setting->getValue()) {
            return (int)$setting->getValue(); // Window already running
        }

        if (!$setting) {
            $setting = new SessionSetting();
            $setting->setSession($session);
            $setting->setName(SessionSettingType::LOCKED_FILES_REMOVAL_DEADLINE);
        }

        $deadline = time() + 60;
        $setting->setValue((string)$deadline);
        $this->entityManager->persist($setting);
        $this->entityManager->flush();

        return $deadline;
    }

    /**
     * Public, session-wide view of the active locked-files removal deadline (if any), safe
     * to expose on page load so the UI can schedule its auto-check timer after a refresh.
     */
    public function getPublicLockedFilesDeadline(Session $session): ?int
    {
        $setting = $this->sessionSettingRepository->getSetting($session, SessionSettingType::LOCKED_FILES_REMOVAL_DEADLINE);
        if (!$setting || !$setting->getValue()) {
            return null;
        }

        $deadline = (int)$setting->getValue();

        return $deadline > time() ? $deadline : null;
    }

    /**
     * Lazily checked on every player interaction: if the 60-second window to remove all
     * locked files has expired without all of them being removed, restore whichever ones
     * were removed and clear the window.
     */
    private function enforceLockedFilesRemovalDeadline(Session $session): void
    {
        if ($session->getStatus() !== SessionStatus::PLAYING) {
            return;
        }

        $setting = $this->sessionSettingRepository->getSetting($session, SessionSettingType::LOCKED_FILES_REMOVAL_DEADLINE);
        if (!$setting || !$setting->getValue()) {
            return;
        }

        if (time() < (int)$setting->getValue()) {
            return;
        }

        $player = $session->getPlayers()->first() ?: null;
        $lockedFiles = $this->getLockedFiles();

        if ($player) {
            $deletedFiles = $this->playerService->getDeletedFilesOfSession($player);
            $stillLocked = array_intersect($lockedFiles, $deletedFiles);

            if (count($stillLocked) < count($lockedFiles)) {
                foreach ($stillLocked as $file) {
                    $this->playerService->removeDeletedFileFromSession($player, $file);
                }

                if (!empty($stillLocked)) {
                    $topic = '/game/hub/' . $session->getId();
                    $message = 'AI VIRUS: Integrity check complete. Restored files that were not fully purged in time.';
                    try {
                        $this->hub->publish(new Update($topic, json_encode([0, $message, 'virus'])));
                    } catch (\Exception $e) {
                        // Mercure might be down
                    }
                }
            }
        }

        $setting->setValue(null);
        $this->entityManager->persist($setting);
        $this->entityManager->flush();
    }

    private function fileExists(string $file, Player $player) : bool
    {
        $files = $this->getAllPossibleFiles($player);

        if(in_array($file, $files))
            return true;

        return false;
    }

    private function getAllPossibleFiles(Player $player = null) : array
    {
        $files = [];

        $files[] = '/var/arrest/handle.sh';
        $files[] = '/var/arrest/bars.sh';
        $files[] = '/var/arrest/cell.sh';
        $files[] = '/var/marriage/share.sh';
        $files[] = '/var/marriage/divorce.sh';
        $files[] = '/var/rapports/095_07-14.txt';
        $files[] = '/var/rapports/007_19-52.txt';
        $files[] = '/var/rapports/083_25-39.txt';
        $files[] = '/var/rapports/019_31-11.txt';
        $files[] = '/var/rapports/075_46-77.txt';
        $files[] = '/var/rapports/031_53-28.txt';
        $files[] = '/var/rapports/072_61-05.txt';
        $files[] = '/var/rapports/064_72-90.txt';
        $files[] = '/var/rapports/091_81-33.txt';
        $files[] = '/var/rapports/079_89-47.txt';
        $files[] = '/var/rapports/098_92-14.txt';
        $files[] = '/var/rapports/012_94-31.txt';
        $files[] = '/var/rapports/016_98-07.txt';
        $files[] = '/var/rapports/087_102-45.txt';
        $files[] = '/var/rapports/094_110-19.txt';
        $files[] = '/var/rapports/063_117-56.txt';
        $files[] = '/var/rapports/017_123-88.txt';
        $files[] = '/var/rapports/093_138-24.txt';
        $files[] = '/var/rapports/001_145-93.txt';
        $files[] = '/var/rapports/011_130-62.txt';
        $files[] = '/var/rapports/index.txt';

        if ($player === null) {
            return $files;
        }

        $players = $player->getSession()->getPlayers();

        foreach($players as $p) {
            $files[] = '/var/home/' . $p->getUser()->getUsername() . '/verifyCodes.txt';
        }

        return $files;
    }

    private function getAllCurrentFilesInDirectory(Player $player) : array
    {
        $pwd = $this->playerService->getCurrentPwdOfPlayer($player);
        if (!$pwd) {
            return [];
        }

        $allPaths = $this->getAllPossiblePaths($player);
        $allFiles = $this->getAllPossibleFiles($player);
        $deletedFiles = $this->playerService->getDeletedFilesOfSession($player);

        $entries = [];

        // Find directories in current pwd
        foreach ($allPaths as $path) {
            if ($path === $pwd) {
                continue;
            }

            // Check if $path is a direct child of $pwd
            $parent = $this->getPrevPath($path);
            if ($parent === $pwd) {
                $parts = explode('/', $path);
                $entries[] = [end($parts) . '/', 'dir'];
            }
        }

        // Find files in current pwd
        foreach ($allFiles as $file) {
            if (in_array($file, $deletedFiles)) {
                continue;
            }

            $parent = $this->getPrevPath($file);
            if ($parent === $pwd) {
                $parts = explode('/', $file);
                $entries[] = [end($parts), 'file'];
            }
        }

        sort($entries);
        return $entries;
    }

    public function getFileContent(Player $player, string $file) : array
    {
        $allPossibleFiles = $this->getAllPossibleFiles($player);
        if (!in_array($file, $allPossibleFiles)) {
            return ['File does not exist'];
        }

        if (str_ends_with($file, '.sh')) {
            return ['It is not possible to read this file'];
        }

        if (str_ends_with($file, 'verifyCodes.txt')) {
            return $this->readVerificationFile($player, $file);
        }

        $physicalPath = $this->projectDir . '/assets/game1/filesystem' . $file;
        if (!file_exists($physicalPath)) {
            return ['File does not exist'];
        }

        $content = file($physicalPath);
        if ($content === false) {
            return ['Error reading file'];
        }

        $specialFiles = [
            '/var/rapports/083_25-39.txt' => [
                'setting' => SessionSettingType::SPECIAL_REPORT_CODE_DOYLE,
                'digit' => 1
            ],
            '/var/rapports/019_31-11.txt' => [
                'setting' => SessionSettingType::SPECIAL_REPORT_CODE_VEGA,
                'digit' => 2
            ],
            '/var/rapports/011_130-62.txt' => [
                'setting' => SessionSettingType::SPECIAL_REPORT_CODE_LENNOX,
                'digit' => 3
            ],
        ];

        if (isset($specialFiles[$file])) {
            $everyoneVerifiedSetting = $this->sessionSettingRepository->getSetting($player->getSession(), SessionSettingType::EVERYONE_VERIFIED);
            if ($everyoneVerifiedSetting && $everyoneVerifiedSetting->getValue() === 'true') {
                $settingInfo = $specialFiles[$file];
                $codeSetting = $this->sessionSettingRepository->getSetting($player->getSession(), $settingInfo['setting']);

                if (!$codeSetting) {
                    $codeSetting = new SessionSetting();
                    $codeSetting->setSession($player->getSession());
                    $codeSetting->setName($settingInfo['setting']);
                    $codeSetting->setValue($this->generateSpecialCode($settingInfo['digit'], 75, 100));

                    $this->entityManager->persist($codeSetting);
                    $this->entityManager->flush();
                }

                $specialCode = $codeSetting->getValue();

                $newContent = [];
                foreach ($content as $line) {
                    $newContent[] = $line;
                    if (str_starts_with(trim($line), 'Date:')) {
                        $newContent[] = $specialCode . "\n";
                    }
                }
                $content = $newContent;
            }
        }

        return $content;
    }

    private function readVerificationFile(Player $player, string $file)
    {
        $parts = explode('/', $file);
        $ownerUsername = $parts[3] ?? null;

        $ownerPlayer = null;
        foreach ($player->getSession()->getPlayers() as $p) {
            if ($p->getUser()->getUsername() === $ownerUsername) {
                $ownerPlayer = $p;
                break;
            }
        }

        if (!$ownerPlayer) {
            return 'File does not exist';
        }

        $screen = $ownerPlayer->getScreen();
        $settingName = SessionSettingType::tryFrom('VerifyCodesForPlayer' . $screen);

        if (!$settingName) {
            return 'Error: Invalid player screen.';
        }

        $setting = $this->sessionSettingRepository->getSetting($player->getSession(), $settingName, $ownerPlayer);
        if (!$setting) {
            $setting = new SessionSetting();
            $setting->setSession($player->getSession());
            $setting->setPlayer($ownerPlayer);
            $setting->setName($settingName);
        }

        $codes = json_decode($setting->getValue() ?? '[]', true) ?? [];

        $playerNames = ['Luke', 'Charles', 'William', 'Peter'];
        foreach ($player->getSession()->getPlayers() as $p) {
            $playerNames[] = $p->getUser()->getUsername();
        }
        $playerNames = array_unique($playerNames);
        sort($playerNames);

        $content = [];

        $content[] = "Verification codes:";
        $content[] = "";

        foreach ($playerNames as $name) {
            $key = null;
            foreach ($player->getSession()->getPlayers() as $p) {
                if ($p->getUser()->getUsername() === $name) {
                    $key = (string)$p->getScreen();
                    break;
                }
            }
            if ($key === null) {
                $key = $name;
            }

            if (!isset($codes[$key])) {
                $codes[$key] = bin2hex(random_bytes(3));
            }

            $content[] = $name . ": " . $codes[$key] . "\n";
        }

        return $content;
    }
}
