using System.Diagnostics;
using System.Text.Json;

var options = BridgeOptions.Parse(args);
Directory.CreateDirectory(options.QueueDir);
Directory.CreateDirectory(Path.Combine(options.QueueDir, "jobs"));
Directory.CreateDirectory(Path.Combine(options.QueueDir, "results"));

Console.WriteLine($"AutoCadDwgBridge queue: {options.QueueDir}");
Console.WriteLine($"Extractor: {options.ExtractorDll}");
Console.WriteLine("Press Ctrl+C to stop.");

var stopping = false;
Console.CancelKeyPress += (_, eventArgs) =>
{
    eventArgs.Cancel = true;
    stopping = true;
};

while (!stopping)
{
    WriteHeartbeat(options);
    ProcessJobs(options);
    Thread.Sleep(options.PollMs);
}

static void ProcessJobs(BridgeOptions options)
{
    var jobsDir = Path.Combine(options.QueueDir, "jobs");
    foreach (var requestPath in Directory.GetFiles(jobsDir, "*.request.json").OrderBy(File.GetCreationTimeUtc))
    {
        var runningPath = Path.ChangeExtension(requestPath, ".running.json");
        try
        {
            File.Move(requestPath, runningPath);
        }
        catch
        {
            continue;
        }

        BridgeJob? job = null;
        try
        {
            job = JsonSerializer.Deserialize<BridgeJob>(File.ReadAllText(runningPath), JsonDefaults.Options)
                ?? throw new InvalidOperationException($"Invalid bridge job: {runningPath}");
            var result = RunJob(options, job);
            WriteResult(options, job.JobId, result);
            File.Delete(runningPath);
        }
        catch (Exception ex)
        {
            var jobId = job?.JobId ?? Path.GetFileNameWithoutExtension(Path.GetFileNameWithoutExtension(runningPath));
            WriteResult(options, jobId, new BridgeJobResult
            {
                Ok = false,
                JobId = jobId,
                ExitCode = -1,
                Error = ex.ToString()
            });
            TryMove(runningPath, Path.ChangeExtension(runningPath, ".failed.json"));
        }
    }
}

static BridgeJobResult RunJob(BridgeOptions options, BridgeJob job)
{
    if (string.IsNullOrWhiteSpace(job.JobId))
        throw new InvalidOperationException("Bridge job missing job_id.");
    if (!File.Exists(options.ExtractorDll))
        throw new FileNotFoundException($"AutoCadDwgExtractor.dll not found: {options.ExtractorDll}");
    if (string.IsNullOrWhiteSpace(job.DrawingPath) || !File.Exists(job.DrawingPath))
        throw new FileNotFoundException($"DWG not found: {job.DrawingPath}");
    if (string.IsNullOrWhiteSpace(job.OutputDir))
        throw new InvalidOperationException("Bridge job missing output_dir.");

    Directory.CreateDirectory(job.OutputDir);
    var startInfo = new ProcessStartInfo
    {
        FileName = "dotnet",
        WorkingDirectory = options.AgentRoot,
        UseShellExecute = false,
        RedirectStandardOutput = true,
        RedirectStandardError = true
    };
    startInfo.ArgumentList.Add(options.ExtractorDll);
    AddArg(startInfo, "--drawing-path", job.DrawingPath);
    AddArg(startInfo, "--output-dir", job.OutputDir);
    AddArg(startInfo, "--prog-id", string.IsNullOrWhiteSpace(job.ProgId) ? options.ProgId : job.ProgId);
    AddArg(startInfo, "--visible", job.Visible ? "true" : "false");
    AddArg(startInfo, "--close-after-extract", job.CloseAfterExtract ? "true" : "false");
    AddArg(startInfo, "--max-entities", (job.MaxEntities <= 0 ? 100_000 : job.MaxEntities).ToString());
    if (!string.IsNullOrWhiteSpace(job.MainViewSeedMode))
        AddArg(startInfo, "--main-view-seed-mode", job.MainViewSeedMode);
    if (!string.IsNullOrWhiteSpace(job.ReuseExtractionDir))
        AddArg(startInfo, "--reuse-extraction-dir", job.ReuseExtractionDir);

    Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] job {job.JobId} start: {Path.GetFileName(job.DrawingPath)}");
    using var process = Process.Start(startInfo) ?? throw new InvalidOperationException("Failed to start extractor.");
    var stdout = process.StandardOutput.ReadToEnd();
    var stderr = process.StandardError.ReadToEnd();
    process.WaitForExit();
    Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] job {job.JobId} exit={process.ExitCode}");

    var manifestPath = Path.Combine(job.OutputDir, "manifest.json");
    return new BridgeJobResult
    {
        Ok = process.ExitCode == 0 && File.Exists(manifestPath),
        JobId = job.JobId,
        ExitCode = process.ExitCode,
        Stdout = stdout,
        Stderr = stderr,
        OutputDir = job.OutputDir,
        ManifestPath = manifestPath,
        Error = process.ExitCode == 0 ? "" : $"Extractor failed with exit code {process.ExitCode}."
    };
}

static void AddArg(ProcessStartInfo startInfo, string key, string value)
{
    startInfo.ArgumentList.Add(key);
    startInfo.ArgumentList.Add(value);
}

static void WriteHeartbeat(BridgeOptions options)
{
    var path = Path.Combine(options.QueueDir, "heartbeat.json");
    File.WriteAllText(path, JsonSerializer.Serialize(new BridgeHeartbeat
    {
        Ok = true,
        ProcessId = Environment.ProcessId,
        MachineName = Environment.MachineName,
        UserName = Environment.UserName,
        WrittenAtUtc = DateTime.UtcNow
    }, JsonDefaults.Options));
}

static void WriteResult(BridgeOptions options, string jobId, BridgeJobResult result)
{
    var path = Path.Combine(options.QueueDir, "results", $"{jobId}.result.json");
    var tempPath = path + ".tmp";
    File.WriteAllText(tempPath, JsonSerializer.Serialize(result, JsonDefaults.Options));
    File.Move(tempPath, path, overwrite: true);
}

static void TryMove(string source, string destination)
{
    try
    {
        if (File.Exists(source))
            File.Move(source, destination, overwrite: true);
    }
    catch
    {
        // Best effort.
    }
}

sealed class BridgeOptions
{
    public string AgentRoot { get; set; } = Directory.GetCurrentDirectory();
    public string QueueDir { get; set; } = "";
    public string ExtractorDll { get; set; } = "";
    public string ProgId { get; set; } = "AutoCAD.Application.23.1";
    public int PollMs { get; set; } = 1000;

    public static BridgeOptions Parse(string[] args)
    {
        var options = new BridgeOptions();
        for (var i = 0; i < args.Length; i++)
        {
            var key = args[i];
            var value = i + 1 < args.Length ? args[i + 1] : "";
            switch (key)
            {
                case "--agent-root":
                    options.AgentRoot = Path.GetFullPath(value.Trim().Trim('"'));
                    i++;
                    break;
                case "--queue-dir":
                    options.QueueDir = Path.GetFullPath(value.Trim().Trim('"'));
                    i++;
                    break;
                case "--extractor-dll":
                    options.ExtractorDll = Path.GetFullPath(value.Trim().Trim('"'));
                    i++;
                    break;
                case "--prog-id":
                    options.ProgId = value.Trim();
                    i++;
                    break;
                case "--poll-ms":
                    options.PollMs = int.TryParse(value, out var pollMs) ? Math.Max(100, pollMs) : options.PollMs;
                    i++;
                    break;
            }
        }

        if (string.IsNullOrWhiteSpace(options.QueueDir))
            options.QueueDir = Path.Combine(options.AgentRoot, "runtime", "dwg-draft", "bridge");
        if (string.IsNullOrWhiteSpace(options.ExtractorDll))
            options.ExtractorDll = Path.Combine(
                options.AgentRoot,
                "tools",
                "drawing-diagnostics",
                "AutoCadDwgExtractor",
                "bin",
                "Debug",
                "net10.0",
                "AutoCadDwgExtractor.dll");
        return options;
    }
}

sealed class BridgeJob
{
    public string JobId { get; set; } = "";
    public string DrawingPath { get; set; } = "";
    public string OutputDir { get; set; } = "";
    public string ProgId { get; set; } = "";
    public bool Visible { get; set; } = true;
    public bool CloseAfterExtract { get; set; }
    public int MaxEntities { get; set; } = 100_000;
    public string MainViewSeedMode { get; set; } = "";
    public string ReuseExtractionDir { get; set; } = "";
}

sealed class BridgeJobResult
{
    public bool Ok { get; set; }
    public string JobId { get; set; } = "";
    public int ExitCode { get; set; }
    public string Stdout { get; set; } = "";
    public string Stderr { get; set; } = "";
    public string OutputDir { get; set; } = "";
    public string ManifestPath { get; set; } = "";
    public string Error { get; set; } = "";
}

sealed class BridgeHeartbeat
{
    public bool Ok { get; set; }
    public int ProcessId { get; set; }
    public string MachineName { get; set; } = "";
    public string UserName { get; set; } = "";
    public DateTime WrittenAtUtc { get; set; }
}

static class JsonDefaults
{
    public static JsonSerializerOptions Options { get; } = new()
    {
        WriteIndented = true,
        PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
        DictionaryKeyPolicy = JsonNamingPolicy.SnakeCaseLower
    };
}
