using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using LiquidCode.Tester.Worker.Models;

namespace LiquidCode.Tester.Worker.Services;

public class CppCompilationService : ICompilationService
{
    private readonly ILogger<CppCompilationService> _logger;
    private readonly IConfiguration _configuration;

    public CppCompilationService(ILogger<CppCompilationService> logger, IConfiguration configuration)
    {
        _logger = logger;
        _configuration = configuration;
    }

    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 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 compilation");
            return new CompilationResult
            {
                Success = false,
                ErrorMessage = $"Compilation error: {ex.Message}"
            };
        }
    }

    public async Task<CompilationResult> CompileFileAsync(
        string sourceFilePath,
        string outputExecutablePath,
        string? version = null,
        IEnumerable<string>? includeDirectories = null,
        IEnumerable<string>? additionalFlags = null)
    {
        _logger.LogInformation("Compiling C++ source {Source} -> {Output} with version {Version}", sourceFilePath, outputExecutablePath, version ?? "latest");

        try
        {
            Directory.CreateDirectory(Path.GetDirectoryName(outputExecutablePath)!);

            var (compiler, compilerFlags) = ResolveVersion(version);

            _logger.LogDebug("Using compiler: {Compiler} with flags: {Flags}", compiler, string.Join(' ', compilerFlags));

            var process = new Process
            {
                StartInfo = new ProcessStartInfo
                {
                    FileName = compiler,
                    WorkingDirectory = Path.GetDirectoryName(sourceFilePath) ?? Directory.GetCurrentDirectory(),
                    RedirectStandardOutput = true,
                    RedirectStandardError = true,
                    UseShellExecute = false,
                    CreateNoWindow = true
                }
            };

            foreach (var flag in compilerFlags)
            {
                process.StartInfo.ArgumentList.Add(flag);
            }

            if (includeDirectories != null)
            {
                foreach (var includeDir in includeDirectories.Where(d => !string.IsNullOrWhiteSpace(d)))
                {
                    process.StartInfo.ArgumentList.Add($"-I{includeDir}");
                }
            }

            if (additionalFlags != null)
            {
                foreach (var flag in additionalFlags.Where(f => !string.IsNullOrWhiteSpace(f)))
                {
                    process.StartInfo.ArgumentList.Add(flag);
                }
            }

            process.StartInfo.ArgumentList.Add(sourceFilePath);
            process.StartInfo.ArgumentList.Add("-o");
            process.StartInfo.ArgumentList.Add(outputExecutablePath);

            process.Start();

            var stdOutTask = process.StandardOutput.ReadToEndAsync();
            var stdErrTask = process.StandardError.ReadToEndAsync();

            await process.WaitForExitAsync();

            var compilerOutput = $"{await stdOutTask}\n{await stdErrTask}".Trim();

            if (process.ExitCode == 0 && File.Exists(outputExecutablePath))
            {
                _logger.LogInformation("Compilation successful");
                return new CompilationResult
                {
                    Success = true,
                    ExecutablePath = outputExecutablePath,
                    CompilerOutput = compilerOutput
                };
            }

            _logger.LogWarning("Compilation failed with exit code {ExitCode}", process.ExitCode);
            return new CompilationResult
            {
                Success = false,
                ErrorMessage = "Compilation failed",
                CompilerOutput = compilerOutput
            };
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error during compilation");
            return new CompilationResult
            {
                Success = false,
                ErrorMessage = $"Compilation error: {ex.Message}"
            };
        }
    }

    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
        };
    }
}
