using System.Diagnostics;
using LiquidCode.Tester.Worker.Services.Isolate;

namespace LiquidCode.Tester.Worker.Services;

/// <summary>
/// Java program execution service using Isolate sandbox
/// </summary>
public class JavaExecutionServiceIsolate : IExecutionService
{
    private readonly ILogger<JavaExecutionServiceIsolate> _logger;
    private readonly IsolateService _isolateService;
    private readonly IsolateBoxPool _boxPool;

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

    public async Task<ExecutionResult> ExecuteAsync(
        string executablePath,
        string inputFilePath,
        int timeLimitMs,
        int memoryLimitMb)
    {
        var workingDirectory = Path.GetDirectoryName(executablePath)!;
        _logger.LogInformation(
            "Executing Java with Isolate from {WorkingDirectory}: time={TimeLimit}ms, memory={MemoryLimit}MB",
            workingDirectory, timeLimitMs, memoryLimitMb);

        var result = new ExecutionResult();
        var stopwatch = Stopwatch.StartNew();
        int boxId = -1;

        try
        {
            // Acquire a box from pool
            boxId = await _boxPool.AcquireBoxAsync();
            _logger.LogDebug("Acquired isolate box {BoxId}", boxId);

            // Initialize the box
            await _isolateService.InitBoxAsync(boxId);

            // Copy all .class files to box (Java needs classpath)
            var boxDir = $"/var/local/lib/isolate/{boxId}/box";

            // Copy all files from working directory (includes Solution.class and dependencies)
            foreach (var file in Directory.GetFiles(workingDirectory, "*.*"))
            {
                var fileName = Path.GetFileName(file);
                var destPath = Path.Combine(boxDir, fileName);
                File.Copy(file, destPath, overwrite: true);
            }

            // Prepare input/output files inside the sandbox
            var outputFilePath = Path.Combine(boxDir, "output.txt");
            string? sandboxInputPath = null;

            if (!string.IsNullOrEmpty(inputFilePath) && File.Exists(inputFilePath))
            {
                sandboxInputPath = Path.Combine(boxDir, "input.txt");
                File.Copy(inputFilePath, sandboxInputPath, overwrite: true);
            }

            // Run in Isolate
            // Note: Java needs more memory for JVM overhead
            // We set JVM heap limit to the requested memory, but give Isolate more for JVM internals
            var jvmHeapMb = memoryLimitMb;
            var jvmTotalMemoryMb = memoryLimitMb + 64; // Add 64MB for JVM overhead (metaspace, code cache, etc.)

            // Extract class name from .class file path
            var className = Path.GetFileNameWithoutExtension(executablePath);

            var arguments = new List<string>
            {
                $"-Xmx{jvmHeapMb}m",  // Max heap size = requested memory limit
                $"-Xms{Math.Min(jvmHeapMb, 32)}m",  // Initial heap size (min 32MB or requested)
                "-cp", "/box", className
            };

            var isolateResult = await _isolateService.RunAsync(new IsolateRunOptions
            {
                BoxId = boxId,
                Executable = "/usr/bin/java",
                Arguments = arguments.ToArray(),
                TimeLimitSeconds = timeLimitMs / 1000.0,
                WallTimeLimitSeconds = (timeLimitMs / 1000.0) * 2,
                MemoryLimitKb = jvmTotalMemoryMb * 1024,
                StackLimitKb = 256 * 1024, // 256 MB stack
                ProcessLimit = 64, // Java creates multiple threads
                EnableNetwork = false,
                StdinFile = sandboxInputPath,
                StdoutFile = outputFilePath,
                WorkingDirectory = "/box",
                DirectoryBindings = new List<DirectoryBinding>
                {
                    new DirectoryBinding { HostPath = "/usr/bin", SandboxPath = "/usr/bin", ReadOnly = true },
                    new DirectoryBinding { HostPath = "/usr/lib", SandboxPath = "/usr/lib", ReadOnly = true },
                    new DirectoryBinding { HostPath = "/lib", SandboxPath = "/lib", ReadOnly = true },
                    new DirectoryBinding { HostPath = "/lib64", SandboxPath = "/lib64", ReadOnly = true },
                    new DirectoryBinding { HostPath = "/etc", SandboxPath = "/etc", ReadOnly = true }
                }
            });

            stopwatch.Stop();

            // Read output
            if (File.Exists(outputFilePath))
            {
                result.Output = await File.ReadAllTextAsync(outputFilePath);
            }

            // Map Isolate result to ExecutionResult
            result.ExecutionTimeMs = (long)(isolateResult.CpuTimeSeconds * 1000);
            result.MemoryUsedMb = isolateResult.MemoryUsedKb / 1024;
            result.ErrorOutput = isolateResult.ErrorOutput;
            result.ExitCode = isolateResult.ExitCode;

            if (isolateResult.TimeLimitExceeded)
            {
                result.TimeLimitExceeded = true;
                result.ErrorMessage = $"Time limit exceeded: {isolateResult.CpuTimeSeconds:F3}s";
                _logger.LogWarning("Time limit exceeded for box {BoxId}", boxId);
            }
            else if (isolateResult.MemoryLimitExceeded)
            {
                result.MemoryLimitExceeded = true;
                result.ErrorMessage = $"Memory limit exceeded: {isolateResult.MemoryUsedKb / 1024}MB";
                if (isolateResult.CgroupOomKilled)
                {
                    result.ErrorMessage += " (OOM killed by cgroup)";
                }
                _logger.LogWarning("Memory limit exceeded for box {BoxId}", boxId);
            }
            else if (isolateResult.RuntimeError)
            {
                result.RuntimeError = true;
                result.ErrorMessage = $"Runtime error: {isolateResult.Message}";
                if (isolateResult.ExitSignal.HasValue)
                {
                    result.ErrorMessage += $" (signal {isolateResult.ExitSignal})";
                }
                _logger.LogWarning("Runtime error for box {BoxId}: {Message}", boxId, isolateResult.Message);
            }
            else if (isolateResult.ExitCode == 0)
            {
                result.Success = true;
                _logger.LogInformation(
                    "Execution successful: time={Time}ms, memory={Memory}MB, box={BoxId}",
                    result.ExecutionTimeMs, result.MemoryUsedMb, boxId);
            }
            else
            {
                result.RuntimeError = true;
                result.ErrorMessage = $"Non-zero exit code: {isolateResult.ExitCode}";
                _logger.LogWarning("Non-zero exit code {ExitCode} for box {BoxId}",
                    isolateResult.ExitCode, boxId);
            }

            _logger.LogDebug(
                "Isolate stats: CPU={Cpu}s, Wall={Wall}s, Memory={Mem}KB",
                isolateResult.CpuTimeSeconds,
                isolateResult.WallTimeSeconds,
                isolateResult.MemoryUsedKb);
        }
        catch (Exception ex)
        {
            stopwatch.Stop();
            result.ExecutionTimeMs = stopwatch.ElapsedMilliseconds;
            result.RuntimeError = true;
            result.ErrorMessage = $"Execution error: {ex.Message}";
            _logger.LogError(ex, "Error during Isolate execution");
        }
        finally
        {
            // Cleanup box
            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 box {BoxId}", boxId);
                }

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

        return result;
    }
}
