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

namespace NatsNet.PortTracker.Commands;

public static class BatchFilters
{
    public static Option<string?> IdsOption() => new("--ids")
    {
        Description = "ID range: 100-200, 1,5,10, or mixed 1-5,10,20-25"
    };

    public static Option<int?> ModuleOption() => new("--module")
    {
        Description = "Filter by module ID"
    };

    public static Option<string?> StatusOption() => new("--status")
    {
        Description = "Filter by current status"
    };

    public static Option<bool> ExecuteOption() => new("--execute")
    {
        Description = "Actually apply changes (default is dry-run preview)",
        DefaultValueFactory = _ => false
    };

    public static void AddFilterOptions(Command cmd, bool includeModuleFilter)
    {
        cmd.Add(IdsOption());
        if (includeModuleFilter)
            cmd.Add(ModuleOption());
        cmd.Add(StatusOption());
        cmd.Add(ExecuteOption());
    }

    public static List<int> ParseIds(string? idsSpec)
    {
        if (string.IsNullOrWhiteSpace(idsSpec)) return [];

        var ids = new List<int>();
        foreach (var part in idsSpec.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
        {
            if (part.Contains('-'))
            {
                var range = part.Split('-', 2);
                if (int.TryParse(range[0], out var start) && int.TryParse(range[1], out var end))
                {
                    for (var i = start; i <= end; i++)
                        ids.Add(i);
                }
                else
                {
                    Console.WriteLine($"Warning: invalid range '{part}', skipping.");
                }
            }
            else if (int.TryParse(part, out var id))
            {
                ids.Add(id);
            }
            else
            {
                Console.WriteLine($"Warning: invalid ID '{part}', skipping.");
            }
        }
        return ids;
    }

    public static (string whereClause, List<(string name, object? value)> parameters) BuildWhereClause(
        string? idsSpec, int? moduleId, string? status, string idColumn = "id", string moduleColumn = "module_id")
    {
        var clauses = new List<string>();
        var parameters = new List<(string name, object? value)>();

        if (!string.IsNullOrWhiteSpace(idsSpec))
        {
            var ids = ParseIds(idsSpec);
            if (ids.Count > 0)
            {
                var placeholders = new List<string>();
                for (var i = 0; i < ids.Count; i++)
                {
                    placeholders.Add($"@id{i}");
                    parameters.Add(($"@id{i}", ids[i]));
                }
                clauses.Add($"{idColumn} IN ({string.Join(", ", placeholders)})");
            }
        }

        if (moduleId is not null)
        {
            clauses.Add($"{moduleColumn} = @moduleFilter");
            parameters.Add(("@moduleFilter", moduleId));
        }

        if (!string.IsNullOrWhiteSpace(status))
        {
            clauses.Add("status = @statusFilter");
            parameters.Add(("@statusFilter", status));
        }

        if (clauses.Count == 0)
            return ("", parameters);

        return (" WHERE " + string.Join(" AND ", clauses), parameters);
    }

    public static void PreviewOrExecute(
        Database db,
        string table,
        string displayColumns,
        string updateSetClause,
        List<(string name, object? value)> updateParams,
        string whereClause,
        List<(string name, object? value)> filterParams,
        bool execute)
    {
        // Count matching rows
        var countSql = $"SELECT COUNT(*) FROM {table}{whereClause}";
        var count = db.ExecuteScalar<long>(countSql, filterParams.ToArray());

        if (count == 0)
        {
            Console.WriteLine("No items match the specified filters.");
            return;
        }

        // Preview
        var previewSql = $"SELECT {displayColumns} FROM {table}{whereClause} ORDER BY id";
        var rows = db.Query(previewSql, filterParams.ToArray());

        if (!execute)
        {
            Console.WriteLine($"Would affect {count} items:");
            Console.WriteLine();
            PrintPreviewTable(rows);
            Console.WriteLine();
            Console.WriteLine("Add --execute to apply these changes.");
            return;
        }

        // Execute
        var allParams = new List<(string name, object? value)>();
        allParams.AddRange(updateParams);
        allParams.AddRange(filterParams);

        var updateSql = $"UPDATE {table} SET {updateSetClause}{whereClause}";
        var affected = db.ExecuteInTransaction(updateSql, allParams.ToArray());
        Console.WriteLine($"Updated {affected} items.");
    }

    private static void PrintPreviewTable(List<Dictionary<string, object?>> rows)
    {
        if (rows.Count == 0) return;

        var columns = rows[0].Keys.ToList();
        var widths = columns.Select(c => c.Length).ToList();

        foreach (var row in rows)
        {
            for (var i = 0; i < columns.Count; i++)
            {
                var val = row[columns[i]]?.ToString() ?? "";
                if (val.Length > widths[i]) widths[i] = Math.Min(val.Length, 40);
            }
        }

        // Header
        var header = string.Join(" ", columns.Select((c, i) => Truncate(c, widths[i]).PadRight(widths[i])));
        Console.WriteLine(header);
        Console.WriteLine(new string('-', header.Length));

        // Rows (cap at 50 for preview)
        var displayRows = rows.Take(50).ToList();
        foreach (var row in displayRows)
        {
            var line = string.Join(" ", columns.Select((c, i) =>
                Truncate(row[c]?.ToString() ?? "", widths[i]).PadRight(widths[i])));
            Console.WriteLine(line);
        }

        if (rows.Count > 50)
            Console.WriteLine($"  ... and {rows.Count - 50} more");
    }

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