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

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

public static class SecurityCommands
{
    /// <summary>
    /// Builds the <c>security</c> command group with API key, role mapping, and scope rule subcommands.
    /// </summary>
    /// <param name="urlOption">Shared management URL option.</param>
    /// <param name="formatOption">Shared output format option.</param>
    /// <param name="usernameOption">Shared username option for authentication.</param>
    /// <param name="passwordOption">Shared password option for authentication.</param>
    /// <returns>The configured <c>security</c> command with all subcommands attached.</returns>
    public static Command Build(Option<string> urlOption, Option<string> formatOption, Option<string> usernameOption, Option<string> passwordOption)
    {
        var command = new Command("security") { Description = "Manage security settings" };

        command.Add(BuildApiKey(urlOption, formatOption, usernameOption, passwordOption));
        command.Add(BuildRoleMapping(urlOption, formatOption, usernameOption, passwordOption));
        command.Add(BuildScopeRule(urlOption, formatOption, usernameOption, passwordOption));

        return command;
    }

    private static Command BuildApiKey(Option<string> urlOption, Option<string> formatOption, Option<string> usernameOption, Option<string> passwordOption)
    {
        var group = new Command("api-key") { Description = "Manage API keys" };

        var listCmd = new Command("list") { Description = "List all API keys" };
        listCmd.SetAction(async (ParseResult result) =>
        {
            return await CommandHelpers.ExecuteCommandAsync(
                result, urlOption, formatOption, usernameOption, passwordOption, new ListApiKeysCommand());
        });
        group.Add(listCmd);

        var nameOption = new Option<string>("--name") { Description = "API key name", Required = true };
        var createMethodsOption = new Option<string>("--methods")
        {
            Description = "Comma-separated API method names this key may call (e.g. \"MethodA,MethodB\")",
            Required = true
        };
        var createCmd = new Command("create") { Description = "Create an API key" };
        createCmd.Add(nameOption);
        createCmd.Add(createMethodsOption);
        createCmd.SetAction(async (ParseResult result) =>
        {
            var name = result.GetValue(nameOption)!;
            var methods = ParseMethods(result.GetValue(createMethodsOption));
            return await CommandHelpers.ExecuteCommandAsync(
                result, urlOption, formatOption, usernameOption, passwordOption,
                new CreateApiKeyCommand(name, methods),
                onSuccess: PrintCreatedKey);
        });
        group.Add(createCmd);

        var deleteKeyIdOption = new Option<string>("--key-id") { Description = "API key ID", Required = true };
        var deleteCmd = new Command("delete") { Description = "Delete an API key" };
        deleteCmd.Add(deleteKeyIdOption);
        deleteCmd.SetAction(async (ParseResult result) =>
        {
            var keyId = result.GetValue(deleteKeyIdOption)!;
            return await CommandHelpers.ExecuteCommandAsync(
                result, urlOption, formatOption, usernameOption, passwordOption, new DeleteApiKeyCommand(keyId));
        });
        group.Add(deleteCmd);

        var updateKeyIdOption = new Option<string>("--key-id") { Description = "API key ID", Required = true };
        var enabledOption = new Option<bool>("--enabled") { Description = "Enable or disable", Required = true };
        var updateCmd = new Command("update") { Description = "Enable or disable an API key" };
        updateCmd.Add(updateKeyIdOption);
        updateCmd.Add(enabledOption);
        updateCmd.SetAction(async (ParseResult result) =>
        {
            var keyId = result.GetValue(updateKeyIdOption)!;
            var enabled = result.GetValue(enabledOption);
            return await CommandHelpers.ExecuteCommandAsync(
                result, urlOption, formatOption, usernameOption, passwordOption, new UpdateApiKeyCommand(keyId, enabled));
        });
        group.Add(updateCmd);

        var setMethodsKeyIdOption = new Option<string>("--key-id") { Description = "API key ID", Required = true };
        var setMethodsOption = new Option<string>("--methods")
        {
            Description = "Comma-separated API method names this key may call (replaces the existing set)",
            Required = true
        };
        var setMethodsCmd = new Command("set-methods") { Description = "Replace the method-scopes on an API key" };
        setMethodsCmd.Add(setMethodsKeyIdOption);
        setMethodsCmd.Add(setMethodsOption);
        setMethodsCmd.SetAction(async (ParseResult result) =>
        {
            var keyId = result.GetValue(setMethodsKeyIdOption)!;
            var methods = ParseMethods(result.GetValue(setMethodsOption));
            return await CommandHelpers.ExecuteCommandAsync(
                result, urlOption, formatOption, usernameOption, passwordOption,
                new SetApiKeyMethodsCommand(keyId, methods));
        });
        group.Add(setMethodsCmd);

        return group;
    }

    /// <summary>
    /// Splits a comma-separated <c>--methods</c> value into a trimmed, non-empty list of
    /// method names. A null/empty value yields an empty list (the server rejects an empty
    /// scope set if its rules require one).
    /// </summary>
    /// <param name="raw">The raw delimited option value.</param>
    private static IReadOnlyList<string> ParseMethods(string? raw)
    {
        if (string.IsNullOrWhiteSpace(raw))
            return Array.Empty<string>();

        return raw
            .Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
            .ToArray();
    }

    /// <summary>
    /// Renders the create-key response, surfacing the one-time bearer token prominently —
    /// it is the only moment the secret is available and cannot be retrieved afterwards.
    /// The advisory line is written to stderr so that piping stdout captures only the token.
    /// </summary>
    /// <param name="json">The JSON success body returned by the management API.</param>
    /// <returns>Exit code 0.</returns>
    internal static int PrintCreatedKey(string json)
    {
        using var doc = System.Text.Json.JsonDocument.Parse(json);
        var root = doc.RootElement;
        var keyId = root.TryGetProperty("keyId", out var k) ? k.GetString() : null;
        var token = root.TryGetProperty("token", out var t) ? t.GetString() : null;

        Console.WriteLine($"API key created. KeyId: {keyId}");
        Console.WriteLine();
        Console.Error.WriteLine("Save this token now — it will not be shown again:");
        Console.WriteLine($"  {token}");
        return 0;
    }

    private static Command BuildRoleMapping(Option<string> urlOption, Option<string> formatOption, Option<string> usernameOption, Option<string> passwordOption)
    {
        var group = new Command("role-mapping") { Description = "Manage LDAP role mappings" };

        var listCmd = new Command("list") { Description = "List all role mappings" };
        listCmd.SetAction(async (ParseResult result) =>
        {
            return await CommandHelpers.ExecuteCommandAsync(
                result, urlOption, formatOption, usernameOption, passwordOption, new ListRoleMappingsCommand());
        });
        group.Add(listCmd);

        var ldapGroupOption = new Option<string>("--ldap-group") { Description = "LDAP group name", Required = true };
        var roleOption = new Option<string>("--role") { Description = "Role name", Required = true };
        var createCmd = new Command("create") { Description = "Create a role mapping" };
        createCmd.Add(ldapGroupOption);
        createCmd.Add(roleOption);
        createCmd.SetAction(async (ParseResult result) =>
        {
            var ldapGroup = result.GetValue(ldapGroupOption)!;
            var role = result.GetValue(roleOption)!;
            return await CommandHelpers.ExecuteCommandAsync(
                result, urlOption, formatOption, usernameOption, passwordOption,
                new CreateRoleMappingCommand(ldapGroup, role));
        });
        group.Add(createCmd);

        var idOption = new Option<int>("--id") { Description = "Mapping ID", Required = true };
        var deleteCmd = new Command("delete") { Description = "Delete a role mapping" };
        deleteCmd.Add(idOption);
        deleteCmd.SetAction(async (ParseResult result) =>
        {
            var id = result.GetValue(idOption);
            return await CommandHelpers.ExecuteCommandAsync(
                result, urlOption, formatOption, usernameOption, passwordOption, new DeleteRoleMappingCommand(id));
        });
        group.Add(deleteCmd);

        var updateIdOption = new Option<int>("--id") { Description = "Mapping ID", Required = true };
        var updateLdapGroupOption = new Option<string>("--ldap-group") { Description = "LDAP group name", Required = true };
        var updateRoleOption = new Option<string>("--role") { Description = "Role name", Required = true };
        var updateCmd = new Command("update") { Description = "Update a role mapping" };
        updateCmd.Add(updateIdOption);
        updateCmd.Add(updateLdapGroupOption);
        updateCmd.Add(updateRoleOption);
        updateCmd.SetAction(async (ParseResult result) =>
        {
            var id = result.GetValue(updateIdOption);
            var ldapGroup = result.GetValue(updateLdapGroupOption)!;
            var role = result.GetValue(updateRoleOption)!;
            return await CommandHelpers.ExecuteCommandAsync(
                result, urlOption, formatOption, usernameOption, passwordOption,
                new UpdateRoleMappingCommand(id, ldapGroup, role));
        });
        group.Add(updateCmd);

        return group;
    }

    private static Command BuildScopeRule(Option<string> urlOption, Option<string> formatOption, Option<string> usernameOption, Option<string> passwordOption)
    {
        var group = new Command("scope-rule") { Description = "Manage LDAP scope rules" };

        var mappingIdOption = new Option<int>("--mapping-id") { Description = "Role mapping ID", Required = true };
        var listCmd = new Command("list") { Description = "List scope rules for a mapping" };
        listCmd.Add(mappingIdOption);
        listCmd.SetAction(async (ParseResult result) =>
        {
            var mappingId = result.GetValue(mappingIdOption);
            return await CommandHelpers.ExecuteCommandAsync(
                result, urlOption, formatOption, usernameOption, passwordOption, new ListScopeRulesCommand(mappingId));
        });
        group.Add(listCmd);

        var addMappingIdOption = new Option<int>("--mapping-id") { Description = "Role mapping ID", Required = true };
        var siteIdOption = new Option<int>("--site-id") { Description = "Site ID", Required = true };
        var addCmd = new Command("add") { Description = "Add a scope rule" };
        addCmd.Add(addMappingIdOption);
        addCmd.Add(siteIdOption);
        addCmd.SetAction(async (ParseResult result) =>
        {
            var mappingId = result.GetValue(addMappingIdOption);
            var siteId = result.GetValue(siteIdOption);
            return await CommandHelpers.ExecuteCommandAsync(
                result, urlOption, formatOption, usernameOption, passwordOption, new AddScopeRuleCommand(mappingId, siteId));
        });
        group.Add(addCmd);

        var deleteIdOption = new Option<int>("--id") { Description = "Scope rule ID", Required = true };
        var deleteCmd = new Command("delete") { Description = "Delete a scope rule" };
        deleteCmd.Add(deleteIdOption);
        deleteCmd.SetAction(async (ParseResult result) =>
        {
            var id = result.GetValue(deleteIdOption);
            return await CommandHelpers.ExecuteCommandAsync(
                result, urlOption, formatOption, usernameOption, passwordOption, new DeleteScopeRuleCommand(id));
        });
        group.Add(deleteCmd);

        return group;
    }
}
