using System.Globalization;
using Spectre.Console;

namespace ZB.MOM.WW.Secrets.Cli.Interactive.Flows;

/// <summary>
/// Exports the session's store to a ciphertext-only <see cref="SecretBundle"/> on disk. The operator is
/// offered a default filename (<c>secrets-bundle-&lt;target&gt;-&lt;yyyyMMdd&gt;.json</c>) they can accept
/// or override, and a confirmation for whether tombstoned rows are included. The bundle carries no
/// plaintext — only the encrypted rows and the source KEK id — so it is safe at rest and useless without
/// the source key. Requires a KEK-capable session (the shell upgrades a degraded session before dispatch).
/// </summary>
public sealed class BundleExportFlow : IInteractiveFlow
{
    private readonly TimeProvider _timeProvider;

    /// <summary>Creates the flow.</summary>
    /// <param name="timeProvider">
    /// Clock used for the default filename's date stamp and the bundle's <see cref="SecretBundle.ExportedUtc"/>
    /// timestamp; defaults to <see cref="TimeProvider.System"/>.
    /// </param>
    public BundleExportFlow(TimeProvider? timeProvider = null) =>
        _timeProvider = timeProvider ?? TimeProvider.System;

    /// <inheritdoc />
    public string Title => "Export bundle (ciphertext-only)";

    /// <inheritdoc />
    public bool RequiresKek => true;

    /// <inheritdoc />
    public async Task RunAsync(IAnsiConsole console, SecretsSession session, CancellationToken ct)
    {
        ArgumentNullException.ThrowIfNull(console);
        ArgumentNullException.ThrowIfNull(session);

        string defaultPath = DefaultBundleFileName(session.Target.Name, _timeProvider.GetUtcNow());
        string path = console.Prompt(
            new TextPrompt<string>("Bundle [green]path[/]:").DefaultValue(defaultPath));

        bool includeDeleted = console.Prompt(
            new ConfirmationPrompt("Include tombstoned (deleted) rows?") { DefaultValue = false });

        int count;
        try
        {
            count = await new BundleService(_timeProvider)
                .ExportAsync(session, path, includeDeleted, ct).ConfigureAwait(false);
        }
        catch (Exception ex) when (ex is IOException or UnauthorizedAccessException)
        {
            console.MarkupLineInterpolated($"[red]Could not write bundle to '{path}': {ex.Message}[/]");
            return;
        }

        console.MarkupLineInterpolated($"[green]Exported {count} row(s) to '{path}'.[/]");
    }

    // Builds a filesystem-safe default name: secrets-bundle-<target>-<yyyyMMdd>.json, with any character
    // illegal in a file name collapsed to '-' so the offered default is always a valid path segment.
    private static string DefaultBundleFileName(string targetName, DateTimeOffset now)
    {
        string sanitized = Sanitize(targetName);
        string date = now.ToString("yyyyMMdd", CultureInfo.InvariantCulture);
        return $"secrets-bundle-{sanitized}-{date}.json";
    }

    private static string Sanitize(string name)
    {
        if (string.IsNullOrWhiteSpace(name))
        {
            return "store";
        }

        char[] invalid = Path.GetInvalidFileNameChars();
        char[] chars = name.Trim().Select(c => invalid.Contains(c) ? '-' : c).ToArray();
        return new string(chars);
    }
}
