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

namespace NatsNet.PortTracker.Commands;

public static class BatchCommands
{
    public static Command Create(Option<string> dbOption)
    {
        var batchCommand = new Command("batch", "Manage implementation batches");

        batchCommand.Add(CreateList(dbOption));
        batchCommand.Add(CreateShow(dbOption));
        batchCommand.Add(CreateReady(dbOption));
        batchCommand.Add(CreateNext(dbOption));
        batchCommand.Add(CreateStart(dbOption));
        batchCommand.Add(CreateComplete(dbOption));

        return batchCommand;
    }

    private static Command CreateList(Option<string> dbOption)
    {
        var statusOpt = new Option<string?>("--status")
        {
            Description = "Filter by status: pending, in_progress, complete"
        };

        var cmd = new Command("list", "List all implementation batches");
        cmd.Add(statusOpt);
        cmd.SetAction(parseResult =>
        {
            var dbPath = parseResult.GetValue(dbOption)!;
            var status = parseResult.GetValue(statusOpt);
            using var db = new Database(dbPath);

            var sql = "SELECT id, name, feature_count, test_count, status, depends_on FROM implementation_batches";
            var parameters = new List<(string, object?)>();

            if (status is not null)
            {
                sql += " WHERE status = @status";
                parameters.Add(("@status", status));
            }
            sql += " ORDER BY priority, id";

            var rows = db.Query(sql, parameters.ToArray());
            if (rows.Count == 0)
            {
                Console.WriteLine("No batches found.");
                return;
            }

            Console.WriteLine($"{"ID",-4} {"Name",-45} {"Feat",-5} {"Test",-5} {"Status",-12} {"Depends On"}");
            Console.WriteLine(new string('-', 100));
            foreach (var row in rows)
            {
                Console.WriteLine(
                    $"{row["id"],-4} " +
                    $"{Truncate(row["name"]?.ToString(), 44),-45} " +
                    $"{row["feature_count"],-5} " +
                    $"{row["test_count"],-5} " +
                    $"{row["status"],-12} " +
                    $"{row["depends_on"] ?? ""}");
            }
            Console.WriteLine($"\nTotal: {rows.Count} batches");
        });

        return cmd;
    }

    private static Command CreateShow(Option<string> dbOption)
    {
        var idArg = new Argument<int>("id") { Description = "Batch ID to show" };

        var cmd = new Command("show", "Show batch details with features and tests");
        cmd.Add(idArg);
        cmd.SetAction(parseResult =>
        {
            var dbPath = parseResult.GetValue(dbOption)!;
            var id = parseResult.GetValue(idArg);
            using var db = new Database(dbPath);

            var batches = db.Query(
                "SELECT * FROM implementation_batches WHERE id = @id",
                ("@id", id));

            if (batches.Count == 0)
            {
                Console.WriteLine($"Batch {id} not found.");
                return;
            }

            var batch = batches[0];
            Console.WriteLine($"Batch {batch["id"]}: {batch["name"]}");
            Console.WriteLine($"  Description: {batch["description"] ?? "(none)"}");
            Console.WriteLine($"  Priority:    {batch["priority"]}");
            Console.WriteLine($"  Status:      {batch["status"]}");
            Console.WriteLine($"  Depends On:  {batch["depends_on"] ?? "(none)"}");
            Console.WriteLine($"  Go Files:    {batch["go_files"] ?? "(none)"}");
            Console.WriteLine($"  Features:    {batch["feature_count"]}");
            Console.WriteLine($"  Tests:       {batch["test_count"]}");
            Console.WriteLine();

            // Show features
            var features = db.Query(
                """
                SELECT f.id, f.name, f.status, f.go_file
                FROM batch_features bf
                JOIN features f ON f.id = bf.feature_id
                WHERE bf.batch_id = @id
                ORDER BY f.id
                """,
                ("@id", id));

            if (features.Count > 0)
            {
                Console.WriteLine($"Features ({features.Count}):");
                Console.WriteLine($"  {"ID",-6} {"Status",-10} {"Name",-50} {"Go File"}");
                Console.WriteLine("  " + new string('-', 90));
                foreach (var f in features)
                {
                    Console.WriteLine(
                        $"  {f["id"],-6} " +
                        $"{f["status"],-10} " +
                        $"{Truncate(f["name"]?.ToString(), 49),-50} " +
                        $"{Truncate(f["go_file"]?.ToString(), 30)}");
                }
            }

            Console.WriteLine();

            // Show tests
            var tests = db.Query(
                """
                SELECT t.id, t.name, t.status, t.go_file
                FROM batch_tests bt
                JOIN unit_tests t ON t.id = bt.test_id
                WHERE bt.batch_id = @id
                ORDER BY t.id
                """,
                ("@id", id));

            if (tests.Count > 0)
            {
                Console.WriteLine($"Tests ({tests.Count}):");
                Console.WriteLine($"  {"ID",-6} {"Status",-10} {"Name",-50} {"Go File"}");
                Console.WriteLine("  " + new string('-', 90));
                foreach (var t in tests)
                {
                    Console.WriteLine(
                        $"  {t["id"],-6} " +
                        $"{t["status"],-10} " +
                        $"{Truncate(t["name"]?.ToString(), 49),-50} " +
                        $"{Truncate(t["go_file"]?.ToString(), 30)}");
                }
            }
        });

        return cmd;
    }

    private static Command CreateReady(Option<string> dbOption)
    {
        var cmd = new Command("ready", "List batches ready to start (all dependencies complete)");
        cmd.SetAction(parseResult =>
        {
            var dbPath = parseResult.GetValue(dbOption)!;
            using var db = new Database(dbPath);

            var readyBatches = GetReadyBatches(db);
            if (readyBatches.Count == 0)
            {
                Console.WriteLine("No batches ready.");
                return;
            }

            Console.WriteLine($"{"ID",-4} {"Name",-45} {"Feat",-5} {"Test",-5} {"Priority"}");
            Console.WriteLine(new string('-', 70));
            foreach (var row in readyBatches)
            {
                Console.WriteLine(
                    $"{row["id"],-4} " +
                    $"{Truncate(row["name"]?.ToString(), 44),-45} " +
                    $"{row["feature_count"],-5} " +
                    $"{row["test_count"],-5} " +
                    $"{row["priority"]}");
            }
            Console.WriteLine($"\n{readyBatches.Count} batches ready");
        });

        return cmd;
    }

    private static Command CreateNext(Option<string> dbOption)
    {
        var cmd = new Command("next", "Show the next recommended batch (lowest priority ready)");
        cmd.SetAction(parseResult =>
        {
            var dbPath = parseResult.GetValue(dbOption)!;
            using var db = new Database(dbPath);

            var readyBatches = GetReadyBatches(db);
            if (readyBatches.Count == 0)
            {
                Console.WriteLine("No batches ready.");
                return;
            }

            var next = readyBatches[0]; // Already sorted by priority
            Console.WriteLine($"Next batch: #{next["id"]} — {next["name"]}");
            Console.WriteLine($"  Priority:    {next["priority"]}");
            Console.WriteLine($"  Features:    {next["feature_count"]}");
            Console.WriteLine($"  Tests:       {next["test_count"]}");
            Console.WriteLine($"  Depends On:  {next["depends_on"] ?? "(none)"}");
            Console.WriteLine($"\nRun: batch start {next["id"]}");
        });

        return cmd;
    }

    private static Command CreateStart(Option<string> dbOption)
    {
        var idArg = new Argument<int>("id") { Description = "Batch ID to start" };

        var cmd = new Command("start", "Mark batch as in-progress (validates dependencies)");
        cmd.Add(idArg);
        cmd.SetAction(parseResult =>
        {
            var dbPath = parseResult.GetValue(dbOption)!;
            var id = parseResult.GetValue(idArg);
            using var db = new Database(dbPath);

            var batches = db.Query(
                "SELECT * FROM implementation_batches WHERE id = @id",
                ("@id", id));

            if (batches.Count == 0)
            {
                Console.WriteLine($"Batch {id} not found.");
                return;
            }

            var batch = batches[0];
            var status = batch["status"]?.ToString();

            if (status == "complete")
            {
                Console.WriteLine($"Batch {id} is already complete.");
                return;
            }

            if (status == "in_progress")
            {
                Console.WriteLine($"Batch {id} is already in progress.");
                return;
            }

            // Check dependencies
            if (!AreDependenciesMet(db, batch))
            {
                var deps = batch["depends_on"]?.ToString() ?? "";
                Console.WriteLine($"Cannot start batch {id}: dependencies not met.");
                Console.WriteLine($"  Depends on: {deps}");
                PrintDependencyStatus(db, deps);
                return;
            }

            db.Execute(
                "UPDATE implementation_batches SET status = 'in_progress' WHERE id = @id",
                ("@id", id));

            Console.WriteLine($"Batch {id} started: {batch["name"]}");
            Console.WriteLine($"  Features: {batch["feature_count"]}, Tests: {batch["test_count"]}");
            Console.WriteLine($"\nRun: batch show {id}  — to see all items");
        });

        return cmd;
    }

    private static Command CreateComplete(Option<string> dbOption)
    {
        var idArg = new Argument<int>("id") { Description = "Batch ID to complete" };

        var cmd = new Command("complete", "Mark batch as complete (validates all items done)");
        cmd.Add(idArg);
        cmd.SetAction(parseResult =>
        {
            var dbPath = parseResult.GetValue(dbOption)!;
            var id = parseResult.GetValue(idArg);
            using var db = new Database(dbPath);

            var batches = db.Query(
                "SELECT * FROM implementation_batches WHERE id = @id",
                ("@id", id));

            if (batches.Count == 0)
            {
                Console.WriteLine($"Batch {id} not found.");
                return;
            }

            var batch = batches[0];
            if (batch["status"]?.ToString() == "complete")
            {
                Console.WriteLine($"Batch {id} is already complete.");
                return;
            }

            // Check all features are done
            var incompleteFeatures = db.Query(
                """
                SELECT f.id, f.name, f.status
                FROM batch_features bf
                JOIN features f ON f.id = bf.feature_id
                WHERE bf.batch_id = @id AND f.status NOT IN ('verified', 'complete', 'n_a')
                ORDER BY f.id
                """,
                ("@id", id));

            // Check all tests are done
            var incompleteTests = db.Query(
                """
                SELECT t.id, t.name, t.status
                FROM batch_tests bt
                JOIN unit_tests t ON t.id = bt.test_id
                WHERE bt.batch_id = @id AND t.status NOT IN ('verified', 'complete', 'n_a')
                ORDER BY t.id
                """,
                ("@id", id));

            if (incompleteFeatures.Count > 0 || incompleteTests.Count > 0)
            {
                Console.WriteLine($"Cannot complete batch {id}: items remain.");

                if (incompleteFeatures.Count > 0)
                {
                    Console.WriteLine($"\n  Incomplete features ({incompleteFeatures.Count}):");
                    foreach (var f in incompleteFeatures.Take(20))
                    {
                        Console.WriteLine($"    {f["id"],-6} {f["status"],-10} {Truncate(f["name"]?.ToString(), 50)}");
                    }
                    if (incompleteFeatures.Count > 20)
                        Console.WriteLine($"    ... and {incompleteFeatures.Count - 20} more");
                }

                if (incompleteTests.Count > 0)
                {
                    Console.WriteLine($"\n  Incomplete tests ({incompleteTests.Count}):");
                    foreach (var t in incompleteTests.Take(20))
                    {
                        Console.WriteLine($"    {t["id"],-6} {t["status"],-10} {Truncate(t["name"]?.ToString(), 50)}");
                    }
                    if (incompleteTests.Count > 20)
                        Console.WriteLine($"    ... and {incompleteTests.Count - 20} more");
                }
                return;
            }

            db.Execute(
                "UPDATE implementation_batches SET status = 'complete' WHERE id = @id",
                ("@id", id));

            Console.WriteLine($"Batch {id} completed: {batch["name"]}");
        });

        return cmd;
    }

    private static List<Dictionary<string, object?>> GetReadyBatches(Database db)
    {
        // Get all pending batches
        var pending = db.Query(
            "SELECT * FROM implementation_batches WHERE status = 'pending' ORDER BY priority, id");

        // Get statuses of all batches for dependency checking
        var allStatuses = db.Query("SELECT id, status FROM implementation_batches");
        var statusMap = new Dictionary<long, string>();
        foreach (var row in allStatuses)
        {
            statusMap[(long)row["id"]!] = row["status"]?.ToString() ?? "pending";
        }

        var ready = new List<Dictionary<string, object?>>();
        foreach (var batch in pending)
        {
            if (AreDependenciesMet(statusMap, batch["depends_on"]?.ToString()))
                ready.Add(batch);
        }

        return ready;
    }

    private static bool AreDependenciesMet(Database db, Dictionary<string, object?> batch)
    {
        var depsStr = batch["depends_on"]?.ToString();
        if (string.IsNullOrWhiteSpace(depsStr))
            return true;

        var allStatuses = db.Query("SELECT id, status FROM implementation_batches");
        var statusMap = new Dictionary<long, string>();
        foreach (var row in allStatuses)
        {
            statusMap[(long)row["id"]!] = row["status"]?.ToString() ?? "pending";
        }

        return AreDependenciesMet(statusMap, depsStr);
    }

    private static bool AreDependenciesMet(Dictionary<long, string> statusMap, string? depsStr)
    {
        if (string.IsNullOrWhiteSpace(depsStr))
            return true;

        var depIds = depsStr.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
        foreach (var depIdStr in depIds)
        {
            if (long.TryParse(depIdStr, out var depId))
            {
                if (!statusMap.TryGetValue(depId, out var depStatus) || depStatus != "complete")
                    return false;
            }
        }

        return true;
    }

    private static void PrintDependencyStatus(Database db, string depsStr)
    {
        if (string.IsNullOrWhiteSpace(depsStr)) return;

        var depIds = depsStr.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
        foreach (var depIdStr in depIds)
        {
            if (int.TryParse(depIdStr, out var depId))
            {
                var rows = db.Query(
                    "SELECT id, name, status FROM implementation_batches WHERE id = @id",
                    ("@id", depId));
                if (rows.Count > 0)
                {
                    var r = rows[0];
                    Console.WriteLine($"    Batch {r["id"]}: {r["status"]} — {r["name"]}");
                }
            }
        }
    }

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