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

namespace NatsNet.PortTracker.Commands;

public static class DependencyCommands
{
    public static Command Create(Option<string> dbOption, Option<string> schemaOption)
    {
        var depCommand = new Command("dependency", "Manage dependencies");

        // show
        var showType = new Argument<string>("type") { Description = "Item type (module, feature, unit_test)" };
        var showId = new Argument<int>("id") { Description = "Item ID" };
        var showCmd = new Command("show", "Show dependencies for an item");
        showCmd.Add(showType);
        showCmd.Add(showId);
        showCmd.SetAction(parseResult =>
        {
            var dbPath = parseResult.GetValue(dbOption)!;
            var type = parseResult.GetValue(showType)!;
            var id = parseResult.GetValue(showId);
            using var db = new Database(dbPath);

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

            var rdeps = db.Query(
                "SELECT source_type, source_id, dependency_kind FROM dependencies WHERE target_type = @type AND target_id = @id",
                ("@type", type), ("@id", id));
            Console.WriteLine($"\nReverse dependencies (depends on {type} #{id}) ({rdeps.Count}):");
            foreach (var d in rdeps)
                Console.WriteLine($"  <- {d["source_type"]} #{d["source_id"]} [{d["dependency_kind"]}]");
        });

        // blocked
        var blockedCmd = new Command("blocked", "Show items blocked by unported dependencies");
        blockedCmd.SetAction(parseResult =>
        {
            var dbPath = parseResult.GetValue(dbOption)!;
            using var db = new Database(dbPath);

            var sql = @"
SELECT 'module' as item_type, m.id, m.name, m.status, d.target_type, d.target_id, d.dependency_kind
FROM modules m
JOIN dependencies d ON d.source_type = 'module' AND d.source_id = m.id
WHERE m.status NOT IN ('complete', 'verified', 'n_a')
AND (
    (d.target_type = 'module' AND d.target_id IN (SELECT id FROM modules WHERE status NOT IN ('complete', 'verified', 'n_a')))
    OR (d.target_type = 'feature' AND d.target_id IN (SELECT id FROM features WHERE status NOT IN ('complete', 'verified', 'n_a')))
    OR (d.target_type = 'unit_test' AND d.target_id IN (SELECT id FROM unit_tests WHERE status NOT IN ('complete', 'verified', 'n_a')))
)
UNION ALL
SELECT 'feature' as item_type, f.id, f.name, f.status, d.target_type, d.target_id, d.dependency_kind
FROM features f
JOIN dependencies d ON d.source_type = 'feature' AND d.source_id = f.id
WHERE f.status NOT IN ('complete', 'verified', 'n_a')
AND (
    (d.target_type = 'module' AND d.target_id IN (SELECT id FROM modules WHERE status NOT IN ('complete', 'verified', 'n_a')))
    OR (d.target_type = 'feature' AND d.target_id IN (SELECT id FROM features WHERE status NOT IN ('complete', 'verified', 'n_a')))
    OR (d.target_type = 'unit_test' AND d.target_id IN (SELECT id FROM unit_tests WHERE status NOT IN ('complete', 'verified', 'n_a')))
)
ORDER BY 1, 2";

            var rows = db.Query(sql);
            if (rows.Count == 0)
            {
                Console.WriteLine("No blocked items found.");
                return;
            }
            Console.WriteLine($"{"Type",-10} {"ID",-5} {"Name",-30} {"Status",-15} {"Blocked By",-15} {"Dep ID",-8} {"Kind",-10}");
            Console.WriteLine(new string('-', 93));
            foreach (var row in rows)
            {
                Console.WriteLine($"{row["item_type"],-10} {row["id"],-5} {Truncate(row["name"]?.ToString(), 29),-30} {row["status"],-15} {row["target_type"],-15} {row["target_id"],-8} {row["dependency_kind"],-10}");
            }
            Console.WriteLine($"\n{rows.Count} blocking relationships found.");
        });

        // ready
        var readyCmd = new Command("ready", "Show items ready to port (no unported dependencies)");
        readyCmd.SetAction(parseResult =>
        {
            var dbPath = parseResult.GetValue(dbOption)!;
            using var db = new Database(dbPath);

            var sql = @"
SELECT 'module' as item_type, m.id, m.name, m.status
FROM modules m
WHERE m.status IN ('not_started', 'stub')
AND NOT EXISTS (
    SELECT 1 FROM dependencies d
    WHERE d.source_type = 'module' AND d.source_id = m.id
    AND (
        (d.target_type = 'module' AND d.target_id IN (SELECT id FROM modules WHERE status NOT IN ('complete', 'verified', 'n_a')))
        OR (d.target_type = 'feature' AND d.target_id IN (SELECT id FROM features WHERE status NOT IN ('complete', 'verified', 'n_a')))
        OR (d.target_type = 'unit_test' AND d.target_id IN (SELECT id FROM unit_tests WHERE status NOT IN ('complete', 'verified', 'n_a')))
    )
)
UNION ALL
SELECT 'feature' as item_type, f.id, f.name, f.status
FROM features f
WHERE f.status IN ('not_started', 'stub')
AND NOT EXISTS (
    SELECT 1 FROM dependencies d
    WHERE d.source_type = 'feature' AND d.source_id = f.id
    AND (
        (d.target_type = 'module' AND d.target_id IN (SELECT id FROM modules WHERE status NOT IN ('complete', 'verified', 'n_a')))
        OR (d.target_type = 'feature' AND d.target_id IN (SELECT id FROM features WHERE status NOT IN ('complete', 'verified', 'n_a')))
        OR (d.target_type = 'unit_test' AND d.target_id IN (SELECT id FROM unit_tests WHERE status NOT IN ('complete', 'verified', 'n_a')))
    )
)
ORDER BY 1, 2";

            var rows = db.Query(sql);
            if (rows.Count == 0)
            {
                Console.WriteLine("No items are ready to port (all items either have unported deps or are already done).");
                return;
            }
            Console.WriteLine($"{"Type",-10} {"ID",-5} {"Name",-40} {"Status",-15}");
            Console.WriteLine(new string('-', 70));
            foreach (var row in rows)
            {
                Console.WriteLine($"{row["item_type"],-10} {row["id"],-5} {Truncate(row["name"]?.ToString(), 39),-40} {row["status"],-15}");
            }
            Console.WriteLine($"\n{rows.Count} items ready to port.");
        });

        depCommand.Add(showCmd);
        depCommand.Add(blockedCmd);
        depCommand.Add(readyCmd);

        return depCommand;
    }

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