using System.Diagnostics;

namespace LiquidCode.Tester.Worker.Services;

public class KotlinExecutionService : IExecutionService
{
    private readonly ILogger<KotlinExecutionService> _logger;

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

    public async Task<ExecutionResult> ExecuteAsync(string executablePath, string inputFilePath, int timeLimitMs, int memoryLimitMb)
    {
        _logger.LogInformation("Executing Kotlin JAR {Executable} with input {Input}, time limit {TimeLimit}ms, memory limit {MemoryLimit}MB",
            executablePath, inputFilePath, timeLimitMs, memoryLimitMb);

        var result = new ExecutionResult();
        var stopwatch = Stopwatch.StartNew();

        try
        {
            using var inputStream = File.OpenRead(inputFilePath);

            var process = new Process
            {
                StartInfo = new ProcessStartInfo
                {
                    FileName = "java",
                    Arguments = $"-jar \"{executablePath}\"",
                    WorkingDirectory = Path.GetDirectoryName(executablePath),
                    RedirectStandardInput = true,
                    RedirectStandardOutput = true,
                    RedirectStandardError = true,
                    UseShellExecute = false,
                    CreateNoWindow = true
                }
            };

            process.Start();

            await inputStream.CopyToAsync(process.StandardInput.BaseStream);
            process.StandardInput.Close();

            var outputTask = process.StandardOutput.ReadToEndAsync();
            var errorTask = process.StandardError.ReadToEndAsync();

            var completedInTime = await Task.Run(() => process.WaitForExit(timeLimitMs));

            stopwatch.Stop();
            result.ExecutionTimeMs = stopwatch.ElapsedMilliseconds;

            if (!completedInTime)
            {
                try
                {
                    process.Kill(entireProcessTree: true);
                }
                catch { }

                result.TimeLimitExceeded = true;
                result.ErrorMessage = "Time limit exceeded";
                _logger.LogWarning("Execution exceeded time limit");
                return result;
            }

            result.Output = await outputTask;
            result.ErrorOutput = await errorTask;
            result.ExitCode = process.ExitCode;

            if (process.ExitCode != 0)
            {
                result.RuntimeError = true;
                result.ErrorMessage = $"Runtime error (exit code {process.ExitCode})";
                _logger.LogWarning("Runtime error with exit code {ExitCode}", process.ExitCode);
                return result;
            }

            try
            {
                result.MemoryUsedMb = process.PeakWorkingSet64 / (1024 * 1024);
                if (result.MemoryUsedMb > memoryLimitMb)
                {
                    result.MemoryLimitExceeded = true;
                    result.ErrorMessage = "Memory limit exceeded";
                    _logger.LogWarning("Memory limit exceeded: {Used}MB > {Limit}MB", result.MemoryUsedMb, memoryLimitMb);
                    return result;
                }
            }
            catch (Exception ex)
            {
                _logger.LogWarning(ex, "Could not measure memory usage");
            }

            result.Success = true;
            _logger.LogInformation("Execution completed successfully in {Time}ms", result.ExecutionTimeMs);
        }
        catch (Exception ex)
        {
            stopwatch.Stop();
            result.ExecutionTimeMs = stopwatch.ElapsedMilliseconds;
            result.RuntimeError = true;
            result.ErrorMessage = $"Execution error: {ex.Message}";
            _logger.LogError(ex, "Error during execution");
        }

        return result;
    }
}
