using LiquidCode.Tester.Worker.Services.Isolate;
using LiquidCode.Tester.Worker.Models;

namespace LiquidCode.Tester.Worker.Services;

/// <summary>
/// Java compilation service using Isolate sandbox for security
/// </summary>
public class JavaCompilationServiceIsolate : ICompilationService
{
    private readonly ILogger<JavaCompilationServiceIsolate> _logger;
    private readonly IConfiguration _configuration;
    private readonly IsolateService _isolateService;
    private readonly IsolateBoxPool _boxPool;

    private const int CompilationTimeLimitSeconds = 30;
    private const int CompilationMemoryLimitMb = 1024; // JVM needs more memory

    public JavaCompilationServiceIsolate(
        ILogger<JavaCompilationServiceIsolate> logger,
        IConfiguration configuration,
        IsolateService isolateService,
        IsolateBoxPool boxPool)
    {
        _logger = logger;
        _configuration = configuration;
        _isolateService = isolateService;
        _boxPool = boxPool;
    }

    public async Task<CompilationResult> CompileAsync(string sourceCode, string workingDirectory, string? version = null)
    {
        var sourceFilePath = Path.Combine(workingDirectory, "Solution.java");
        var classFilePath = Path.Combine(workingDirectory, "Solution.class");

        _logger.LogInformation("Compiling Java code with Isolate in {WorkingDirectory} with version {Version}",
            workingDirectory, version ?? "latest");

        try
        {
            await File.WriteAllTextAsync(sourceFilePath, sourceCode);
            return await CompileFileInIsolateAsync(sourceFilePath, classFilePath, workingDirectory, version);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error during Isolate Java compilation");
            return new CompilationResult
            {
                Success = false,
                ErrorMessage = $"Compilation error: {ex.Message}"
            };
        }
    }

    private async Task<CompilationResult> CompileFileInIsolateAsync(
        string sourceFilePath,
        string classFilePath,
        string workingDirectory,
        string? version = null)
    {
        int boxId = -1;

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

            await _isolateService.InitBoxAsync(boxId);

            var boxDir = $"/var/local/lib/isolate/{boxId}/box";
            var sourceFileName = Path.GetFileName(sourceFilePath);
            var boxSourcePath = Path.Combine(boxDir, sourceFileName);

            File.Copy(sourceFilePath, boxSourcePath, overwrite: true);

            var (compiler, compilerFlags) = ResolveVersion(version);
            _logger.LogDebug("Using compiler: {Compiler} with flags: {Flags}", compiler, compilerFlags);

            // Build arguments
            var arguments = new List<string>();
            if (!string.IsNullOrWhiteSpace(compilerFlags))
            {
                arguments.AddRange(compilerFlags.Split(' ', StringSplitOptions.RemoveEmptyEntries));
            }
            arguments.Add($"/box/{sourceFileName}");

            var stderrFilePath = Path.Combine(boxDir, "compile_stderr.txt");
            var stdoutFilePath = Path.Combine(boxDir, "compile_stdout.txt");

            var isolateResult = await _isolateService.RunAsync(new IsolateRunOptions
            {
                BoxId = boxId,
                Executable = $"/usr/bin/{compiler}",
                Arguments = arguments.ToArray(),
                TimeLimitSeconds = CompilationTimeLimitSeconds,
                WallTimeLimitSeconds = CompilationTimeLimitSeconds * 2,
                MemoryLimitKb = CompilationMemoryLimitMb * 1024,
                StackLimitKb = 128 * 1024, // 128MB stack per process
                ProcessLimit = 64, // JVM needs many threads/processes
                EnableNetwork = false,
                StdoutFile = stdoutFilePath,
                StderrFile = stderrFilePath,
                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 }
                }
            });

            var compilerOutput = string.Empty;

            // Read stdout (javac may output errors there)
            if (File.Exists(stdoutFilePath))
            {
                var stdoutContent = await File.ReadAllTextAsync(stdoutFilePath);
                if (!string.IsNullOrWhiteSpace(stdoutContent))
                {
                    compilerOutput = stdoutContent;
                    _logger.LogDebug("Read stdout file: {Length} bytes", stdoutContent.Length);
                }
            }

            // Read stderr
            if (File.Exists(stderrFilePath))
            {
                var stderrContent = await File.ReadAllTextAsync(stderrFilePath);
                if (!string.IsNullOrWhiteSpace(stderrContent))
                {
                    compilerOutput = string.IsNullOrEmpty(compilerOutput)
                        ? stderrContent
                        : $"{compilerOutput}\n{stderrContent}";
                    _logger.LogDebug("Read stderr file: {Length} bytes", stderrContent.Length);
                }
            }

            if (!string.IsNullOrEmpty(isolateResult.ErrorOutput))
            {
                compilerOutput = string.IsNullOrEmpty(compilerOutput)
                    ? isolateResult.ErrorOutput
                    : $"{compilerOutput}\n{isolateResult.ErrorOutput}";
            }

            _logger.LogDebug("Final compiler output: {Output}", compilerOutput);

            if (isolateResult.TimeLimitExceeded)
            {
                _logger.LogWarning("Java compilation time limit exceeded for box {BoxId}", boxId);
                return new CompilationResult
                {
                    Success = false,
                    ErrorMessage = $"Compilation timeout: exceeded {CompilationTimeLimitSeconds}s limit",
                    CompilerOutput = compilerOutput
                };
            }

            if (isolateResult.MemoryLimitExceeded)
            {
                _logger.LogWarning("Java compilation memory limit exceeded for box {BoxId}", boxId);
                return new CompilationResult
                {
                    Success = false,
                    ErrorMessage = $"Compilation memory limit exceeded: {CompilationMemoryLimitMb}MB",
                    CompilerOutput = compilerOutput
                };
            }

            // Copy .class file back
            var boxClassPath = Path.Combine(boxDir, "Solution.class");
            if (isolateResult.ExitCode == 0 && File.Exists(boxClassPath))
            {
                Directory.CreateDirectory(Path.GetDirectoryName(classFilePath)!);
                File.Copy(boxClassPath, classFilePath, overwrite: true);

                _logger.LogInformation("Java compilation successful in Isolate box {BoxId}", boxId);
                return new CompilationResult
                {
                    Success = true,
                    ExecutablePath = classFilePath,
                    CompilerOutput = compilerOutput
                };
            }

            _logger.LogWarning("Java compilation failed with exit code {ExitCode} in box {BoxId}",
                isolateResult.ExitCode, boxId);
            return new CompilationResult
            {
                Success = false,
                ErrorMessage = $"Compilation failed with exit code {isolateResult.ExitCode}",
                CompilerOutput = compilerOutput
            };
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error during Isolate Java compilation");
            return new CompilationResult
            {
                Success = false,
                ErrorMessage = $"Compilation error: {ex.Message}"
            };
        }
        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 compilation box {BoxId}", boxId);
                }

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

    private (string compiler, string compilerFlags) ResolveVersion(string? version)
    {
        if (string.IsNullOrEmpty(version) || version.Equals("latest", StringComparison.OrdinalIgnoreCase))
        {
            var compiler = _configuration["Java:Compiler"] ?? "javac";
            var compilerFlags = _configuration["Java:CompilerFlags"] ?? "";
            return (compiler, compilerFlags);
        }

        var versionKey = $"Java:Versions:{version}";
        var versionCompiler = _configuration[$"{versionKey}:Compiler"];
        var versionFlags = _configuration[$"{versionKey}:CompilerFlags"];

        if (!string.IsNullOrEmpty(versionCompiler))
        {
            _logger.LogInformation("Using Java version {Version} configuration", version);
            return (versionCompiler, versionFlags ?? "");
        }

        _logger.LogWarning("Java version {Version} not found in configuration, using default", version);
        var defaultCompiler = _configuration["Java:Compiler"] ?? "javac";
        var defaultFlags = _configuration["Java:CompilerFlags"] ?? "";
        return (defaultCompiler, defaultFlags);
    }
}
