using System.IO.Compression;
using LiquidCode.Tester.Worker.Services;
using Microsoft.Extensions.Logging;
using Moq;

namespace LiquidCode.Tester.Worker.Tests;

public class PolygonPackageParserTests : IDisposable
{
    private readonly PackageParserService _service;
    private readonly string _testDirectory;

    public PolygonPackageParserTests()
    {
        var logger = new Mock<ILogger<PackageParserService>>();
        var xmlLogger = new Mock<ILogger<PolygonProblemXmlParser>>();
        var answerGenLogger = new Mock<ILogger<AnswerGenerationService>>();
        var cppLogger = new Mock<ILogger<CppCompilationService>>();
        var cppConfigMock = new Mock<Microsoft.Extensions.Configuration.IConfiguration>();

        var polygonParser = new PolygonProblemXmlParser(xmlLogger.Object);

        var compilationFactory = new Mock<ICompilationServiceFactory>();
        var executionFactory = new Mock<IExecutionServiceFactory>();
        var answerGenerator = new AnswerGenerationService(
            compilationFactory.Object,
            executionFactory.Object,
            answerGenLogger.Object);

        var cppCompilation = new CppCompilationService(cppLogger.Object, cppConfigMock.Object);

        _service = new PackageParserService(logger.Object, polygonParser, answerGenerator, cppCompilation);
        _testDirectory = Path.Combine(Path.GetTempPath(), "PolygonPackageTests", Guid.NewGuid().ToString());
        Directory.CreateDirectory(_testDirectory);
    }

    [Fact]
    public async Task ParsePackageAsync_PolygonPackageWithProblemXml_ParsesSuccessfully()
    {
        // Arrange
        var problemXml = @"<?xml version=""1.0"" encoding=""utf-8"" standalone=""no""?>
<problem revision=""7"" short-name=""test-problem"">
    <judging>
        <testset name=""tests"">
            <time-limit>1000</time-limit>
            <memory-limit>268435456</memory-limit>
            <test-count>2</test-count>
            <input-path-pattern>tests/%02d</input-path-pattern>
            <answer-path-pattern>tests/%02d.a</answer-path-pattern>
        </testset>
    </judging>
</problem>";

        var zipStream = CreatePolygonPackage(problemXml, new[]
        {
            ("tests/01", "input1"),
            ("tests/01.a", "output1"),
            ("tests/02", "input2"),
            ("tests/02.a", "output2")
        });

        // Act
        var result = await _service.ParsePackageAsync(zipStream);

        // Assert
        Assert.NotNull(result);
        Assert.Equal(2, result.TestCases.Count);
        Assert.Equal(1000, result.DefaultTimeLimit);
        Assert.Equal(256, result.DefaultMemoryLimit);

        // Verify first test
        Assert.Equal(1, result.TestCases[0].Number);
        Assert.True(File.Exists(result.TestCases[0].InputFilePath));
        Assert.True(File.Exists(result.TestCases[0].OutputFilePath));
        Assert.Equal("input1", await File.ReadAllTextAsync(result.TestCases[0].InputFilePath));
        Assert.Equal("output1", await File.ReadAllTextAsync(result.TestCases[0].OutputFilePath));

        // Verify second test
        Assert.Equal(2, result.TestCases[1].Number);
        Assert.True(File.Exists(result.TestCases[1].InputFilePath));
        Assert.True(File.Exists(result.TestCases[1].OutputFilePath));

        // Cleanup
        if (Directory.Exists(result.WorkingDirectory))
        {
            Directory.Delete(result.WorkingDirectory, true);
        }
    }

    [Fact]
    public async Task ParsePackageAsync_PolygonPackageMissingAnswerFiles_SkipsTests()
    {
        // Arrange
        var problemXml = @"<?xml version=""1.0"" encoding=""utf-8"" standalone=""no""?>
<problem revision=""7"" short-name=""test-problem"">
    <judging>
        <testset name=""tests"">
            <time-limit>2000</time-limit>
            <memory-limit>536870912</memory-limit>
            <test-count>3</test-count>
            <input-path-pattern>tests/%02d</input-path-pattern>
            <answer-path-pattern>tests/%02d.a</answer-path-pattern>
        </testset>
    </judging>
</problem>";

        var zipStream = CreatePolygonPackage(problemXml, new[]
        {
            ("tests/01", "input1"),
            ("tests/01.a", "output1"),
            ("tests/02", "input2"),
            // Missing 02.a
            ("tests/03", "input3"),
            ("tests/03.a", "output3")
        });

        // Act
        var result = await _service.ParsePackageAsync(zipStream);

        // Assert
        Assert.NotNull(result);
        Assert.Equal(2, result.TestCases.Count); // Only tests with answer files
        Assert.Equal(1, result.TestCases[0].Number);
        Assert.Equal(3, result.TestCases[1].Number); // Test 2 skipped

        // Cleanup
        if (Directory.Exists(result.WorkingDirectory))
        {
            Directory.Delete(result.WorkingDirectory, true);
        }
    }

    [Fact]
    public async Task ParsePackageAsync_NoProblemXml_FallsBackToLegacyFormat()
    {
        // Arrange - create package without problem.xml
        var zipStream = CreateLegacyPackage(new[]
        {
            ("tests/test1.in", "input1"),
            ("tests/test1.out", "output1"),
            ("tests/test2.in", "input2"),
            ("tests/test2.out", "output2")
        });

        // Act
        var result = await _service.ParsePackageAsync(zipStream);

        // Assert
        Assert.NotNull(result);
        Assert.Equal(2, result.TestCases.Count);
        Assert.Equal(2000, result.DefaultTimeLimit); // Default values
        Assert.Equal(256, result.DefaultMemoryLimit);

        // Cleanup
        if (Directory.Exists(result.WorkingDirectory))
        {
            Directory.Delete(result.WorkingDirectory, true);
        }
    }

    private MemoryStream CreatePolygonPackage(string problemXml, IEnumerable<(string fileName, string content)> files)
    {
        var memoryStream = new MemoryStream();

        using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true))
        {
            // Add problem.xml
            var xmlEntry = archive.CreateEntry("problem.xml");
            using (var xmlStream = xmlEntry.Open())
            using (var xmlWriter = new StreamWriter(xmlStream))
            {
                xmlWriter.Write(problemXml);
            }

            // Add test files
            foreach (var (fileName, content) in files)
            {
                var entry = archive.CreateEntry(fileName);
                using (var entryStream = entry.Open())
                using (var writer = new StreamWriter(entryStream))
                {
                    writer.Write(content);
                }
            }
        }

        memoryStream.Position = 0;
        return memoryStream;
    }

    private MemoryStream CreateLegacyPackage(IEnumerable<(string fileName, string content)> files)
    {
        var memoryStream = new MemoryStream();

        using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true))
        {
            foreach (var (fileName, content) in files)
            {
                var entry = archive.CreateEntry(fileName);
                using (var entryStream = entry.Open())
                using (var writer = new StreamWriter(entryStream))
                {
                    writer.Write(content);
                }
            }
        }

        memoryStream.Position = 0;
        return memoryStream;
    }

    public void Dispose()
    {
        if (Directory.Exists(_testDirectory))
        {
            try
            {
                Directory.Delete(_testDirectory, true);
            }
            catch
            {
                // Ignore cleanup errors
            }
        }
    }
}
