using System;
using System.Diagnostics;
using System.Text;

namespace LiquidCode.Tester.Worker.Services.Isolate;

/// <summary>
/// Service for running programs in Isolate sandbox
/// </summary>
public class IsolateService
{
    private readonly ILogger<IsolateService> _logger;
    private readonly string _isolatePath;

    public IsolateService(ILogger<IsolateService> logger)
    {
        _logger = logger;
        _isolatePath = FindIsolatePath();
    }

    /// <summary>
    /// Initialize a sandbox box
    /// </summary>
    public async Task InitBoxAsync(int boxId)
    {
        _logger.LogDebug("Initializing isolate box {BoxId}", boxId);

    var result = await RunIsolateCommandAsync($"--box-id={boxId} --cg --init");

        if (result.ExitCode != 0)
        {
            throw new InvalidOperationException(
                $"Failed to initialize isolate box {boxId}: {result.Error}");
        }

        _logger.LogDebug("Box {BoxId} initialized at {Path}", boxId, result.Output.Trim());
    }

    /// <summary>
    /// Execute program in sandbox
    /// </summary>
    public async Task<IsolateExecutionResult> RunAsync(IsolateRunOptions options)
    {
        _logger.LogInformation("Running in isolate box {BoxId}: {Executable}",
            options.BoxId, options.Executable);

        // Create metadata file path
        var metaFile = Path.Combine(Path.GetTempPath(), $"isolate_meta_{options.BoxId}_{Guid.NewGuid()}.txt");

        try
        {
            // Build isolate command
            var command = BuildRunCommand(options, metaFile);

            _logger.LogDebug("Isolate command: {Command}", command);

            // Execute
            var cmdResult = await RunIsolateCommandAsync(command);

            // Parse metadata
            var result = await ParseMetadataAsync(metaFile);
            result.Output = cmdResult.Output;
            result.ErrorOutput = cmdResult.Error;

            _logger.LogInformation("Execution completed: time={Time}s, memory={Memory}KB, exitcode={ExitCode}",
                result.CpuTimeSeconds, result.MemoryUsedKb, result.ExitCode);

            return result;
        }
        finally
        {
            // Cleanup metadata file
            try
            {
                if (File.Exists(metaFile))
                {
                    File.Delete(metaFile);
                }
            }
            catch (Exception ex)
            {
                _logger.LogWarning(ex, "Failed to delete metadata file {MetaFile}", metaFile);
            }
        }
    }

    /// <summary>
    /// Cleanup sandbox box
    /// </summary>
    public async Task CleanupBoxAsync(int boxId)
    {
        _logger.LogDebug("Cleaning up isolate box {BoxId}", boxId);

    var result = await RunIsolateCommandAsync($"--box-id={boxId} --cg --cleanup");

        if (result.ExitCode != 0)
        {
            _logger.LogWarning("Failed to cleanup isolate box {BoxId}: {Error}", boxId, result.Error);
        }
    }

    /// <summary>
    /// Build isolate run command from options
    /// </summary>
    private string BuildRunCommand(IsolateRunOptions options, string metaFile)
    {
        var args = new List<string>
        {
            $"--box-id={options.BoxId}",
            $"--meta={metaFile}",
            "--cg",  // Enable cgroups
            "--silent" // Suppress status messages
        };

        // Time limits
        if (options.TimeLimitSeconds > 0)
        {
            args.Add($"--time={options.TimeLimitSeconds:F3}");
        }

        if (options.WallTimeLimitSeconds > 0)
        {
            args.Add($"--wall-time={options.WallTimeLimitSeconds:F3}");
        }

        // Memory limit
        if (options.MemoryLimitKb > 0)
        {
            args.Add($"--cg-mem={options.MemoryLimitKb}");
        }

        // Process limit
        if (options.ProcessLimit > 0)
        {
            args.Add($"--processes={options.ProcessLimit}");
        }
        else
        {
            args.Add("--processes=1");  // Default: single process
        }

        // Stack size
        if (options.StackLimitKb > 0)
        {
            args.Add($"--stack={options.StackLimitKb}");
        }

        // Network isolation (default: disabled)
        if (!options.EnableNetwork)
        {
            // Network is isolated by default in isolate
        }
        else
        {
            args.Add("--share-net");
        }

        // I/O redirection
        if (!string.IsNullOrEmpty(options.StdinFile))
        {
            args.Add($"--stdin={MapSandboxPath(options.StdinFile, options.BoxId)}");
        }

        if (!string.IsNullOrEmpty(options.StdoutFile))
        {
            var mappedStdout = MapSandboxPath(options.StdoutFile, options.BoxId);
            _logger.LogDebug("Stdout mapping: {Original} -> {Mapped}", options.StdoutFile, mappedStdout);
            args.Add($"--stdout={mappedStdout}");
        }

        if (!string.IsNullOrEmpty(options.StderrFile))
        {
            var mappedStderr = MapSandboxPath(options.StderrFile, options.BoxId);
            _logger.LogDebug("Stderr mapping: {Original} -> {Mapped}", options.StderrFile, mappedStderr);
            args.Add($"--stderr={mappedStderr}");
        }

        // Working directory
        if (!string.IsNullOrEmpty(options.WorkingDirectory))
        {
            args.Add($"--chdir={options.WorkingDirectory}");
        }

        // Directory bindings
        if (options.DirectoryBindings != null)
        {
            foreach (var binding in options.DirectoryBindings)
            {
                var dirSpec = binding.ReadOnly
                    ? $"--dir={binding.HostPath}={binding.SandboxPath}"
                    : $"--dir={binding.HostPath}={binding.SandboxPath}:rw";
                args.Add(dirSpec);
            }
        }

        // Environment variables
        if (options.EnvironmentVariables != null)
        {
            foreach (var env in options.EnvironmentVariables)
            {
                args.Add($"--env={env.Key}={env.Value}");
            }
        }

        // Run command
        args.Add("--run");
        args.Add("--");
        args.Add(options.Executable);

        if (options.Arguments != null)
        {
            args.AddRange(options.Arguments);
        }

        return string.Join(" ", args);
    }

    private static string MapSandboxPath(string path, int boxId)
    {
        if (string.IsNullOrEmpty(path))
        {
            return path;
        }

    var normalizedPath = Path.GetFullPath(path);
    var boxRoot = Path.GetFullPath($"/var/local/lib/isolate/{boxId}/box");

        if (normalizedPath.StartsWith(boxRoot, StringComparison.Ordinal))
        {
            var relative = normalizedPath.Substring(boxRoot.Length).TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
            return relative.Length == 0 ? "/box" : $"/box/{relative.Replace(Path.DirectorySeparatorChar, '/')}";
        }

        return normalizedPath;
    }

    /// <summary>
    /// Parse isolate metadata file
    /// </summary>
    private async Task<IsolateExecutionResult> ParseMetadataAsync(string metaFile)
    {
        var result = new IsolateExecutionResult();

        if (!File.Exists(metaFile))
        {
            _logger.LogWarning("Metadata file not found: {MetaFile}", metaFile);
            return result;
        }

        var lines = await File.ReadAllLinesAsync(metaFile);

        foreach (var line in lines)
        {
            var parts = line.Split(':', 2);
            if (parts.Length != 2) continue;

            var key = parts[0].Trim();
            var value = parts[1].Trim();

            switch (key)
            {
                case "time":
                    if (double.TryParse(value, out var time))
                        result.CpuTimeSeconds = time;
                    break;

                case "time-wall":
                    if (double.TryParse(value, out var wallTime))
                        result.WallTimeSeconds = wallTime;
                    break;

                case "max-rss":
                    if (long.TryParse(value, out var rss))
                        result.MaxRssKb = rss;
                    break;

                case "cg-mem":
                    if (long.TryParse(value, out var cgMem))
                        result.MemoryUsedKb = cgMem;
                    break;

                case "exitcode":
                    if (int.TryParse(value, out var exitCode))
                        result.ExitCode = exitCode;
                    break;

                case "exitsig":
                    if (int.TryParse(value, out var exitSig))
                        result.ExitSignal = exitSig;
                    break;

                case "status":
                    result.Status = value;
                    break;

                case "message":
                    result.Message = value;
                    break;

                case "killed":
                    result.WasKilled = value == "1";
                    break;

                case "cg-oom-killed":
                    result.CgroupOomKilled = value == "1";
                    if (result.CgroupOomKilled)
                    {
                        result.MemoryLimitExceeded = true;
                    }
                    break;

                case "csw-voluntary":
                    if (int.TryParse(value, out var csvVoluntary))
                        result.VoluntaryContextSwitches = csvVoluntary;
                    break;

                case "csw-forced":
                    if (int.TryParse(value, out var csvForced))
                        result.ForcedContextSwitches = csvForced;
                    break;
            }
        }

        // Derive status-related flags after parsing all metadata
        switch (result.Status)
        {
            case "TO":
                result.TimeLimitExceeded = true;
                break;
            case "RE":
            case "SG":
                result.RuntimeError = true;
                break;
            case "XX":
                // Internal error reported by isolate
                result.RuntimeError = true;
                break;
        }

        if (!result.MemoryLimitExceeded &&
            !string.IsNullOrEmpty(result.Message) &&
            result.Message.Contains("memory limit", StringComparison.OrdinalIgnoreCase))
        {
            result.MemoryLimitExceeded = true;
        }

        return result;
    }

    /// <summary>
    /// Execute isolate command
    /// </summary>
    private async Task<(int ExitCode, string Output, string Error)> RunIsolateCommandAsync(string arguments)
    {
        var process = new Process
        {
            StartInfo = new ProcessStartInfo
            {
                FileName = _isolatePath,
                Arguments = arguments,
                RedirectStandardOutput = true,
                RedirectStandardError = true,
                UseShellExecute = false,
                CreateNoWindow = true
            }
        };

        process.Start();

        var outputTask = process.StandardOutput.ReadToEndAsync();
        var errorTask = process.StandardError.ReadToEndAsync();

        await process.WaitForExitAsync();

        return (process.ExitCode, await outputTask, await errorTask);
    }

    /// <summary>
    /// Find isolate binary path
    /// </summary>
    private string FindIsolatePath()
    {
        var paths = new[] { "/usr/local/bin/isolate", "/usr/bin/isolate" };

        foreach (var path in paths)
        {
            if (File.Exists(path))
            {
                _logger.LogInformation("Found isolate at {Path}", path);
                return path;
            }
        }

        throw new FileNotFoundException("Isolate binary not found. Make sure isolate is installed.");
    }
}
