using System.Globalization;
using System.IO;
using System.Linq;
using System.Xml.Linq;
using LiquidCode.Tester.Common.Models;

namespace LiquidCode.Tester.Worker.Services;

/// <summary>
/// Parser for Polygon problem.xml format
/// </summary>
public class PolygonProblemXmlParser
{
    private readonly ILogger<PolygonProblemXmlParser> _logger;

    public PolygonProblemXmlParser(ILogger<PolygonProblemXmlParser> logger)
    {
        _logger = logger;
    }

    public PolygonProblemDescriptor? ParseProblemXml(string xmlPath)
    {
        try
        {
            var doc = XDocument.Load(xmlPath);
            var problem = doc.Element("problem");

            if (problem == null)
            {
                _logger.LogWarning("Invalid problem.xml: root 'problem' element not found");
                return null;
            }

            var judging = problem.Element("judging");
            if (judging == null)
            {
                _logger.LogWarning("No 'judging' section found in problem.xml");
                return null;
            }

            var testset = judging.Element("testset");
            if (testset == null)
            {
                _logger.LogWarning("No 'testset' section found in problem.xml");
                return null;
            }

            var descriptor = new PolygonProblemDescriptor
            {
                ShortName = problem.Attribute("short-name")?.Value ?? "unknown",
                Revision = int.TryParse(problem.Attribute("revision")?.Value, out var rev) ? rev : 0
            };

            // Parse time limit (in milliseconds)
            var timeLimitText = testset.Element("time-limit")?.Value;
            if (int.TryParse(timeLimitText, out var timeLimit))
            {
                descriptor.TimeLimitMs = timeLimit;
            }

            // Parse memory limit (in bytes)
            var memoryLimitText = testset.Element("memory-limit")?.Value;
            if (long.TryParse(memoryLimitText, out var memoryLimit))
            {
                descriptor.MemoryLimitMb = (int)(memoryLimit / (1024 * 1024)); // Convert bytes to MB
            }

            // Parse test count
            var testCountText = testset.Element("test-count")?.Value;
            if (int.TryParse(testCountText, out var testCount))
            {
                descriptor.TestCount = testCount;
            }

            // Parse path patterns
            descriptor.InputPathPattern = testset.Element("input-path-pattern")?.Value ?? "tests/%02d";
            descriptor.AnswerPathPattern = testset.Element("answer-path-pattern")?.Value ?? "tests/%02d.a";

            // Parse detailed test definitions (if present)
            var testsElement = testset.Element("tests");
            if (testsElement != null)
            {
                var index = 1;
                foreach (var testElement in testsElement.Elements("test"))
                {
                    var methodValue = testElement.Attribute("method")?.Value ?? "manual";
                    var method = methodValue.ToLowerInvariant() switch
                    {
                        "generated" => PolygonTestMethod.Generated,
                        "manual" => PolygonTestMethod.Manual,
                        "script" => PolygonTestMethod.Script,
                        _ => PolygonTestMethod.Unknown
                    };

                    double? points = null;
                    var pointsAttr = testElement.Attribute("points")?.Value;
                    if (!string.IsNullOrWhiteSpace(pointsAttr) &&
                        double.TryParse(pointsAttr, NumberStyles.Float, CultureInfo.InvariantCulture, out var parsedPoints))
                    {
                        points = parsedPoints;
                    }

                    var definition = new PolygonTestDefinition
                    {
                        Index = index,
                        Method = method,
                        Command = testElement.Attribute("cmd")?.Value,
                        Group = testElement.Attribute("group")?.Value,
                        IsSample = bool.TryParse(testElement.Attribute("sample")?.Value, out var sample) && sample,
                        Points = points,
                        Comment = testElement.Attribute("comment")?.Value
                    };

                    descriptor.Tests.Add(definition);
                    index++;
                }

                if (descriptor.TestCount == 0)
                {
                    descriptor.TestCount = descriptor.Tests.Count;
                }
            }

            // Parse auxiliary executables defined in <files><executables>
            var filesSection = problem.Element("files");
            if (filesSection != null)
            {
                var executablesSection = filesSection.Element("executables");
                if (executablesSection != null)
                {
                    foreach (var executableElement in executablesSection.Elements("executable"))
                    {
                        var sourceElement = executableElement.Element("source");
                        if (sourceElement == null)
                        {
                            continue;
                        }

                        var sourcePath = sourceElement.Attribute("path")?.Value;
                        if (string.IsNullOrWhiteSpace(sourcePath))
                        {
                            continue;
                        }

                        var binaryPath = executableElement.Element("binary")?.Attribute("path")?.Value;
                        var name = !string.IsNullOrWhiteSpace(binaryPath)
                            ? Path.GetFileNameWithoutExtension(binaryPath)
                            : Path.GetFileNameWithoutExtension(sourcePath);

                        descriptor.Executables.Add(new PolygonExecutableDescriptor
                        {
                            Name = name,
                            SourcePath = sourcePath,
                            BinaryPath = binaryPath,
                            Type = sourceElement.Attribute("type")?.Value
                        });
                    }
                }
            }

            // Parse assets: solutions, checker, validators
            var assets = problem.Element("assets");
            if (assets != null)
            {
                var solutions = assets.Element("solutions");
                if (solutions != null)
                {
                    // Try to find main solution first
                    var mainSolution = solutions.Elements("solution")
                        .FirstOrDefault(s => s.Attribute("tag")?.Value == "main");

                    // If no main solution, try to find any accepted solution
                    if (mainSolution == null)
                    {
                        mainSolution = solutions.Elements("solution")
                            .FirstOrDefault(s => s.Attribute("tag")?.Value == "accepted");
                    }

                    if (mainSolution != null)
                    {
                        var source = mainSolution.Element("source");
                        if (source != null)
                        {
                            descriptor.MainSolutionPath = source.Attribute("path")?.Value;
                            descriptor.MainSolutionType = source.Attribute("type")?.Value;

                            _logger.LogInformation("Found main solution: {Path} (type: {Type})",
                                descriptor.MainSolutionPath, descriptor.MainSolutionType);
                        }
                    }
                    else
                    {
                        _logger.LogWarning("No main or accepted solution found in problem.xml");
                    }
                }

                var checkerElement = assets.Element("checker");
                if (checkerElement != null)
                {
                    var checkerSource = checkerElement.Element("source");
                    if (checkerSource != null)
                    {
                        descriptor.Checker = new PolygonCheckerDescriptor
                        {
                            SourcePath = checkerSource.Attribute("path")?.Value ?? string.Empty,
                            Type = checkerSource.Attribute("type")?.Value,
                            BinaryPath = checkerElement.Element("binary")?.Attribute("path")?.Value,
                            CopyPath = checkerElement.Element("copy")?.Attribute("path")?.Value
                        };
                    }
                }

                var interactorElement = assets.Element("interactor");
                if (interactorElement != null)
                {
                    var interactorSource = interactorElement.Element("source");
                    if (interactorSource != null)
                    {
                        descriptor.Interactor = new PolygonInteractorDescriptor
                        {
                            SourcePath = interactorSource.Attribute("path")?.Value ?? string.Empty,
                            Type = interactorSource.Attribute("type")?.Value,
                            BinaryPath = interactorElement.Element("binary")?.Attribute("path")?.Value
                        };
                    }
                }

                var validatorsSection = assets.Element("validators");
                if (validatorsSection != null)
                {
                    foreach (var validatorElement in validatorsSection.Elements("validator"))
                    {
                        var validatorSource = validatorElement.Element("source");
                        if (validatorSource == null)
                        {
                            continue;
                        }

                        var validator = new PolygonValidatorDescriptor
                        {
                            SourcePath = validatorSource.Attribute("path")?.Value ?? string.Empty,
                            Type = validatorSource.Attribute("type")?.Value,
                            BinaryPath = validatorElement.Element("binary")?.Attribute("path")?.Value
                        };

                        descriptor.Validators.Add(validator);
                    }
                }
            }

            _logger.LogInformation(
                "Parsed problem.xml: {ShortName} (rev {Revision}), {TestCount} tests, TL={TimeLimit}ms, ML={MemoryLimit}MB",
                descriptor.ShortName, descriptor.Revision, descriptor.TestCount, descriptor.TimeLimitMs, descriptor.MemoryLimitMb);

            return descriptor;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to parse problem.xml at {Path}", xmlPath);
            return null;
        }
    }
}

/// <summary>
/// Descriptor parsed from problem.xml
/// </summary>
public class PolygonProblemDescriptor
{
    public string ShortName { get; set; } = string.Empty;
    public int Revision { get; set; }
    public int TimeLimitMs { get; set; } = 2000;
    public int MemoryLimitMb { get; set; } = 256;
    public int TestCount { get; set; }
    public string InputPathPattern { get; set; } = "tests/%02d";
    public string AnswerPathPattern { get; set; } = "tests/%02d.a";
    public string? MainSolutionPath { get; set; }
    public string? MainSolutionType { get; set; }
    public List<PolygonTestDefinition> Tests { get; } = new();
    public List<PolygonExecutableDescriptor> Executables { get; } = new();
    public PolygonCheckerDescriptor? Checker { get; set; }
    public PolygonInteractorDescriptor? Interactor { get; set; }
    public List<PolygonValidatorDescriptor> Validators { get; } = new();
}

/// <summary>
/// Represents a single test entry defined in problem.xml
/// </summary>
public class PolygonTestDefinition
{
    public int Index { get; set; }
    public PolygonTestMethod Method { get; set; } = PolygonTestMethod.Manual;
    public string? Command { get; set; }
    public string? Group { get; set; }
    public bool IsSample { get; set; }
    public double? Points { get; set; }
    public string? Comment { get; set; }
}

public enum PolygonTestMethod
{
    Manual,
    Generated,
    Script,
    Unknown
}

/// <summary>
/// Represents additional executables (generators, validators, printers) declared in problem.xml
/// </summary>
public class PolygonExecutableDescriptor
{
    public string Name { get; set; } = string.Empty;
    public string SourcePath { get; set; } = string.Empty;
    public string? BinaryPath { get; set; }
    public string? Type { get; set; }
}

/// <summary>
/// Represents checker information declared in problem.xml
/// </summary>
public class PolygonCheckerDescriptor
{
    public string SourcePath { get; set; } = string.Empty;
    public string? BinaryPath { get; set; }
    public string? CopyPath { get; set; }
    public string? Type { get; set; }
}

/// <summary>
/// Represents interactor information declared in problem.xml
/// </summary>
public class PolygonInteractorDescriptor
{
    public string SourcePath { get; set; } = string.Empty;
    public string? BinaryPath { get; set; }
    public string? Type { get; set; }
}

/// <summary>
/// Represents validator information declared in problem.xml
/// </summary>
public class PolygonValidatorDescriptor
{
    public string SourcePath { get; set; } = string.Empty;
    public string? BinaryPath { get; set; }
    public string? Type { get; set; }
}
