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

namespace LiquidCode.Tester.Worker.Services;

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

    private const int CompilationTimeLimitSeconds = 30;
    private const int CompilationMemoryLimitMb = 512;

    public CSharpCompilationServiceIsolate(
        ILogger<CSharpCompilationServiceIsolate> 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.cs");
        var executablePath = Path.Combine(workingDirectory, "solution.exe");

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

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

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

        try
        {
            boxId = await _boxPool.AcquireBoxAsync();
            _logger.LogDebug("Acquired isolate box {BoxId} for C# 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);
            var exeFileName = "solution.exe";
            var boxExePath = Path.Combine(boxDir, exeFileName);

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

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

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

            var stderrFilePath = Path.Combine(boxDir, "compile_stderr.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 = 256 * 1024,
                ProcessLimit = 10,
                EnableNetwork = false,
                StderrFile = stderrFilePath,
                WorkingDirectory = "/box",
                DirectoryBindings = new List<DirectoryBinding>
                {
                    new DirectoryBinding { HostPath = "/usr/lib", SandboxPath = "/usr/lib", ReadOnly = true },
                    new DirectoryBinding { HostPath = "/lib", SandboxPath = "/lib", ReadOnly = true },
                    new DirectoryBinding { HostPath = "/usr/bin", SandboxPath = "/usr/bin", ReadOnly = true },
                    new DirectoryBinding { HostPath = "/usr/share", SandboxPath = "/usr/share", ReadOnly = true },
                    new DirectoryBinding { HostPath = "/etc/mono", SandboxPath = "/etc/mono", ReadOnly = true }
                }
            });

            var compilerOutput = string.Empty;
            if (File.Exists(stderrFilePath))
            {
                compilerOutput = await File.ReadAllTextAsync(stderrFilePath);
            }
            if (!string.IsNullOrEmpty(isolateResult.ErrorOutput))
            {
                compilerOutput = $"{compilerOutput}\n{isolateResult.ErrorOutput}".Trim();
            }

            if (isolateResult.TimeLimitExceeded)
            {
                _logger.LogWarning("C# 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("C# compilation memory limit exceeded for box {BoxId}", boxId);
                return new CompilationResult
                {
                    Success = false,
                    ErrorMessage = $"Compilation memory limit exceeded: {CompilationMemoryLimitMb}MB",
                    CompilerOutput = compilerOutput
                };
            }

            if (isolateResult.ExitCode == 0 && File.Exists(boxExePath))
            {
                Directory.CreateDirectory(Path.GetDirectoryName(executablePath)!);
                File.Copy(boxExePath, executablePath, overwrite: true);

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

            _logger.LogWarning("C# 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 C# 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["CSharp:Compiler"] ?? "csc";
            var compilerFlags = _configuration["CSharp:CompilerFlags"] ?? "/optimize+";
            return (compiler, compilerFlags);
        }

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

        if (!string.IsNullOrEmpty(versionCompiler))
        {
            _logger.LogInformation("Using C# version {Version} configuration", version);
            return (versionCompiler, versionFlags ?? "/optimize+");
        }

        _logger.LogWarning("C# version {Version} not found in configuration, using default", version);
        var defaultCompiler = _configuration["CSharp:Compiler"] ?? "csc";
        var defaultFlags = _configuration["CSharp:CompilerFlags"] ?? "/optimize+";
        return (defaultCompiler, defaultFlags);
    }
}
