using System.CommandLine;
using System.CommandLine.Parsing;

namespace ZB.MOM.WW.ScadaBridge.CLI.Commands;

/// <summary>
/// The <c>scadabridge audit</c> command group. Provides read access to
/// the centralized append-only Audit Log via the REST endpoints
/// (<c>GET /api/audit/query</c>, <c>GET /api/audit/export</c>,
/// <c>GET /api/audit/tree</c>), plus a v1 no-op <c>verify-chain</c> placeholder
/// for the deferred hash-chain tamper-evidence feature.
/// </summary>
public static class AuditCommands
{
    /// <summary>
    /// Builds the <c>audit</c> command group with query, export, tree, and verify-chain
    /// sub-commands.
    /// </summary>
    /// <param name="urlOption">Global <c>--url</c> option for the management API endpoint.</param>
    /// <param name="formatOption">Global <c>--format</c> option for output format.</param>
    /// <param name="usernameOption">Global <c>--username</c> option for authentication.</param>
    /// <param name="passwordOption">Global <c>--password</c> option for authentication.</param>
    /// <returns>The configured <c>audit</c> <see cref="Command"/> with all sub-commands attached.</returns>
    public static Command Build(Option<string> urlOption, Option<string> formatOption, Option<string> usernameOption, Option<string> passwordOption)
    {
        var command = new Command("audit") { Description = "Query and export the centralized audit log" };

        command.Add(BuildQuery(urlOption, formatOption, usernameOption, passwordOption));
        command.Add(BuildExport(urlOption, formatOption, usernameOption, passwordOption));
        command.Add(BuildTree(urlOption, formatOption, usernameOption, passwordOption));
        command.Add(BuildVerifyChain(urlOption, formatOption, usernameOption, passwordOption));
        command.Add(BuildBackfillSourceNode(urlOption, formatOption, usernameOption, passwordOption));

        return command;
    }

    private static Command BuildQuery(Option<string> urlOption, Option<string> formatOption, Option<string> usernameOption, Option<string> passwordOption)
    {
        var sinceOption = new Option<string?>("--since") { Description = "Start time: relative (1h, 24h, 7d) or ISO-8601" };
        var untilOption = new Option<string?>("--until") { Description = "End time: relative (1h, 24h, 7d) or ISO-8601" };
        // --channel/--kind/--status/--site are multi-valued: System.CommandLine accepts
        // both repeated tokens (--channel A --channel B) and, with
        // AllowMultipleArgumentsPerToken, a single token carrying several values
        // (--channel A B). AcceptOnlyFromAmong validates EACH supplied value.
        var channelOption = new Option<string[]>("--channel")
        {
            Description = "Filter by channel (ApiOutbound, DbOutbound, Notification, ApiInbound); repeatable",
            AllowMultipleArgumentsPerToken = true,
        };
        channelOption.AcceptOnlyFromAmong("ApiOutbound", "DbOutbound", "Notification", "ApiInbound");
        var kindOption = new Option<string[]>("--kind")
        {
            Description = "Filter by event kind (ApiCall, ApiCallCached, DbWrite, DbWriteCached, NotifySend, NotifyDeliver, InboundRequest, InboundAuthFailure, CachedSubmit, CachedResolve); repeatable",
            AllowMultipleArgumentsPerToken = true,
        };
        kindOption.AcceptOnlyFromAmong(
            "ApiCall", "ApiCallCached", "DbWrite", "DbWriteCached", "NotifySend",
            "NotifyDeliver", "InboundRequest", "InboundAuthFailure", "CachedSubmit", "CachedResolve");
        var statusOption = new Option<string[]>("--status")
        {
            Description = "Filter by status (Submitted, Forwarded, Attempted, Delivered, Failed, Parked, Discarded, Skipped); repeatable",
            AllowMultipleArgumentsPerToken = true,
        };
        statusOption.AcceptOnlyFromAmong(
            "Submitted", "Forwarded", "Attempted", "Delivered", "Failed", "Parked", "Discarded", "Skipped");
        var siteOption = new Option<string[]>("--site")
        {
            Description = "Filter by source site ID; repeatable",
            AllowMultipleArgumentsPerToken = true,
        };
        var targetOption = new Option<string?>("--target") { Description = "Filter by target (external system, DB connection, notification list)" };
        var actorOption = new Option<string?>("--actor") { Description = "Filter by actor" };
        var correlationIdOption = new Option<string?>("--correlation-id") { Description = "Filter by correlation ID" };
        var executionIdOption = new Option<string?>("--execution-id") { Description = "Filter by execution ID" };
        var parentExecutionIdOption = new Option<string?>("--parent-execution-id") { Description = "Filter by parent execution ID" };
        var errorsOnlyOption = new Option<bool>("--errors-only") { Description = "Show only failed events (status=Failed; overrides --status)" };
        var pageSizeOption = new Option<int>("--page-size") { Description = "Events per page (1-1000)" };
        pageSizeOption.DefaultValueFactory = _ => 100;
        var allOption = new Option<bool>("--all") { Description = "Fetch every page, following the keyset cursor" };

        var cmd = new Command("query") { Description = "Query audit log events" };
        cmd.Add(sinceOption);
        cmd.Add(untilOption);
        cmd.Add(channelOption);
        cmd.Add(kindOption);
        cmd.Add(statusOption);
        cmd.Add(siteOption);
        cmd.Add(targetOption);
        cmd.Add(actorOption);
        cmd.Add(correlationIdOption);
        cmd.Add(executionIdOption);
        cmd.Add(parentExecutionIdOption);
        cmd.Add(errorsOnlyOption);
        cmd.Add(pageSizeOption);
        cmd.Add(allOption);

        cmd.SetAction(async (ParseResult result) =>
        {
            var connection = AuditCommandHelpers.ResolveConnection(result, urlOption, usernameOption, passwordOption);
            if (connection.Error != null)
            {
                OutputFormatter.WriteError(connection.Error, connection.ErrorCode!);
                return 1;
            }

            var format = AuditCommandHelpers.ResolveFormat(result, formatOption);
            var formatter = AuditFormatterFactory.Create(format, Console.Error);

            var args = new AuditQueryArgs
            {
                Since = result.GetValue(sinceOption),
                Until = result.GetValue(untilOption),
                Channel = result.GetValue(channelOption) ?? Array.Empty<string>(),
                Kind = result.GetValue(kindOption) ?? Array.Empty<string>(),
                Status = result.GetValue(statusOption) ?? Array.Empty<string>(),
                Site = result.GetValue(siteOption) ?? Array.Empty<string>(),
                Target = result.GetValue(targetOption),
                Actor = result.GetValue(actorOption),
                CorrelationId = result.GetValue(correlationIdOption),
                ExecutionId = result.GetValue(executionIdOption),
                ParentExecutionId = result.GetValue(parentExecutionIdOption),
                ErrorsOnly = result.GetValue(errorsOnlyOption),
                PageSize = result.GetValue(pageSizeOption),
            };
            var fetchAll = result.GetValue(allOption);

            try
            {
                using var client = new ManagementHttpClient(connection.Url!, connection.Username!, connection.Password!);
                return await AuditQueryHelpers.RunQueryAsync(
                    client, args, fetchAll, formatter, Console.Out, DateTimeOffset.UtcNow);
            }
            catch (FormatException ex)
            {
                OutputFormatter.WriteError(ex.Message, "INVALID_ARGUMENT");
                return 1;
            }
        });
        return cmd;
    }

    private static Command BuildExport(Option<string> urlOption, Option<string> formatOption, Option<string> usernameOption, Option<string> passwordOption)
    {
        var sinceOption = new Option<string>("--since") { Description = "Start time: relative (1h, 24h, 7d) or ISO-8601", Required = true };
        var untilOption = new Option<string>("--until") { Description = "End time: relative (1h, 24h, 7d) or ISO-8601", Required = true };
        var formatExportOption = new Option<string>("--format") { Description = "Export format", Required = true };
        formatExportOption.AcceptOnlyFromAmong("csv", "jsonl", "parquet");
        var outputOption = new Option<string>("--output") { Description = "Destination file path", Required = true };
        // --channel/--kind/--status/--site are multi-valued — same shape as the
        // `query` subcommand: repeated tokens (--channel A --channel B) and, with
        // AllowMultipleArgumentsPerToken, a single token carrying several values
        // (--channel A B). AcceptOnlyFromAmong validates EACH supplied value.
        var channelOption = new Option<string[]>("--channel")
        {
            Description = "Filter by channel (ApiOutbound, DbOutbound, Notification, ApiInbound); repeatable",
            AllowMultipleArgumentsPerToken = true,
        };
        channelOption.AcceptOnlyFromAmong("ApiOutbound", "DbOutbound", "Notification", "ApiInbound");
        var kindOption = new Option<string[]>("--kind")
        {
            Description = "Filter by event kind (ApiCall, ApiCallCached, DbWrite, DbWriteCached, NotifySend, NotifyDeliver, InboundRequest, InboundAuthFailure, CachedSubmit, CachedResolve); repeatable",
            AllowMultipleArgumentsPerToken = true,
        };
        kindOption.AcceptOnlyFromAmong(
            "ApiCall", "ApiCallCached", "DbWrite", "DbWriteCached", "NotifySend",
            "NotifyDeliver", "InboundRequest", "InboundAuthFailure", "CachedSubmit", "CachedResolve");
        var statusOption = new Option<string[]>("--status")
        {
            Description = "Filter by status (Submitted, Forwarded, Attempted, Delivered, Failed, Parked, Discarded, Skipped); repeatable",
            AllowMultipleArgumentsPerToken = true,
        };
        statusOption.AcceptOnlyFromAmong(
            "Submitted", "Forwarded", "Attempted", "Delivered", "Failed", "Parked", "Discarded", "Skipped");
        var siteOption = new Option<string[]>("--site")
        {
            Description = "Filter by source site ID; repeatable",
            AllowMultipleArgumentsPerToken = true,
        };
        var targetOption = new Option<string?>("--target") { Description = "Filter by target" };
        var actorOption = new Option<string?>("--actor") { Description = "Filter by actor" };

        var cmd = new Command("export") { Description = "Export audit log events to a file" };
        cmd.Add(sinceOption);
        cmd.Add(untilOption);
        cmd.Add(formatExportOption);
        cmd.Add(outputOption);
        cmd.Add(channelOption);
        cmd.Add(kindOption);
        cmd.Add(statusOption);
        cmd.Add(siteOption);
        cmd.Add(targetOption);
        cmd.Add(actorOption);

        cmd.SetAction(async (ParseResult result) =>
        {
            var connection = AuditCommandHelpers.ResolveConnection(result, urlOption, usernameOption, passwordOption);
            if (connection.Error != null)
            {
                OutputFormatter.WriteError(connection.Error, connection.ErrorCode!);
                return 1;
            }

            var args = new AuditExportArgs
            {
                Since = result.GetValue(sinceOption)!,
                Until = result.GetValue(untilOption)!,
                Format = result.GetValue(formatExportOption)!,
                Output = result.GetValue(outputOption)!,
                Channel = result.GetValue(channelOption) ?? Array.Empty<string>(),
                Kind = result.GetValue(kindOption) ?? Array.Empty<string>(),
                Status = result.GetValue(statusOption) ?? Array.Empty<string>(),
                Site = result.GetValue(siteOption) ?? Array.Empty<string>(),
                Target = result.GetValue(targetOption),
                Actor = result.GetValue(actorOption),
            };

            try
            {
                using var client = new ManagementHttpClient(connection.Url!, connection.Username!, connection.Password!);
                return await AuditExportHelpers.RunExportAsync(client, args, Console.Out, DateTimeOffset.UtcNow);
            }
            catch (FormatException ex)
            {
                OutputFormatter.WriteError(ex.Message, "INVALID_ARGUMENT");
                return 1;
            }
        });
        return cmd;
    }

    private static Command BuildTree(Option<string> urlOption, Option<string> formatOption, Option<string> usernameOption, Option<string> passwordOption)
    {
        var executionIdOption = new Option<string>("--execution-id")
        {
            Description = "Execution ID (GUID) to look up — may be any node in the chain",
            Required = true,
        };

        var cmd = new Command("tree") { Description = "Display the full execution-chain tree for an audit execution" };
        cmd.Add(executionIdOption);

        cmd.SetAction(async (ParseResult result) =>
        {
            var connection = AuditCommandHelpers.ResolveConnection(result, urlOption, usernameOption, passwordOption);
            if (connection.Error != null)
            {
                OutputFormatter.WriteError(connection.Error, connection.ErrorCode!);
                return 1;
            }

            var rawId = result.GetValue(executionIdOption);
            if (!Guid.TryParse(rawId, out var executionId))
            {
                OutputFormatter.WriteError(
                    $"Invalid execution ID '{rawId}'. Expected a GUID (e.g. 11111111-1111-1111-1111-111111111111).",
                    "INVALID_ARGUMENT");
                return 1;
            }

            var format = AuditCommandHelpers.ResolveFormat(result, formatOption);

            using var client = new ManagementHttpClient(connection.Url!, connection.Username!, connection.Password!);
            return await AuditTreeHelpers.RunTreeAsync(client, executionId, format, Console.Out);
        });

        return cmd;
    }

    private static Command BuildVerifyChain(Option<string> urlOption, Option<string> formatOption, Option<string> usernameOption, Option<string> passwordOption)
    {
        var monthOption = new Option<string>("--month") { Description = "Month to verify (YYYY-MM)", Required = true };

        var cmd = new Command("verify-chain") { Description = "Verify the audit log hash chain for a month" };
        cmd.Add(monthOption);
        cmd.SetAction((ParseResult result) =>
        {
            var month = result.GetValue(monthOption)!;
            if (!AuditVerifyChainHelpers.IsValidMonth(month))
            {
                OutputFormatter.WriteError(
                    $"Invalid month '{month}'. Expected YYYY-MM (e.g. 2026-05).", "INVALID_ARGUMENT");
                return 1;
            }

            Console.WriteLine(
                "Hash-chain tamper-evidence is not enabled in this release. "
                + "See Component-AuditLog.md (Security & Tamper-Evidence) for the v1.x roadmap.");
            return 0;
        });
        return cmd;
    }

    /// <summary>
    /// Builds the <c>audit backfill-source-node</c> sub-command.
    /// Sets <c>SourceNode</c> on historical pre-feature rows whose <c>SourceNode IS NULL</c>
    /// and <c>OccurredAtUtc</c> is older than <c>--before</c>, in batches. Admin-only.
    /// </summary>
    private static Command BuildBackfillSourceNode(Option<string> urlOption, Option<string> formatOption, Option<string> usernameOption, Option<string> passwordOption)
    {
        var sentinelOption = new Option<string>("--sentinel")
        {
            Description = "Value to write for pre-feature rows whose node-of-origin is unknown (default: unknown)",
        };
        sentinelOption.DefaultValueFactory = _ => "unknown";

        var beforeOption = new Option<string>("--before")
        {
            Description = "ISO-8601 UTC datetime; only rows older than this date are eligible (required)",
            Required = true,
        };

        var batchOption = new Option<int>("--batch")
        {
            Description = "Max rows updated per batch (default: 5000)",
        };
        batchOption.DefaultValueFactory = _ => 5000;

        var cmd = new Command("backfill-source-node")
        {
            Description = "Set SourceNode to a sentinel value on pre-feature rows where it is NULL (admin-only, maintenance path)",
        };
        cmd.Add(sentinelOption);
        cmd.Add(beforeOption);
        cmd.Add(batchOption);

        cmd.SetAction(async (ParseResult result) =>
        {
            var connection = AuditCommandHelpers.ResolveConnection(result, urlOption, usernameOption, passwordOption);
            if (connection.Error != null)
            {
                OutputFormatter.WriteError(connection.Error, connection.ErrorCode!);
                return 1;
            }

            var sentinel = result.GetValue(sentinelOption) ?? "unknown";
            var before = result.GetValue(beforeOption)!;
            var batch = result.GetValue(batchOption);

            if (string.IsNullOrWhiteSpace(sentinel))
            {
                OutputFormatter.WriteError("--sentinel must be a non-empty string.", "INVALID_ARGUMENT");
                return 1;
            }

            if (batch <= 0)
            {
                OutputFormatter.WriteError("--batch must be > 0.", "INVALID_ARGUMENT");
                return 1;
            }

            var args = new AuditBackfillSourceNodeArgs
            {
                Sentinel = sentinel,
                Before = before,
                BatchSize = batch,
            };

            using var client = new ManagementHttpClient(connection.Url!, connection.Username!, connection.Password!);
            return await AuditBackfillHelpers.RunBackfillAsync(client, args, Console.Out);
        });

        return cmd;
    }
}
