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

namespace LiquidCode.Tester.Worker.Tests;

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

    public PackageParserServiceTests()
    {
        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(), "PackageParserTests", Guid.NewGuid().ToString());
        Directory.CreateDirectory(_testDirectory);
    }

    [Fact]
    public async Task ParsePackageAsync_ValidPackageWithTestCases_ParsesSuccessfully()
    {
        // Arrange
        var zipStream = CreateTestPackage(new[]
        {
            ("1.in", "input1"),
            ("1.out", "output1"),
            ("2.in", "input2"),
            ("2.out", "output2"),
            ("3.in", "input3"),
            ("3.out", "output3")
        });

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

        // Assert
        Assert.NotNull(result);
        Assert.Equal(3, result.TestCases.Count);
        Assert.True(Directory.Exists(result.WorkingDirectory));

        // Check test case 1
        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));

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

        // Check test case 3
        Assert.Equal(3, result.TestCases[2].Number);
        Assert.True(File.Exists(result.TestCases[2].InputFilePath));
        Assert.True(File.Exists(result.TestCases[2].OutputFilePath));
    }

    [Fact]
    public async Task ParsePackageAsync_PackageWithDefaultLimits_UsesDefaultValues()
    {
        // Arrange
        var zipStream = CreateTestPackage(new[]
        {
            ("1.in", "input1"),
            ("1.out", "output1")
        });

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

        // Assert
        Assert.Single(result.TestCases);
        Assert.Equal(2000, result.TestCases[0].TimeLimit); // Default time limit
        Assert.Equal(256, result.TestCases[0].MemoryLimit); // Default memory limit
    }

    [Fact]
    public async Task ParsePackageAsync_EmptyPackage_ReturnsEmptyTestCasesList()
    {
        // Arrange
        var zipStream = CreateTestPackage(Array.Empty<(string, string)>());

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

        // Assert
        Assert.NotNull(result);
        Assert.Empty(result.TestCases);
    }

    [Fact]
    public async Task ParsePackageAsync_TestCasesNotInOrder_SortsCorrectly()
    {
        // Arrange
        var zipStream = CreateTestPackage(new[]
        {
            ("3.in", "input3"),
            ("3.out", "output3"),
            ("1.in", "input1"),
            ("1.out", "output1"),
            ("2.in", "input2"),
            ("2.out", "output2")
        });

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

        // Assert
        Assert.Equal(3, result.TestCases.Count);
        Assert.Equal(1, result.TestCases[0].Number);
        Assert.Equal(2, result.TestCases[1].Number);
        Assert.Equal(3, result.TestCases[2].Number);
    }

    [Fact]
    public async Task ParsePackageAsync_MissingOutputFile_SkipsTestCase()
    {
        // Arrange
        var zipStream = CreateTestPackage(new[]
        {
            ("1.in", "input1"),
            ("1.out", "output1"),
            ("2.in", "input2"),
            // Missing 2.out
            ("3.in", "input3"),
            ("3.out", "output3")
        });

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

    // Assert
    Assert.Equal(2, result.TestCases.Count); // Only tests with complete I/O pairs
    Assert.Equal(1, result.TestCases[0].Number);
    Assert.Equal(2, result.TestCases[1].Number);
    }

    [Fact]
    public async Task ParsePackageAsync_MissingInputFile_SkipsTestCase()
    {
        // Arrange
        var zipStream = CreateTestPackage(new[]
        {
            ("1.in", "input1"),
            ("1.out", "output1"),
            // Missing 2.in
            ("2.out", "output2"),
            ("3.in", "input3"),
            ("3.out", "output3")
        });

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

        // Assert
    Assert.Equal(2, result.TestCases.Count); // Only tests with complete I/O pairs
    Assert.Equal(1, result.TestCases[0].Number);
    Assert.Equal(2, result.TestCases[1].Number);
    }

    [Fact]
    public async Task ParsePackageAsync_TestsInSubdirectory_ParsesSuccessfully()
    {
        // Arrange
        var zipStream = CreateTestPackage(new[]
        {
            ("tests/1.in", "input1"),
            ("tests/1.out", "output1"),
            ("tests/2.in", "input2"),
            ("tests/2.out", "output2")
        });

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

        // Assert
        Assert.Equal(2, result.TestCases.Count);
        Assert.Equal(1, result.TestCases[0].Number);
        Assert.Equal(2, result.TestCases[1].Number);
    }

    private MemoryStream CreateTestPackage(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))
        {
            Directory.Delete(_testDirectory, true);
        }
    }
}
