using System.Collections.Generic;
using LiquidCode.Tester.Worker.Services.Isolate;
using LiquidCode.Tester.Worker.Models;

namespace LiquidCode.Tester.Worker.Services;

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

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

    public KotlinCompilationServiceIsolate(
        ILogger<KotlinCompilationServiceIsolate> 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.kt");
        var jarFilePath = Path.Combine(workingDirectory, "solution.jar");

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

        try
        {
            // Normalize line endings
            var normalizedSourceCode = sourceCode
                .Replace("\\r\\n", "\n")  // Handle escaped \r\n (literal text "\\r\\n")
                .Replace("\\n", "\n")      // Handle escaped \n (literal text "\\n")
                .Replace("\\r", "\n")      // Handle escaped \r (literal text "\\r")
                .Replace("\r\n", "\n")     // Handle actual Windows line endings
                .Replace("\r", "\n");      // Handle actual Mac line endings

            _logger.LogDebug("Source code length: {Length}, normalized length: {NormalizedLength}, line count: {LineCount}",
                sourceCode.Length, normalizedSourceCode.Length, normalizedSourceCode.Split('\n').Length);

            _logger.LogDebug("First 200 chars of source: {Preview}",
                normalizedSourceCode.Length > 200 ? normalizedSourceCode.Substring(0, 200) : normalizedSourceCode);

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

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

        try
        {
            boxId = await _boxPool.AcquireBoxAsync();
            _logger.LogDebug("Acquired isolate box {BoxId} for Kotlin 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 jarFileName = "solution.jar";
            var boxJarPath = Path.Combine(boxDir, jarFileName);

            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($"/box/{sourceFileName}");
            arguments.Add("-include-runtime");
            arguments.Add("-d");
            arguments.Add($"/box/{jarFileName}");

            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/local/bin/{compiler}",
                Arguments = arguments.ToArray(),
                TimeLimitSeconds = CompilationTimeLimitSeconds,
                WallTimeLimitSeconds = CompilationTimeLimitSeconds * 2,
                MemoryLimitKb = CompilationMemoryLimitMb * 1024,
                StackLimitKb = 128 * 1024, // 128MB stack per process
                ProcessLimit = 64, // Kotlin uses JVM, needs many threads/processes
                EnableNetwork = false,
                StdoutFile = stdoutFilePath,
                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 = "/lib64", SandboxPath = "/lib64", ReadOnly = true },
                    new DirectoryBinding { HostPath = "/usr/bin", SandboxPath = "/usr/bin", ReadOnly = true },
                    new DirectoryBinding { HostPath = "/bin", SandboxPath = "/bin", ReadOnly = true },
                    new DirectoryBinding { HostPath = "/etc", SandboxPath = "/etc", ReadOnly = true },
                    new DirectoryBinding { HostPath = "/opt/kotlinc", SandboxPath = "/opt/kotlinc", ReadOnly = true }
                },
                EnvironmentVariables = new Dictionary<string, string>
                {
                    ["PATH"] = "/usr/local/bin:/usr/bin:/bin"
                }
            });

            var compilerOutput = string.Empty;

            // Read stdout (kotlinc may output errors there)
            if (File.Exists(stdoutFilePath))
            {
                var stdoutContent = await File.ReadAllTextAsync(stdoutFilePath);
                if (!string.IsNullOrWhiteSpace(stdoutContent))
                {
                    compilerOutput = stdoutContent;
                }
            }

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

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

            if (isolateResult.TimeLimitExceeded)
            {
                _logger.LogWarning("Kotlin 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("Kotlin 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(boxJarPath))
            {
                Directory.CreateDirectory(Path.GetDirectoryName(jarFilePath)!);
                File.Copy(boxJarPath, jarFilePath, overwrite: true);

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

            _logger.LogWarning("Kotlin 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 Kotlin 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["Kotlin:Compiler"] ?? "kotlinc";
            var compilerFlags = _configuration["Kotlin:CompilerFlags"] ?? "";
            return (compiler, compilerFlags);
        }

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

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

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