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

/// <summary>
namespace LiquidCode.Tester.Worker.Services;

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

    // Compilation limits (more generous than execution limits)
    private const int CompilationTimeLimitSeconds = 30;
    private const int CompilationMemoryLimitMb = 512;

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

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

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

    /// <summary>
    /// Compile a C++ source file to an executable using Isolate sandbox
    /// </summary>
    public async Task<CompilationResult> CompileFileAsync(
        string sourceFilePath,
        string outputExecutablePath,
        string? version = null,
        IEnumerable<string>? includeDirectories = null,
        IEnumerable<string>? additionalFlags = null)
    {
        int boxId = -1;

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

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

            // Copy source file to box
            var boxDir = $"/var/local/lib/isolate/{boxId}/box";
            var sourceFileName = Path.GetFileName(sourceFilePath);
            var boxSourcePath = Path.Combine(boxDir, sourceFileName);
            var outputFileName = Path.GetFileName(outputExecutablePath);
            var boxOutputPath = Path.Combine(boxDir, outputFileName);

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

            // Copy common headers from the source directory (e.g., testlib.h)
            var sourceDirectory = Path.GetDirectoryName(sourceFilePath);
            if (!string.IsNullOrEmpty(sourceDirectory) && Directory.Exists(sourceDirectory))
            {
                foreach (var header in Directory.EnumerateFiles(sourceDirectory))
                {
                    if (string.Equals(header, sourceFilePath, StringComparison.OrdinalIgnoreCase))
                    {
                        continue;
                    }

                    var extension = Path.GetExtension(header);
                    if (extension is ".h" or ".hpp" or ".hh" or ".hxx" or ".h++" or ".inl" or ".tcc" )
                    {
                        var destination = Path.Combine(boxDir, Path.GetFileName(header));
                        File.Copy(header, destination, overwrite: true);
                    }
                }
            }

            // Resolve compiler and flags
            var (compiler, compilerFlags) = ResolveVersion(version);
            _logger.LogDebug("Using compiler: {Compiler} with flags: {Flags}", compiler, string.Join(' ', compilerFlags));

            // Build compiler arguments
            var arguments = new List<string>(compilerFlags);
            var includeCounter = 0;

            // Add include directories
            if (includeDirectories != null)
            {
                foreach (var includeDir in includeDirectories.Where(d => !string.IsNullOrWhiteSpace(d)))
                {
                    var resolvedIncludeDir = includeDir;

                    if (!Path.IsPathRooted(resolvedIncludeDir))
                    {
                        var baseDir = sourceDirectory ?? Directory.GetCurrentDirectory();
                        resolvedIncludeDir = Path.GetFullPath(Path.Combine(baseDir, includeDir));
                    }

                    if (Directory.Exists(resolvedIncludeDir))
                    {
                        includeCounter++;
                        var targetIncludeDir = Path.Combine(boxDir, $"include_{includeCounter}");
                        CopyDirectory(resolvedIncludeDir, targetIncludeDir);
                        arguments.Add($"-I/box/include_{includeCounter}");
                    }
                    else
                    {
                        arguments.Add($"-I{includeDir}");
                    }
                }
            }

            // Add additional flags
            if (additionalFlags != null)
            {
                foreach (var flag in additionalFlags.Where(f => !string.IsNullOrWhiteSpace(f)))
                {
                    arguments.Add(flag);
                }
            }

            arguments.Add($"/box/{sourceFileName}");
            arguments.Add("-o");
            arguments.Add($"/box/{outputFileName}");

            // Prepare stderr output file for compiler messages
            var stderrFilePath = Path.Combine(boxDir, "compile_stderr.txt");

            // Run compiler in Isolate
            // Bind the system toolchain directories read-only so the linker and headers remain reachable
            var directoryBindings = new List<DirectoryBinding>
            {
                new DirectoryBinding { HostPath = "/usr/include", SandboxPath = "/usr/include", ReadOnly = true },
                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 }
            };

            if (Directory.Exists("/bin"))
            {
                directoryBindings.Add(new DirectoryBinding { HostPath = "/bin", SandboxPath = "/bin", ReadOnly = true });
            }

            if (Directory.Exists("/usr/local/bin"))
            {
                directoryBindings.Add(new DirectoryBinding { HostPath = "/usr/local/bin", SandboxPath = "/usr/local/bin", ReadOnly = true });
            }

            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, // g++ spawns multiple processes
                EnableNetwork = false,
                StderrFile = stderrFilePath,
                WorkingDirectory = "/box",
                DirectoryBindings = directoryBindings,
                EnvironmentVariables = new Dictionary<string, string>
                {
                    ["PATH"] = GetSandboxPath()
                }
            });

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

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

            // Copy compiled executable back if successful
            if (isolateResult.ExitCode == 0 && File.Exists(boxOutputPath))
            {
                Directory.CreateDirectory(Path.GetDirectoryName(outputExecutablePath)!);
                File.Copy(boxOutputPath, outputExecutablePath, overwrite: true);

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

            // Compilation failed
            _logger.LogWarning("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 compilation");
            return new CompilationResult
            {
                Success = false,
                ErrorMessage = $"Compilation error: {ex.Message}"
            };
        }
        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 compilation box {BoxId}", boxId);
                }

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

    private static void CopyDirectory(string sourceDirectory, string destinationDirectory)
    {
        var sourceRoot = Path.GetFullPath(sourceDirectory);
        var destinationRoot = Path.GetFullPath(destinationDirectory);

        Directory.CreateDirectory(destinationRoot);

        foreach (var directory in Directory.EnumerateDirectories(sourceRoot, "*", SearchOption.AllDirectories))
        {
            var relativePath = Path.GetRelativePath(sourceRoot, directory);
            var targetDir = Path.Combine(destinationRoot, relativePath);
            Directory.CreateDirectory(targetDir);
        }

        foreach (var file in Directory.EnumerateFiles(sourceRoot, "*", SearchOption.AllDirectories))
        {
            var relativePath = Path.GetRelativePath(sourceRoot, file);
            var targetFile = Path.Combine(destinationRoot, relativePath);
            Directory.CreateDirectory(Path.GetDirectoryName(targetFile)!);
            File.Copy(file, targetFile, overwrite: true);
        }
    }

    private static string GetSandboxPath()
    {
        var defaultPaths = new[] { "/usr/local/bin", "/usr/bin", "/bin" };
        var hostPath = Environment.GetEnvironmentVariable("PATH");

        if (string.IsNullOrWhiteSpace(hostPath))
        {
            return string.Join(':', defaultPaths);
        }

        var segments = hostPath
            .Split(':', StringSplitOptions.RemoveEmptyEntries)
            .Where(path => path.StartsWith("/usr", StringComparison.Ordinal) || path.StartsWith("/bin", StringComparison.Ordinal))
            .ToList();

        foreach (var defaultPath in defaultPaths)
        {
            if (!segments.Contains(defaultPath))
            {
                segments.Add(defaultPath);
            }
        }

        return segments.Count == 0 ? string.Join(':', defaultPaths) : string.Join(':', segments);
    }

    private (string compiler, List<string> compilerFlags) ResolveVersion(string? version)
    {
        var defaultCompiler = _configuration["Cpp:Compiler"] ?? "g++";
        var defaultFlags = SplitFlags(_configuration["Cpp:CompilerFlags"] ?? "-O2 -std=c++17 -Wall");

        if (string.IsNullOrEmpty(version) || version.Equals("latest", StringComparison.OrdinalIgnoreCase))
        {
            return (defaultCompiler, defaultFlags);
        }

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

        if (!string.IsNullOrEmpty(versionCompiler) || !string.IsNullOrEmpty(versionFlagsValue))
        {
            var resolvedFlags = !string.IsNullOrEmpty(versionFlagsValue)
                ? SplitFlags(versionFlagsValue)
                : defaultFlags;

            _logger.LogInformation("Using C++ version {Version} configuration", version);
            return (versionCompiler ?? defaultCompiler, resolvedFlags);
        }

        var normalized = NormalizeCppVersion(version);
        if (normalized != null)
        {
            var flagsWithoutStd = defaultFlags
                .Where(flag => !flag.StartsWith("-std=", StringComparison.OrdinalIgnoreCase))
                .ToList();
            flagsWithoutStd.Add($"-std=c++{normalized}");
            _logger.LogInformation("Using inferred C++ standard c++{Standard}", normalized);
            return (defaultCompiler, flagsWithoutStd);
        }

        _logger.LogWarning("C++ version {Version} not found in configuration, using default", version);
        return (defaultCompiler, defaultFlags);
    }

    private static List<string> SplitFlags(string flags) =>
        flags.Split(' ', StringSplitOptions.RemoveEmptyEntries)
            .ToList();

    private static string? NormalizeCppVersion(string version)
    {
        var cleaned = version.Trim().ToLowerInvariant();
        cleaned = cleaned.Replace("c++", string.Empty, StringComparison.OrdinalIgnoreCase)
                         .Replace("gnu++", string.Empty, StringComparison.OrdinalIgnoreCase)
                         .Trim('+', ' ');

        cleaned = cleaned switch
        {
            "2b" => "23",
            "2a" => "20",
            "1z" => "17",
            "0x" => "11",
            _ => cleaned
        };

        return cleaned switch
        {
            "26" => "26",
            "23" => "23",
            "20" => "20",
            "17" => "17",
            "14" => "14",
            "11" => "11",
            _ => null
        };
    }
}
