using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using LiquidCode.Tester.Worker.Services;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging.Abstractions;
using Xunit;

namespace LiquidCode.Tester.Worker.Tests;

public class PolygonPackageIntegrationTests
{
    private readonly PackageParserService _parserService;

    public PolygonPackageIntegrationTests()
    {
        var configuration = new ConfigurationBuilder().Build();

        var cppCompilation = new CppCompilationService(NullLogger<CppCompilationService>.Instance, configuration);
        var cppExecution = new CppExecutionService(NullLogger<CppExecutionService>.Instance);

        var services = new ServiceCollection();
        services.AddSingleton<IConfiguration>(configuration);
        services.AddSingleton(cppCompilation);
        services.AddSingleton(cppExecution);

        var serviceProvider = services.BuildServiceProvider();

        var compilationFactory = new CompilationServiceFactory(serviceProvider, NullLogger<CompilationServiceFactory>.Instance);
        var executionFactory = new ExecutionServiceFactory(serviceProvider, NullLogger<ExecutionServiceFactory>.Instance);
        var answerGenerator = new AnswerGenerationService(compilationFactory, executionFactory, NullLogger<AnswerGenerationService>.Instance);
        var polygonParser = new PolygonProblemXmlParser(NullLogger<PolygonProblemXmlParser>.Instance);

        _parserService = new PackageParserService(
            NullLogger<PackageParserService>.Instance,
            polygonParser,
            answerGenerator,
            cppCompilation);
    }

    [Fact]
    public async Task ParsePolygonPackageAsync_GeneratesTestsCheckerAndAnswers()
    {
        if (!IsExecutableAvailable("g++"))
        {
            return;
        }

        var packageDirectory = ResolvePackageDirectory("exam-queue-17");
        Assert.True(Directory.Exists(packageDirectory));

        await using var packageStream = CreateZipFromDirectory(packageDirectory);

        var package = await _parserService.ParsePackageAsync(packageStream);

        try
        {
            Assert.Equal(51, package.TestCases.Count);
            Assert.NotNull(package.CheckerPath);
            Assert.True(File.Exists(package.CheckerPath), $"Checker not present at {package.CheckerPath}");

            foreach (var testCase in package.TestCases)
            {
                Assert.True(File.Exists(testCase.InputFilePath), $"Missing input {testCase.InputFilePath}");
                Assert.True(File.Exists(testCase.OutputFilePath), $"Missing output {testCase.OutputFilePath}");
                Assert.True(new FileInfo(testCase.InputFilePath).Length > 0);
                Assert.True(new FileInfo(testCase.OutputFilePath).Length > 0);
            }
        }
        finally
        {
            if (!string.IsNullOrEmpty(package.ExtractionRoot) && Directory.Exists(package.ExtractionRoot))
            {
                Directory.Delete(package.ExtractionRoot, recursive: true);
            }
            else if (Directory.Exists(package.WorkingDirectory))
            {
                Directory.Delete(package.WorkingDirectory, recursive: true);
            }
        }
    }

    private static bool IsExecutableAvailable(string executable)
    {
        try
        {
            using var process = new Process
            {
                StartInfo = new ProcessStartInfo
                {
                    FileName = executable,
                    Arguments = "--version",
                    RedirectStandardOutput = true,
                    RedirectStandardError = true,
                    UseShellExecute = false,
                    CreateNoWindow = true
                }
            };

            process.Start();
            if (!process.WaitForExit(5000))
            {
                try
                {
                    process.Kill(entireProcessTree: true);
                }
                catch
                {
                    // ignored
                }

                return false;
            }

            return process.ExitCode == 0;
        }
        catch
        {
            return false;
        }
    }

    private static MemoryStream CreateZipFromDirectory(string directoryPath)
    {
        var memoryStream = new MemoryStream();
        using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, leaveOpen: true))
        {
            foreach (var file in Directory.GetFiles(directoryPath, "*", SearchOption.AllDirectories))
            {
                var entryName = Path.GetRelativePath(directoryPath, file).Replace("\\", "/");
                var entry = archive.CreateEntry(entryName, CompressionLevel.Fastest);

                using (var entryStream = entry.Open())
                using (var fileStream = File.OpenRead(file))
                {
                    fileStream.CopyTo(entryStream);
                }
            }
        }

        memoryStream.Position = 0;
        return memoryStream;
    }

    private static string ResolvePackageDirectory(string folderName)
    {
        var baseDirectory = AppContext.BaseDirectory;
    var root = Path.GetFullPath(Path.Combine(baseDirectory, "..", "..", "..", "..", ".."));
        return Path.Combine(root, folderName);
    }
}
