using LiquidCode.Tester.Common.Models;
using LiquidCode.Tester.Worker.Controllers;

namespace LiquidCode.Tester.Worker.Services;

public class TestingService : ITestingService
{
    private readonly IPackageParserService _packageParser;
    private readonly ICompilationServiceFactory _compilationServiceFactory;
    private readonly IExecutionServiceFactory _executionServiceFactory;
    private readonly IOutputCheckerService _outputChecker;
    private readonly ICallbackService _callbackService;
    private readonly ILogger<TestingService> _logger;

    public TestingService(
        IPackageParserService packageParser,
        ICompilationServiceFactory compilationServiceFactory,
        IExecutionServiceFactory executionServiceFactory,
        IOutputCheckerService outputChecker,
        ICallbackService callbackService,
        ILogger<TestingService> logger)
    {
        _packageParser = packageParser;
        _compilationServiceFactory = compilationServiceFactory;
        _executionServiceFactory = executionServiceFactory;
        _outputChecker = outputChecker;
        _callbackService = callbackService;
        _logger = logger;
    }

    public async Task ProcessSubmitAsync(TestRequest request)
    {
        _logger.LogInformation("Starting to process submit {SubmitId}", request.Id);

        try
        {
            // Send initial status
            await SendStatusAsync(request, State.Waiting, ErrorCode.None, "Submit received", 0, 0);

            // Parse package
            ProblemPackage package;
            if (!string.IsNullOrEmpty(request.PackageFilePath))
            {
                // Use saved file path (from background task)
                await using var fileStream = File.OpenRead(request.PackageFilePath);
                package = await _packageParser.ParsePackageAsync(fileStream);
            }
            else if (request.Package != null)
            {
                // Use IFormFile directly (should not happen in background tasks)
                using var packageStream = request.Package.OpenReadStream();
                package = await _packageParser.ParsePackageAsync(packageStream);
            }
            else
            {
                throw new InvalidOperationException("No package provided");
            }

            _logger.LogInformation("Package parsed, found {TestCount} tests", package.TestCases.Count);

            // Validate that package contains test cases
            if (package.TestCases.Count == 0)
            {
                _logger.LogError("No test cases found in package for submit {SubmitId}", request.Id);
                await SendStatusAsync(request, State.Done, ErrorCode.UnknownError,
                    "No test cases found in package", 0, 0);
                    CleanupWorkingDirectory(package.ExtractionRoot ?? package.WorkingDirectory);
                return;
            }

            // Send compiling status
            await SendStatusAsync(request, State.Compiling, ErrorCode.None, "Compiling solution", 0, package.TestCases.Count);

            // Get language-specific services
            var compilationService = _compilationServiceFactory.GetCompilationService(request.Language);
            var executionService = _executionServiceFactory.GetExecutionService(request.Language);

            // Compile user solution
            var compilationResult = await compilationService.CompileAsync(request.SourceCode, package.WorkingDirectory, request.LanguageVersion);

            if (!compilationResult.Success)
            {
                _logger.LogWarning("Compilation failed for submit {SubmitId}", request.Id);
                await SendStatusAsync(request, State.Done, ErrorCode.CompileError,
                    $"Compilation failed: {compilationResult.CompilerOutput}", 0, package.TestCases.Count);
                return;
            }

            _logger.LogInformation("Compilation successful");

            // Send testing status
            await SendStatusAsync(request, State.Testing, ErrorCode.None, "Running tests", 0, package.TestCases.Count);

            // Run tests
            for (int i = 0; i < package.TestCases.Count; i++)
            {
                var testCase = package.TestCases[i];
                _logger.LogInformation("Running test {TestNumber}/{TotalTests}", testCase.Number, package.TestCases.Count);

                await SendStatusAsync(request, State.Testing, ErrorCode.None,
                    $"Running test {testCase.Number}", testCase.Number, package.TestCases.Count);

                // Execute solution
                var executionResult = await executionService.ExecuteAsync(
                    compilationResult.ExecutablePath!,
                    testCase.InputFilePath,
                    testCase.TimeLimit,
                    testCase.MemoryLimit);

                // Check for execution errors
                if (executionResult.TimeLimitExceeded)
                {
                    _logger.LogWarning("Time limit exceeded on test {TestNumber}", testCase.Number);
                    await SendStatusAsync(request, State.Done, ErrorCode.TimeLimitError,
                        $"Time limit exceeded on test {testCase.Number}", testCase.Number, package.TestCases.Count);
                        CleanupWorkingDirectory(package.ExtractionRoot ?? package.WorkingDirectory);
                    return;
                }

                if (executionResult.MemoryLimitExceeded)
                {
                    _logger.LogWarning("Memory limit exceeded on test {TestNumber}", testCase.Number);
                    await SendStatusAsync(request, State.Done, ErrorCode.MemoryError,
                        $"Memory limit exceeded on test {testCase.Number}", testCase.Number, package.TestCases.Count);
                        CleanupWorkingDirectory(package.ExtractionRoot ?? package.WorkingDirectory);
                    return;
                }

                if (executionResult.RuntimeError)
                {
                    _logger.LogWarning("Runtime error on test {TestNumber}: {Error}", testCase.Number, executionResult.ErrorMessage);
                    await SendStatusAsync(request, State.Done, ErrorCode.RuntimeError,
                        $"Runtime error on test {testCase.Number}: {executionResult.ErrorMessage}", testCase.Number, package.TestCases.Count);
                        CleanupWorkingDirectory(package.ExtractionRoot ?? package.WorkingDirectory);
                    return;
                }

                // Check output (using custom checker if available)
                var outputCorrect = await _outputChecker.CheckOutputWithCheckerAsync(
                    executionResult.Output,
                    testCase.InputFilePath,
                    testCase.OutputFilePath,
                    package.CheckerPath);

                if (!outputCorrect)
                {
                    _logger.LogWarning("Wrong answer on test {TestNumber}", testCase.Number);
                    await SendStatusAsync(request, State.Done, ErrorCode.IncorrectAnswer,
                        $"Wrong answer on test {testCase.Number}", testCase.Number, package.TestCases.Count);
                        CleanupWorkingDirectory(package.ExtractionRoot ?? package.WorkingDirectory);
                    return;
                }

                _logger.LogInformation("Test {TestNumber} passed", testCase.Number);
            }

            // All tests passed!
            _logger.LogInformation("All tests passed for submit {SubmitId}", request.Id);
            await SendStatusAsync(request, State.Done, ErrorCode.None,
                "All tests passed", package.TestCases.Count, package.TestCases.Count);

            // Cleanup
                CleanupWorkingDirectory(package.ExtractionRoot ?? package.WorkingDirectory);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error processing submit {SubmitId}", request.Id);
            await SendStatusAsync(request, State.Done, ErrorCode.UnknownError,
                $"Internal error: {ex.Message}", 0, 0);
        }
    }

    private async Task SendStatusAsync(TestRequest request, State state, ErrorCode errorCode, string message, int currentTest, int totalTests)
    {
        var response = new TesterResponseModel(
            SubmitId: request.Id,
            State: state,
            ErrorCode: errorCode,
            Message: message,
            CurrentTest: currentTest,
            AmountOfTests: totalTests
        );

        await _callbackService.SendStatusAsync(request.CallbackUrl, response);
    }

    private void CleanupWorkingDirectory(string workingDirectory)
    {
        try
        {
            if (Directory.Exists(workingDirectory))
            {
                Directory.Delete(workingDirectory, recursive: true);
                _logger.LogInformation("Cleaned up working directory {WorkingDirectory}", workingDirectory);
            }
        }
        catch (Exception ex)
        {
            _logger.LogWarning(ex, "Failed to cleanup working directory {WorkingDirectory}", workingDirectory);
        }
    }
}
