using System.Diagnostics.CodeAnalysis;

namespace DodoSSH.Client.Domain;

/// <summary>
/// An S3-compatible bucket and the credentials that reach it, decrypted.
/// </summary>
/// <remarks>
/// <para>
/// Named for the protocol rather than for Amazon, because everything here works the same against MinIO, R2,
/// Backblaze or Ceph — and for those, <see cref="Endpoint"/> is an address on somebody's own network. The
/// interface says S3, which is what people call the protocol; the type says what it is.
/// </para>
/// <para>
/// <b><see cref="SecretAccessKey"/> is a password, and everything this codebase does about passwords applies
/// to it.</b> It is inside the encrypted payload, it never appears in a log line — the activity log records
/// the field's name and not its value — and the merge reports that it differed rather than what it was.
/// </para>
/// </remarks>
public sealed record ObjectStoreSecret : IVaultSecret
{
    /// <summary>What the user calls this bucket. The only name it has anywhere.</summary>
    public required string Label { get; init; }

    /// <summary>The bucket.</summary>
    public required string Bucket { get; init; }

    /// <summary>The access key id.</summary>
    public required string AccessKeyId { get; init; }

    /// <summary>The secret access key.</summary>
    public required string SecretAccessKey { get; init; }

    /// <summary>
    /// The region, or null to let the endpoint decide.
    /// </summary>
    /// <remarks>
    /// Required by AWS and ignored by several S3-compatible services, which is why it is nullable rather than
    /// defaulted to <c>us-east-1</c>. A default would be a guess presented as configuration, and the guess is
    /// wrong for exactly the self-hosted case this field exists to support.
    /// </remarks>
    public string? Region { get; init; }

    /// <summary>
    /// The service endpoint, or null for Amazon's own.
    /// </summary>
    /// <remarks>
    /// Null means AWS and the SDK resolves the host from <see cref="Region"/>. Anything else is a URL, and it
    /// is the field that makes this work against a MinIO in a cupboard.
    /// </remarks>
    public string? Endpoint { get; init; }

    /// <summary>
    /// Whether to address the bucket as a path rather than as a subdomain.
    /// </summary>
    /// <remarks>
    /// <c>https://endpoint/bucket/key</c> instead of <c>https://bucket.endpoint/key</c>. Off for AWS, on for
    /// nearly every self-hosted service — MinIO in its default configuration has no wildcard DNS, so
    /// virtual-host addressing simply does not resolve. It is a setting rather than a guess because getting
    /// it wrong produces a name-resolution failure that says nothing about buckets.
    /// </remarks>
    public bool UsePathStyle { get; init; }

    /// <summary>Free-text notes.</summary>
    public string? Notes { get; init; }

    /// <summary>Whether this is storable, and why not if it is not.</summary>
    /// <remarks>
    /// The endpoint is checked for being a well-formed absolute URL when it is set at all. A relative one, or
    /// a bare hostname, produces an SDK failure at the first request whose message names neither the field
    /// nor this bucket — and the person reading it has typically just typed the value.
    /// </remarks>
    public bool TryValidate([NotNullWhen(false)] out string? reason)
    {
        if (string.IsNullOrWhiteSpace(Label))
        {
            reason = "A bucket needs a name.";
            return false;
        }

        if (string.IsNullOrWhiteSpace(Bucket))
        {
            reason = "A bucket needs the bucket it points at.";
            return false;
        }

        if (string.IsNullOrWhiteSpace(AccessKeyId) || string.IsNullOrWhiteSpace(SecretAccessKey))
        {
            reason = "A bucket needs an access key id and a secret access key.";
            return false;
        }

        if (Endpoint is not null)
        {
            if (!Uri.TryCreate(Endpoint, UriKind.Absolute, out var endpoint))
            {
                reason = "The endpoint has to be a full URL, like https://minio.internal:9000.";
                return false;
            }

            if (!string.Equals(endpoint.Scheme, Uri.UriSchemeHttps, StringComparison.Ordinal)
                && !string.Equals(endpoint.Scheme, Uri.UriSchemeHttp, StringComparison.Ordinal))
            {
                reason = "The endpoint has to be http or https.";
                return false;
            }
        }

        if (Region is null && Endpoint is null)
        {
            // With neither, the SDK has nothing to resolve a host from and fails at the first request with
            // a message about a missing region rather than about this bucket.
            reason = "A bucket needs a region, an endpoint, or both.";
            return false;
        }

        reason = null;
        return true;
    }
}
