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

namespace NatsNet.PortTracker.Commands;

public static class FeatureCommands
{
    public static Command Create(Option<string> dbOption, Option<string> schemaOption)
    {
        var featureCommand = new Command("feature", "Manage features");

        // 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 features");
        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 f.id, f.name, f.status, f.module_id, m.name as module_name, f.go_method, f.dotnet_method FROM features f LEFT JOIN modules m ON f.module_id = m.id";
            var parameters = new List<(string, object?)>();
            var clauses = new List<string>();
            if (moduleId is not null)
            {
                clauses.Add("f.module_id = @module");
                parameters.Add(("@module", moduleId));
            }
            if (status is not null)
            {
                clauses.Add("f.status = @status");
                parameters.Add(("@status", status));
            }
            if (clauses.Count > 0)
                sql += " WHERE " + string.Join(" AND ", clauses);
            sql += " ORDER BY m.name, f.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} features");
        });

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

            var deps = db.Query(
                "SELECT d.target_type, d.target_id, d.dependency_kind FROM dependencies d WHERE d.source_type = 'feature' 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"]}]");

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

        // update
        var updateId = new Argument<int>("id") { Description = "Feature ID (use 0 with --all-in-module)" };
        var updateStatus = new Option<string>("--status") { Description = "New status", Required = true };
        var updateAllInModule = new Option<int?>("--all-in-module") { Description = "Update all features in this module ID" };
        var updateOverride = new Option<string?>("--override") { Description = "Override audit mismatch with this comment" };
        var updateCmd = new Command("update", "Update feature status (audit-verified)");
        updateCmd.Add(updateId);
        updateCmd.Add(updateStatus);
        updateCmd.Add(updateAllInModule);
        updateCmd.Add(updateOverride);
        updateCmd.SetAction(parseResult =>
        {
            var dbPath = parseResult.GetValue(dbOption)!;
            var id = parseResult.GetValue(updateId);
            var status = parseResult.GetValue(updateStatus)!;
            var allInModule = parseResult.GetValue(updateAllInModule);
            var overrideComment = parseResult.GetValue(updateOverride);
            using var db = new Database(dbPath);

            var indexer = AuditVerifier.BuildIndexer("features");

            if (allInModule is not null)
            {
                var verifications = AuditVerifier.VerifyItems(db, indexer, "features",
                    " WHERE module_id = @module", [("@module", (object?)allInModule)], status);
                if (!AuditVerifier.CheckAndReport(verifications, status, overrideComment))
                    return;

                var affected = db.Execute(
                    "UPDATE features SET status = @status WHERE module_id = @module",
                    ("@status", status), ("@module", allInModule));
                Console.WriteLine($"Updated {affected} features in module {allInModule} to '{status}'.");

                var mismatches = verifications.Where(r => !r.Matches).ToList();
                if (mismatches.Count > 0 && overrideComment is not null)
                    AuditVerifier.LogOverrides(db, "features", mismatches, status, overrideComment);
            }
            else
            {
                var verifications = AuditVerifier.VerifyItems(db, indexer, "features",
                    " WHERE id = @id", [("@id", (object?)id)], status);
                if (verifications.Count == 0)
                {
                    Console.WriteLine($"Feature {id} not found.");
                    return;
                }
                if (!AuditVerifier.CheckAndReport(verifications, status, overrideComment))
                    return;

                var affected = db.Execute(
                    "UPDATE features SET status = @status WHERE id = @id",
                    ("@status", status), ("@id", id));
                Console.WriteLine(affected > 0 ? $"Feature {id} updated to '{status}'." : $"Feature {id} not found.");

                var mismatches = verifications.Where(r => !r.Matches).ToList();
                if (mismatches.Count > 0 && overrideComment is not null)
                    AuditVerifier.LogOverrides(db, "features", mismatches, status, overrideComment);
            }
        });

        // map
        var mapId = new Argument<int>("id") { Description = "Feature ID" };
        var mapProject = new Option<string?>("--project") { Description = "Target .NET project" };
        var mapClass = new Option<string?>("--class") { Description = "Target .NET class" };
        var mapMethod = new Option<string?>("--method") { Description = "Target .NET method" };
        var mapCmd = new Command("map", "Map feature to .NET 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 features 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 ? $"Feature {id} mapped." : $"Feature {id} not found.");
        });

        // set-na
        var naId = new Argument<int>("id") { Description = "Feature ID" };
        var naReason = new Option<string>("--reason") { Description = "Reason for N/A", Required = true };
        var naCmd = new Command("set-na", "Mark feature as N/A");
        naCmd.Add(naId);
        naCmd.Add(naReason);
        naCmd.SetAction(parseResult =>
        {
            var dbPath = parseResult.GetValue(dbOption)!;
            var id = parseResult.GetValue(naId);
            var reason = parseResult.GetValue(naReason)!;
            using var db = new Database(dbPath);
            var affected = db.Execute(
                "UPDATE features SET status = 'n_a', notes = @reason WHERE id = @id",
                ("@reason", reason), ("@id", id));
            Console.WriteLine(affected > 0 ? $"Feature {id} set to N/A: {reason}" : $"Feature {id} not found.");
        });

        featureCommand.Add(listCmd);
        featureCommand.Add(showCmd);
        featureCommand.Add(updateCmd);
        featureCommand.Add(mapCmd);
        featureCommand.Add(naCmd);
        featureCommand.Add(CreateBatchUpdate(dbOption));
        featureCommand.Add(CreateBatchMap(dbOption));

        return featureCommand;
    }

    private static Command CreateBatchUpdate(Option<string> dbOption)
    {
        var cmd = new Command("batch-update", "Bulk update feature status (audit-verified)");
        var idsOpt = BatchFilters.IdsOption();
        var moduleOpt = BatchFilters.ModuleOption();
        var statusOpt = BatchFilters.StatusOption();
        var executeOpt = BatchFilters.ExecuteOption();
        var setStatus = new Option<string>("--set-status") { Description = "New status to set", Required = true };
        var setNotes = new Option<string?>("--set-notes") { Description = "Notes to set" };
        var overrideOpt = new Option<string?>("--override") { Description = "Override audit mismatches with this comment" };

        cmd.Add(idsOpt);
        cmd.Add(moduleOpt);
        cmd.Add(statusOpt);
        cmd.Add(executeOpt);
        cmd.Add(setStatus);
        cmd.Add(setNotes);
        cmd.Add(overrideOpt);

        cmd.SetAction(parseResult =>
        {
            var dbPath = parseResult.GetValue(dbOption)!;
            var ids = parseResult.GetValue(idsOpt);
            var module = parseResult.GetValue(moduleOpt);
            var status = parseResult.GetValue(statusOpt);
            var execute = parseResult.GetValue(executeOpt);
            var newStatus = parseResult.GetValue(setStatus)!;
            var notes = parseResult.GetValue(setNotes);
            var overrideComment = parseResult.GetValue(overrideOpt);

            if (string.IsNullOrWhiteSpace(ids) && module is null && string.IsNullOrWhiteSpace(status))
            {
                Console.WriteLine("Error: at least one filter (--ids, --module, --status) is required.");
                return;
            }

            using var db = new Database(dbPath);
            var (whereClause, filterParams) = BatchFilters.BuildWhereClause(ids, module, status);

            // Audit verification
            var indexer = AuditVerifier.BuildIndexer("features");
            var verifications = AuditVerifier.VerifyItems(db, indexer, "features", whereClause, filterParams, newStatus);
            if (!AuditVerifier.CheckAndReport(verifications, newStatus, overrideComment))
                return;

            var setClauses = new List<string> { "status = @newStatus" };
            var updateParams = new List<(string, object?)> { ("@newStatus", newStatus) };
            if (notes is not null)
            {
                setClauses.Add("notes = @newNotes");
                updateParams.Add(("@newNotes", notes));
            }

            BatchFilters.PreviewOrExecute(db, "features",
                "id, name, status, module_id, notes",
                string.Join(", ", setClauses), updateParams,
                whereClause, filterParams, execute);

            // Log overrides after successful execute
            if (execute)
            {
                var mismatches = verifications.Where(r => !r.Matches).ToList();
                if (mismatches.Count > 0 && overrideComment is not null)
                    AuditVerifier.LogOverrides(db, "features", mismatches, newStatus, overrideComment);
            }
        });

        return cmd;
    }

    private static Command CreateBatchMap(Option<string> dbOption)
    {
        var cmd = new Command("batch-map", "Bulk map features to .NET methods");
        var idsOpt = BatchFilters.IdsOption();
        var moduleOpt = BatchFilters.ModuleOption();
        var statusOpt = BatchFilters.StatusOption();
        var executeOpt = BatchFilters.ExecuteOption();
        var setProject = new Option<string?>("--set-project") { Description = ".NET project" };
        var setClass = new Option<string?>("--set-class") { Description = ".NET class" };
        var setMethod = new Option<string?>("--set-method") { Description = ".NET method" };

        cmd.Add(idsOpt);
        cmd.Add(moduleOpt);
        cmd.Add(statusOpt);
        cmd.Add(executeOpt);
        cmd.Add(setProject);
        cmd.Add(setClass);
        cmd.Add(setMethod);

        cmd.SetAction(parseResult =>
        {
            var dbPath = parseResult.GetValue(dbOption)!;
            var ids = parseResult.GetValue(idsOpt);
            var module = parseResult.GetValue(moduleOpt);
            var status = parseResult.GetValue(statusOpt);
            var execute = parseResult.GetValue(executeOpt);
            var project = parseResult.GetValue(setProject);
            var cls = parseResult.GetValue(setClass);
            var method = parseResult.GetValue(setMethod);

            if (string.IsNullOrWhiteSpace(ids) && module is null && string.IsNullOrWhiteSpace(status))
            {
                Console.WriteLine("Error: at least one filter (--ids, --module, --status) is required.");
                return;
            }
            if (project is null && cls is null && method is null)
            {
                Console.WriteLine("Error: at least one of --set-project, --set-class, --set-method is required.");
                return;
            }

            using var db = new Database(dbPath);
            var (whereClause, filterParams) = BatchFilters.BuildWhereClause(ids, module, status);

            var setClauses = new List<string>();
            var updateParams = new List<(string, object?)>();
            if (project is not null) { setClauses.Add("dotnet_project = @setProject"); updateParams.Add(("@setProject", project)); }
            if (cls is not null) { setClauses.Add("dotnet_class = @setClass"); updateParams.Add(("@setClass", cls)); }
            if (method is not null) { setClauses.Add("dotnet_method = @setMethod"); updateParams.Add(("@setMethod", method)); }

            BatchFilters.PreviewOrExecute(db, "features",
                "id, name, status, dotnet_project, dotnet_class, dotnet_method",
                string.Join(", ", setClauses), updateParams,
                whereClause, filterParams, execute);
        });

        return cmd;
    }

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