using LiquidCode.Tester.Worker.Services.Isolate;

namespace LiquidCode.Tester.Worker.Services;

/// <summary>
/// Service for running custom checkers in Isolate sandbox
/// </summary>
public class CheckerServiceIsolate
{
    private readonly ILogger<CheckerServiceIsolate> _logger;
    private readonly IsolateService _isolateService;
    private readonly IsolateBoxPool _boxPool;

    private const int CheckerTimeLimitSeconds = 5;
    private const int CheckerMemoryLimitMb = 256;

    public CheckerServiceIsolate(
        ILogger<CheckerServiceIsolate> logger,
        IsolateService isolateService,
        IsolateBoxPool boxPool)
    {
        _logger = logger;
        _isolateService = isolateService;
        _boxPool = boxPool;
    }

    /// <summary>
    /// Check user output using custom checker in Isolate sandbox
    /// </summary>
    /// <param name="checkerPath">Path to checker executable</param>
    /// <param name="inputPath">Path to input file</param>
    /// <param name="userOutput">User program output</param>
    /// <param name="answerPath">Path to answer file</param>
    /// <returns>Checker result</returns>
    public async Task<CheckerResult> CheckAsync(
        string checkerPath,
        string inputPath,
        string userOutput,
        string answerPath)
    {
        if (!File.Exists(checkerPath))
        {
            _logger.LogError("Checker not found: {CheckerPath}", checkerPath);
            return new CheckerResult
            {
                Accepted = false,
                ExitCode = -1,
                Message = "Checker executable not found"
            };
        }

        int boxId = -1;

        try
        {
            boxId = await _boxPool.AcquireBoxAsync();
            _logger.LogDebug("Acquired isolate box {BoxId} for checker execution", boxId);

            await _isolateService.InitBoxAsync(boxId);

            var boxDir = $"/var/local/lib/isolate/{boxId}/box";

            // Copy checker executable to box
            var checkerName = Path.GetFileName(checkerPath);
            var boxCheckerPath = Path.Combine(boxDir, checkerName);
            File.Copy(checkerPath, boxCheckerPath, overwrite: true);

            // Make checker executable
            var chmodProcess = System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo
            {
                FileName = "chmod",
                Arguments = $"+x {boxCheckerPath}",
                UseShellExecute = false
            });
            chmodProcess?.WaitForExit();

            // Copy input file
            var inputName = "input.txt";
            var boxInputPath = Path.Combine(boxDir, inputName);
            File.Copy(inputPath, boxInputPath, overwrite: true);

            // Save user output to file in box
            var outputName = "output.txt";
            var boxOutputPath = Path.Combine(boxDir, outputName);
            await File.WriteAllTextAsync(boxOutputPath, userOutput);

            // Copy answer file
            var answerName = "answer.txt";
            var boxAnswerPath = Path.Combine(boxDir, answerName);
            File.Copy(answerPath, boxAnswerPath, overwrite: true);

            // Prepare files for checker stdout/stderr
            var stdoutPath = Path.Combine(boxDir, "checker_stdout.txt");
            var stderrPath = Path.Combine(boxDir, "checker_stderr.txt");

            _logger.LogDebug("Running checker in Isolate: {Checker} {Input} {Output} {Answer}",
                checkerName, inputName, outputName, answerName);

            // Run checker in Isolate
            // Checker arguments: <input> <output> <answer>
            var isolateResult = await _isolateService.RunAsync(new IsolateRunOptions
            {
                BoxId = boxId,
                Executable = $"/box/{checkerName}",
                Arguments = new[] { $"/box/{inputName}", $"/box/{outputName}", $"/box/{answerName}" },
                TimeLimitSeconds = CheckerTimeLimitSeconds,
                WallTimeLimitSeconds = CheckerTimeLimitSeconds * 2,
                MemoryLimitKb = CheckerMemoryLimitMb * 1024,
                StackLimitKb = 64 * 1024,
                ProcessLimit = 1, // Single process for checker
                EnableNetwork = false,
                StdoutFile = stdoutPath,
                StderrFile = stderrPath,
                WorkingDirectory = "/box"
            });

            // Read checker output
            var stdout = string.Empty;
            if (File.Exists(stdoutPath))
            {
                stdout = await File.ReadAllTextAsync(stdoutPath);
            }

            var stderr = string.Empty;
            if (File.Exists(stderrPath))
            {
                stderr = await File.ReadAllTextAsync(stderrPath);
            }

            if (isolateResult.TimeLimitExceeded)
            {
                _logger.LogWarning("Checker timeout in box {BoxId}", boxId);
                return new CheckerResult
                {
                    Accepted = false,
                    ExitCode = -1,
                    Message = "Checker timeout",
                    Verdict = CheckerVerdict.CheckerFailed
                };
            }

            if (isolateResult.MemoryLimitExceeded)
            {
                _logger.LogWarning("Checker memory limit exceeded in box {BoxId}", boxId);
                return new CheckerResult
                {
                    Accepted = false,
                    ExitCode = -1,
                    Message = "Checker memory limit exceeded",
                    Verdict = CheckerVerdict.CheckerFailed
                };
            }

            if (isolateResult.RuntimeError)
            {
                _logger.LogWarning("Checker runtime error in box {BoxId}: {Message}", boxId, isolateResult.Message);
                return new CheckerResult
                {
                    Accepted = false,
                    ExitCode = isolateResult.ExitCode,
                    Message = $"Checker runtime error: {isolateResult.Message}",
                    Verdict = CheckerVerdict.CheckerFailed
                };
            }

            var exitCode = isolateResult.ExitCode;
            var message = string.IsNullOrWhiteSpace(stderr) ? stdout : stderr;

            _logger.LogDebug("Checker exit code: {ExitCode}, message: {Message}", exitCode, message);

            return new CheckerResult
            {
                Accepted = exitCode == 0,
                ExitCode = exitCode,
                Message = message,
                Verdict = GetVerdictFromExitCode(exitCode)
            };
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error running checker in Isolate");
            return new CheckerResult
            {
                Accepted = false,
                ExitCode = -1,
                Message = $"Checker error: {ex.Message}",
                Verdict = CheckerVerdict.CheckerFailed
            };
        }
        finally
        {
            if (boxId >= 0)
            {
                try
                {
                    await _isolateService.CleanupBoxAsync(boxId);
                    _logger.LogDebug("Cleaned up isolate box {BoxId}", boxId);
                }
                catch (Exception ex)
                {
                    _logger.LogWarning(ex, "Failed to cleanup checker box {BoxId}", boxId);
                }

                _boxPool.ReleaseBox(boxId);
                _logger.LogDebug("Released checker box {BoxId} back to pool", boxId);
            }
        }
    }

    private CheckerVerdict GetVerdictFromExitCode(int exitCode)
    {
        return exitCode switch
        {
            0 => CheckerVerdict.OK,
            1 => CheckerVerdict.WrongAnswer,
            2 => CheckerVerdict.PresentationError,
            3 => CheckerVerdict.CheckerFailed,
            7 => CheckerVerdict.PartialScore,
            _ => CheckerVerdict.Unknown
        };
    }
}
