using System.CommandLine;
using NatsNet.PortTracker.Data;

namespace NatsNet.PortTracker.Commands;

public static class TestCommands
{
    public static Command Create(Option<string> dbOption, Option<string> schemaOption)
    {
        var testCommand = new Command("test", "Manage unit tests");

        // list
        var listModule = new Option<int?>("--module") { Description = "Filter by module ID" };
        var listStatus = new Option<string?>("--status") { Description = "Filter by status" };
        var listCmd = new Command("list", "List unit tests");
        listCmd.Add(listModule);
        listCmd.Add(listStatus);
        listCmd.SetAction(parseResult =>
        {
            var dbPath = parseResult.GetValue(dbOption)!;
            var moduleId = parseResult.GetValue(listModule);
            var status = parseResult.GetValue(listStatus);
            using var db = new Database(dbPath);
            var sql = "SELECT t.id, t.name, t.status, t.module_id, m.name as module_name, t.go_method, t.dotnet_method FROM unit_tests t LEFT JOIN modules m ON t.module_id = m.id";
            var parameters = new List<(string, object?)>();
            var clauses = new List<string>();
            if (moduleId is not null)
            {
                clauses.Add("t.module_id = @module");
                parameters.Add(("@module", moduleId));
            }
            if (status is not null)
            {
                clauses.Add("t.status = @status");
                parameters.Add(("@status", status));
            }
            if (clauses.Count > 0)
                sql += " WHERE " + string.Join(" AND ", clauses);
            sql += " ORDER BY m.name, t.name";

            var rows = db.Query(sql, parameters.ToArray());
            Console.WriteLine($"{"ID",-5} {"Name",-30} {"Status",-15} {"Module",-20} {"Go Method",-25} {"DotNet Method",-25}");
            Console.WriteLine(new string('-', 120));
            foreach (var row in rows)
            {
                Console.WriteLine($"{row["id"],-5} {Truncate(row["name"]?.ToString(), 29),-30} {row["status"],-15} {Truncate(row["module_name"]?.ToString(), 19),-20} {Truncate(row["go_method"]?.ToString(), 24),-25} {Truncate(row["dotnet_method"]?.ToString(), 24),-25}");
            }
            Console.WriteLine($"\nTotal: {rows.Count} tests");
        });

        // show
        var showId = new Argument<int>("id") { Description = "Test ID" };
        var showCmd = new Command("show", "Show test details");
        showCmd.Add(showId);
        showCmd.SetAction(parseResult =>
        {
            var dbPath = parseResult.GetValue(dbOption)!;
            var id = parseResult.GetValue(showId);
            using var db = new Database(dbPath);
            var tests = db.Query(
                "SELECT t.*, m.name as module_name FROM unit_tests t LEFT JOIN modules m ON t.module_id = m.id WHERE t.id = @id",
                ("@id", id));
            if (tests.Count == 0)
            {
                Console.WriteLine($"Test {id} not found.");
                return;
            }
            var t = tests[0];
            Console.WriteLine($"Test #{t["id"]}: {t["name"]}");
            Console.WriteLine($"  Module:     #{t["module_id"]} ({t["module_name"]})");
            Console.WriteLine($"  Feature:    {(t["feature_id"] is not null ? $"#{t["feature_id"]}" : "(none)")}");
            Console.WriteLine($"  Status:     {t["status"]}");
            Console.WriteLine($"  Go File:    {t["go_file"]}");
            Console.WriteLine($"  Go Class:   {t["go_class"]}");
            Console.WriteLine($"  Go Method:  {t["go_method"]}");
            Console.WriteLine($"  Go Line:    {t["go_line_number"]}");
            Console.WriteLine($"  Go LOC:     {t["go_line_count"]}");
            Console.WriteLine($"  .NET:       {t["dotnet_project"]} / {t["dotnet_class"]} / {t["dotnet_method"]}");
            Console.WriteLine($"  Notes:      {t["notes"]}");

            var deps = db.Query(
                "SELECT d.target_type, d.target_id, d.dependency_kind FROM dependencies d WHERE d.source_type = 'unit_test' AND d.source_id = @id",
                ("@id", id));
            Console.WriteLine($"\n  Dependencies ({deps.Count}):");
            foreach (var d in deps)
                Console.WriteLine($"    -> {d["target_type"]} #{d["target_id"]} [{d["dependency_kind"]}]");
        });

        // update
        var updateId = new Argument<int>("id") { Description = "Test ID" };
        var updateStatus = new Option<string>("--status") { Description = "New status", Required = true };
        var updateCmd = new Command("update", "Update test status");
        updateCmd.Add(updateId);
        updateCmd.Add(updateStatus);
        updateCmd.SetAction(parseResult =>
        {
            var dbPath = parseResult.GetValue(dbOption)!;
            var id = parseResult.GetValue(updateId);
            var status = parseResult.GetValue(updateStatus)!;
            using var db = new Database(dbPath);
            var affected = db.Execute("UPDATE unit_tests SET status = @status WHERE id = @id",
                ("@status", status), ("@id", id));
            Console.WriteLine(affected > 0 ? $"Test {id} updated to '{status}'." : $"Test {id} not found.");
        });

        // map
        var mapId = new Argument<int>("id") { Description = "Test ID" };
        var mapProject = new Option<string?>("--project") { Description = "Target .NET project" };
        var mapClass = new Option<string?>("--class") { Description = "Target .NET test class" };
        var mapMethod = new Option<string?>("--method") { Description = "Target .NET test method" };
        var mapCmd = new Command("map", "Map test to .NET test method");
        mapCmd.Add(mapId);
        mapCmd.Add(mapProject);
        mapCmd.Add(mapClass);
        mapCmd.Add(mapMethod);
        mapCmd.SetAction(parseResult =>
        {
            var dbPath = parseResult.GetValue(dbOption)!;
            var id = parseResult.GetValue(mapId);
            var project = parseResult.GetValue(mapProject);
            var cls = parseResult.GetValue(mapClass);
            var method = parseResult.GetValue(mapMethod);
            using var db = new Database(dbPath);
            var affected = db.Execute(
                "UPDATE unit_tests SET dotnet_project = COALESCE(@project, dotnet_project), dotnet_class = COALESCE(@cls, dotnet_class), dotnet_method = COALESCE(@method, dotnet_method) WHERE id = @id",
                ("@project", project), ("@cls", cls), ("@method", method), ("@id", id));
            Console.WriteLine(affected > 0 ? $"Test {id} mapped." : $"Test {id} not found.");
        });

        testCommand.Add(listCmd);
        testCommand.Add(showCmd);
        testCommand.Add(updateCmd);
        testCommand.Add(mapCmd);

        return testCommand;
    }

    private static string Truncate(string? s, int maxLen)
    {
        if (s is null) return "";
        return s.Length <= maxLen ? s : s[..(maxLen - 2)] + "..";
    }
}
