using System.CommandLine;
using System.CommandLine.Parsing;
using ZB.MOM.WW.ScadaBridge.Commons.Messages.Management;

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

/// <summary>
/// CLI parity for the site-routed OPC UA data-connection operations that the
/// ManagementActor dispatches (arch-review C4): live address-space <c>browse</c> and
/// <c>search</c>, endpoint <c>verify-endpoint</c> probing, and server-certificate trust
/// management (<c>certs list|trust|remove</c>).
/// </summary>
/// <remarks>
/// These subcommands attach under the existing <c>data-connection</c> group. Every
/// command carries an explicit <c>--site</c> (mapped to the command record's
/// <c>SiteIdentifier</c>) so the management API can route it to the target site cluster
/// — the site side ignores it once routing has happened. <c>browse</c>/<c>search</c> and
/// <c>verify-endpoint</c> are Designer operations; <c>certs</c> are Admin operations,
/// enforced server-side by the ManagementActor.
/// </remarks>
public static class ConnectionBrowseCommands
{
    /// <summary>
    /// Attaches the browse / search / verify-endpoint / certs subcommands to the
    /// <c>data-connection</c> command group.
    /// </summary>
    /// <param name="dataConnection">The existing <c>data-connection</c> group to extend.</param>
    /// <param name="urlOption">Global management URL option.</param>
    /// <param name="formatOption">Global output format option.</param>
    /// <param name="usernameOption">Global username option.</param>
    /// <param name="passwordOption">Global password option.</param>
    /// <param name="commandInterceptor">
    /// Test seam: when supplied, the built command record is handed to this delegate
    /// (which returns the process exit code) instead of being sent over HTTP. Lets parse
    /// tests assert the exact record produced from a given command line without a live
    /// management server. Null in production (Program.cs), where the real HTTP path runs.
    /// </param>
    public static void AddTo(
        Command dataConnection,
        Option<string> urlOption,
        Option<string> formatOption,
        Option<string> usernameOption,
        Option<string> passwordOption,
        Func<object, int>? commandInterceptor = null)
    {
        dataConnection.Add(BuildBrowse(urlOption, formatOption, usernameOption, passwordOption, commandInterceptor));
        dataConnection.Add(BuildSearch(urlOption, formatOption, usernameOption, passwordOption, commandInterceptor));
        dataConnection.Add(BuildVerifyEndpoint(urlOption, formatOption, usernameOption, passwordOption, commandInterceptor));
        dataConnection.Add(BuildCerts(urlOption, formatOption, usernameOption, passwordOption, commandInterceptor));
    }

    /// <summary>
    /// Sends <paramref name="command"/> to the management API, or — when a test
    /// interceptor is present — hands it to the interceptor instead.
    /// </summary>
    private static Task<int> DispatchAsync(
        ParseResult result,
        Option<string> urlOption,
        Option<string> formatOption,
        Option<string> usernameOption,
        Option<string> passwordOption,
        object command,
        Func<object, int>? commandInterceptor)
    {
        if (commandInterceptor is not null)
            return Task.FromResult(commandInterceptor(command));

        return CommandHelpers.ExecuteCommandAsync(
            result, urlOption, formatOption, usernameOption, passwordOption, command);
    }

    private static Command BuildBrowse(Option<string> urlOption, Option<string> formatOption, Option<string> usernameOption, Option<string> passwordOption, Func<object, int>? interceptor)
    {
        var siteOption = new Option<string>("--site") { Description = "Target site identifier", Required = true };
        var connectionOption = new Option<string>("--connection") { Description = "Data connection name", Required = true };
        var nodeIdOption = new Option<string?>("--node-id") { Description = "Parent node id to browse (server root / ObjectsFolder if omitted)" };

        var cmd = new Command("browse") { Description = "Browse the immediate children of an OPC UA node on a site data connection" };
        cmd.Add(siteOption);
        cmd.Add(connectionOption);
        cmd.Add(nodeIdOption);
        cmd.SetAction(async (ParseResult result) =>
        {
            var site = result.GetValue(siteOption)!;
            var connection = result.GetValue(connectionOption)!;
            var nodeId = result.GetValue(nodeIdOption);
            return await DispatchAsync(result, urlOption, formatOption, usernameOption, passwordOption,
                new BrowseNodeCommand(connection, nodeId, null, site), interceptor);
        });
        return cmd;
    }

    private static Command BuildSearch(Option<string> urlOption, Option<string> formatOption, Option<string> usernameOption, Option<string> passwordOption, Func<object, int>? interceptor)
    {
        var siteOption = new Option<string>("--site") { Description = "Target site identifier", Required = true };
        var connectionOption = new Option<string>("--connection") { Description = "Data connection name", Required = true };
        var queryOption = new Option<string>("--query") { Description = "Case-insensitive substring matched on DisplayName and root-relative path", Required = true };
        var maxDepthOption = new Option<int>("--max-depth") { Description = "Maximum levels below the root to descend", DefaultValueFactory = _ => 5 };
        var maxResultsOption = new Option<int>("--max-results") { Description = "Maximum number of matches to return", DefaultValueFactory = _ => 100 };

        var cmd = new Command("search") { Description = "Bounded recursive search of an OPC UA address space on a site data connection" };
        cmd.Add(siteOption);
        cmd.Add(connectionOption);
        cmd.Add(queryOption);
        cmd.Add(maxDepthOption);
        cmd.Add(maxResultsOption);
        cmd.SetAction(async (ParseResult result) =>
        {
            var site = result.GetValue(siteOption)!;
            var connection = result.GetValue(connectionOption)!;
            var query = result.GetValue(queryOption)!;
            var maxDepth = result.GetValue(maxDepthOption);
            var maxResults = result.GetValue(maxResultsOption);
            return await DispatchAsync(result, urlOption, formatOption, usernameOption, passwordOption,
                new SearchAddressSpaceCommand(connection, query, maxDepth, maxResults, site), interceptor);
        });
        return cmd;
    }

    private static Command BuildVerifyEndpoint(Option<string> urlOption, Option<string> formatOption, Option<string> usernameOption, Option<string> passwordOption, Func<object, int>? interceptor)
    {
        var siteOption = new Option<string>("--site") { Description = "Target site identifier", Required = true };
        var protocolOption = new Option<string>("--protocol") { Description = "Protocol type (e.g. OpcUa)", Required = true };
        var configFileOption = new Option<string>("--config-file") { Description = "Path to the endpoint configuration JSON file (read verbatim into ConfigJson)", Required = true };
        // Correlation only — a verify probe runs against an endpoint config that may not
        // yet correspond to a saved connection, so the name is optional.
        var connectionOption = new Option<string?>("--connection") { Description = "Connection name for logging/correlation (optional)" };

        var cmd = new Command("verify-endpoint") { Description = "Probe a data-connection endpoint config on a site without persisting it (read-only; never trusts the server cert)" };
        cmd.Add(siteOption);
        cmd.Add(protocolOption);
        cmd.Add(configFileOption);
        cmd.Add(connectionOption);
        cmd.SetAction(async (ParseResult result) =>
        {
            var site = result.GetValue(siteOption)!;
            var protocol = result.GetValue(protocolOption)!;
            var configFile = result.GetValue(configFileOption)!;
            var connection = result.GetValue(connectionOption);
            var configJson = await File.ReadAllTextAsync(configFile);
            return await DispatchAsync(result, urlOption, formatOption, usernameOption, passwordOption,
                new VerifyEndpointCommand(connection ?? string.Empty, protocol, configJson, site), interceptor);
        });
        return cmd;
    }

    private static Command BuildCerts(Option<string> urlOption, Option<string> formatOption, Option<string> usernameOption, Option<string> passwordOption, Func<object, int>? interceptor)
    {
        var group = new Command("certs") { Description = "Manage trusted OPC UA server certificates for a site" };
        group.Add(BuildCertsList(urlOption, formatOption, usernameOption, passwordOption, interceptor));
        group.Add(BuildCertsTrust(urlOption, formatOption, usernameOption, passwordOption, interceptor));
        group.Add(BuildCertsRemove(urlOption, formatOption, usernameOption, passwordOption, interceptor));
        return group;
    }

    private static Command BuildCertsList(Option<string> urlOption, Option<string> formatOption, Option<string> usernameOption, Option<string> passwordOption, Func<object, int>? interceptor)
    {
        var siteOption = new Option<string>("--site") { Description = "Target site identifier", Required = true };
        // Contextual only — the trusted-peer PKI store is site-scoped (not per-connection),
        // so the connection is not part of the ListServerCertsCommand payload. Accepted for
        // symmetry with the connection-scoped UI navigation.
        var connectionOption = new Option<string?>("--connection") { Description = "Data connection name (contextual; not part of the request)" };

        var cmd = new Command("list") { Description = "List the certificates in a site's trusted-peer and rejected PKI stores" };
        cmd.Add(siteOption);
        cmd.Add(connectionOption);
        cmd.SetAction(async (ParseResult result) =>
        {
            var site = result.GetValue(siteOption)!;
            return await DispatchAsync(result, urlOption, formatOption, usernameOption, passwordOption,
                new ListServerCertsCommand(site), interceptor);
        });
        return cmd;
    }

    private static Command BuildCertsTrust(Option<string> urlOption, Option<string> formatOption, Option<string> usernameOption, Option<string> passwordOption, Func<object, int>? interceptor)
    {
        var siteOption = new Option<string>("--site") { Description = "Target site identifier", Required = true };
        var connectionOption = new Option<string>("--connection") { Description = "Data connection the certificate was captured from (correlation)", Required = true };
        var derFileOption = new Option<string>("--der-file") { Description = "Path to the DER-encoded certificate file (base64-encoded into the request)", Required = true };
        var thumbprintOption = new Option<string>("--thumbprint") { Description = "Certificate thumbprint (used as the store filename key)", Required = true };

        var cmd = new Command("trust") { Description = "Trust an OPC UA server certificate on every node of a site" };
        cmd.Add(siteOption);
        cmd.Add(connectionOption);
        cmd.Add(derFileOption);
        cmd.Add(thumbprintOption);
        cmd.SetAction(async (ParseResult result) =>
        {
            var site = result.GetValue(siteOption)!;
            var connection = result.GetValue(connectionOption)!;
            var derFile = result.GetValue(derFileOption)!;
            var thumbprint = result.GetValue(thumbprintOption)!;
            var derBase64 = Convert.ToBase64String(await File.ReadAllBytesAsync(derFile));
            return await DispatchAsync(result, urlOption, formatOption, usernameOption, passwordOption,
                new TrustServerCertCommand(connection, derBase64, thumbprint, site), interceptor);
        });
        return cmd;
    }

    private static Command BuildCertsRemove(Option<string> urlOption, Option<string> formatOption, Option<string> usernameOption, Option<string> passwordOption, Func<object, int>? interceptor)
    {
        var siteOption = new Option<string>("--site") { Description = "Target site identifier", Required = true };
        var thumbprintOption = new Option<string>("--thumbprint") { Description = "Thumbprint of the certificate to remove", Required = true };
        // Contextual only — RemoveServerCertCommand is keyed by thumbprint + site, not connection.
        var connectionOption = new Option<string?>("--connection") { Description = "Data connection name (contextual; not part of the request)" };

        var cmd = new Command("remove") { Description = "Remove a trusted OPC UA server certificate from every node of a site" };
        cmd.Add(siteOption);
        cmd.Add(thumbprintOption);
        cmd.Add(connectionOption);
        cmd.SetAction(async (ParseResult result) =>
        {
            var site = result.GetValue(siteOption)!;
            var thumbprint = result.GetValue(thumbprintOption)!;
            return await DispatchAsync(result, urlOption, formatOption, usernameOption, passwordOption,
                new RemoveServerCertCommand(thumbprint, site), interceptor);
        });
        return cmd;
    }
}
