using DodoSSH.Client.Domain;

namespace DodoSSH.Client.ObjectStore.Tests;

/// <summary>
/// What a bucket needs before it can be stored, and what the factory does with it.
/// </summary>
/// <remarks>
/// Every refusal here exists because the failure it prevents is one whose message names neither the field
/// nor the bucket. A missing region produces an SDK error about resolving an endpoint; a hostname without a
/// scheme produces a URI parse failure; and both arrive at the first listing, long after the typing.
/// </remarks>
public sealed class ObjectStoreSecretTests
{
    [Fact]
    public void ABucketWithARegion_IsStorable()
    {
        Bucket().TryValidate(out var reason).ShouldBeTrue(reason);
    }

    [Fact]
    public void ABucketWithAnEndpointAndNoRegion_IsStorable()
    {
        // The self-hosted case, and the reason Region is nullable rather than defaulted to us-east-1: a
        // default would be a guess presented as configuration, and it is wrong for exactly this user.
        var store = Bucket() with { Region = null, Endpoint = "https://minio.internal:9000" };

        store.TryValidate(out var reason).ShouldBeTrue(reason);
    }

    [Fact]
    public void ABucketWithNeitherARegionNorAnEndpoint_IsRefused()
    {
        var store = Bucket() with { Region = null, Endpoint = null };

        store.TryValidate(out var reason).ShouldBeFalse();
        reason.ShouldNotBeNull().ShouldContain("region");
    }

    /// <remarks>
    /// A bare hostname is what somebody types, and the SDK's own failure for it names a URI rather than this
    /// field. Refusing at the editor is the only place the message can be about what was typed.
    /// </remarks>
    [Theory]
    [InlineData("minio.internal:9000")]
    [InlineData("/buckets")]
    [InlineData("ftp://minio.internal")]
    public void AnEndpointThatIsNotAnHttpUrl_IsRefused(string endpoint)
    {
        var store = Bucket() with { Endpoint = endpoint };

        store.TryValidate(out var reason).ShouldBeFalse();
        reason.ShouldNotBeNull();
    }

    [Theory]
    [InlineData("")]
    [InlineData("   ")]
    public void ABucketMissingSomethingItNeeds_IsRefused(string blank)
    {
        (Bucket() with { Label = blank }).TryValidate(out _).ShouldBeFalse();
        (Bucket() with { Bucket = blank }).TryValidate(out _).ShouldBeFalse();
        (Bucket() with { AccessKeyId = blank }).TryValidate(out _).ShouldBeFalse();
        (Bucket() with { SecretAccessKey = blank }).TryValidate(out _).ShouldBeFalse();
    }

    [Fact]
    public void ABucket_RoundTripsThroughItsCodec()
    {
        var store = Bucket() with
        {
            Endpoint = "https://minio.internal:9000",
            UsePathStyle = true,
            Notes = "the backups bucket",
        };

        ObjectStoreSecretCodec.TryDecode(ObjectStoreSecretCodec.Encode(store), out var document)
            .ShouldBeTrue();

        document.ShouldNotBeNull();
        document.Store.ShouldBe(store);
        document.SchemaVersion.ShouldBe(ObjectStoreSecretCodec.CurrentSchemaVersion);
    }

    /// <remarks>
    /// The factory builds a client and contacts nothing, which is why it is synchronous — S3 is
    /// request-per-operation and there is no connect step to fail. What it does do is refuse a bucket that
    /// could never work, so the failure lands at the button rather than at the first listing.
    /// </remarks>
    [Fact]
    public void TheFactoryRefusesABucketThatCouldNotBeStored()
    {
        var factory = new S3ObjectStoreFactory();

        Should.Throw<ArgumentException>(
            () => factory.Open(Bucket() with { Region = null, Endpoint = null }));
    }

    [Fact]
    public void TheFactoryOpensAValidBucketWithoutContactingAnything()
    {
        var factory = new S3ObjectStoreFactory();

        var store = factory.Open(Bucket() with
        {
            Endpoint = "https://minio.internal:9000",
            UsePathStyle = true,
        });

        store.IsConnected.ShouldBeTrue("nothing is contacted, so there is nothing to be down");
        store.HomeDirectory.ShouldBe("/");
    }

    private static ObjectStoreSecret Bucket() => new()
    {
        Label = "backups",
        Bucket = "dodossh-backups",
        AccessKeyId = "AKIAEXAMPLE",
        SecretAccessKey = "an example secret access key",
        Region = "eu-west-1",
    };
}
