using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using ZB.MOM.WW.Secrets.Abstractions;
using ZB.MOM.WW.Secrets.Cli;
using ZB.MOM.WW.Secrets.DependencyInjection;
using ZB.MOM.WW.Secrets.Sqlite;

// Headless `secret` CLI: set / get / list / rm / rotate over the ZB.MOM.WW secrets store.
// The heavy lifting lives in SecretCommands; Program only builds the host, runs the schema
// migration, parses the verb + positional args, and dispatches.

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Configuration
    .AddJsonFile("appsettings.json", optional: true)
    .AddEnvironmentVariables();
builder.Services.AddZbSecrets(builder.Configuration, "Secrets");

using IHost host = builder.Build();

// Ensure the schema exists before any store operation.
await host.Services.GetRequiredService<SqliteSecretsStoreMigrator>()
    .MigrateAsync(CancellationToken.None);

var commands = new SecretCommands(
    host.Services.GetRequiredService<ISecretStore>(),
    host.Services.GetRequiredService<ISecretCipher>(),
    host.Services.GetRequiredService<ISecretResolver>(),
    Console.Out);

string actor = string.IsNullOrWhiteSpace(Environment.UserName) ? "cli" : Environment.UserName;
CancellationToken ct = CancellationToken.None;

if (args.Length == 0)
{
    return Usage();
}

try
{
    switch (args[0])
    {
        case "set":
        {
            if (args.Length < 3)
                return Usage("set requires <name> and <value>.");
            (SecretContentType contentType, string? description) = ParseSetOptions(args, startIndex: 3);
            return await commands.SetAsync(args[1], args[2], contentType, description, actor, ct);
        }

        case "rotate":
        {
            if (args.Length < 3)
                return Usage("rotate requires <name> and <value>.");
            (SecretContentType contentType, string? description) = ParseSetOptions(args, startIndex: 3);
            return await commands.RotateAsync(args[1], args[2], contentType, description, actor, ct);
        }

        case "get":
        {
            if (args.Length < 2)
                return Usage("get requires <name>.");
            return await commands.GetAsync(args[1], ct);
        }

        case "list":
        {
            bool includeDeleted = args.Skip(1).Any(a =>
                string.Equals(a, "--include-deleted", StringComparison.Ordinal));
            return await commands.ListAsync(includeDeleted, ct);
        }

        case "rm":
        {
            if (args.Length < 2)
                return Usage("rm requires <name>.");
            return await commands.RemoveAsync(args[1], actor, ct);
        }

        default:
            return Usage($"Unknown command '{args[0]}'.");
    }
}
catch (ArgumentException ex)
{
    // Invalid secret name, bad --content-type value, unknown option: report without a stack trace.
    Console.Error.WriteLine(ex.Message);
    return 2;
}

// --- helpers ---------------------------------------------------------------

// Parses the optional flags shared by `set` and `rotate`:
//   [--content-type text|connection-string|json|binary-base64] [--description "..."]
static (SecretContentType contentType, string? description) ParseSetOptions(string[] args, int startIndex)
{
    SecretContentType contentType = SecretContentType.Text;
    string? description = null;

    for (int i = startIndex; i < args.Length; i++)
    {
        switch (args[i])
        {
            case "--content-type":
                if (i + 1 >= args.Length)
                    throw new ArgumentException("--content-type requires a value.");
                contentType = ParseContentType(args[++i]);
                break;

            case "--description":
                if (i + 1 >= args.Length)
                    throw new ArgumentException("--description requires a value.");
                description = args[++i];
                break;

            default:
                throw new ArgumentException($"Unknown option '{args[i]}'.");
        }
    }

    return (contentType, description);
}

// Maps the CLI content-type token (kebab-case) to the enum.
static SecretContentType ParseContentType(string value) => value switch
{
    "text" => SecretContentType.Text,
    "connection-string" => SecretContentType.ConnectionString,
    "json" => SecretContentType.Json,
    "binary-base64" => SecretContentType.BinaryBase64,
    _ => throw new ArgumentException(
        $"Unknown --content-type '{value}'; expected text|connection-string|json|binary-base64."),
};

// Prints usage (optionally preceded by an error line) and returns the standard usage exit code.
int Usage(string? error = null)
{
    if (error is not null)
        Console.Error.WriteLine(error);

    Console.Error.WriteLine(
        """
        Usage: secret <command> [args]

          set <name> <value> [--content-type text|connection-string|json|binary-base64] [--description "..."]
          rotate <name> <value> [--content-type ...] [--description "..."]
          get <name>
          list [--include-deleted]
          rm <name>
        """);
    return 2;
}
