using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Implementations;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;
using Bit.Core.AdminConsole.Repositories;
using Bit.Test.Common.AutoFixture.Attributes;
using NSubstitute;
using Xunit;

namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Policies;

[SutProviderCustomize]
public class PolicyRequirementQueryTests
{
    /// <summary>
    /// Tests that the query correctly registers, retrieves and instantiates arbitrary IPolicyRequirements
    /// according to their provided CreateRequirement delegate.
    /// </summary>
    [Theory, BitAutoData]
    public async Task GetAsync_Works(Guid userId, Guid organizationId)
    {
        var policyRepository = Substitute.For<IPolicyRepository>();
        var factories = new List<RequirementFactory<IPolicyRequirement>>
        {
            // In prod this cast is handled when the CreateRequirement delegate is registered in DI
            (RequirementFactory<TestPolicyRequirement>)TestPolicyRequirement.Create
        };

        var sut = new PolicyRequirementQuery(policyRepository, factories);
        policyRepository.GetPolicyDetailsByUserId(userId).Returns([
            new PolicyDetails
            {
                OrganizationId = organizationId
            }
        ]);

        var requirement = await sut.GetAsync<TestPolicyRequirement>(userId);
        Assert.Equal(organizationId, requirement.OrganizationId);
    }

    [Theory, BitAutoData]
    public async Task GetAsync_ThrowsIfNoRequirementRegistered(Guid userId)
    {
        var policyRepository = Substitute.For<IPolicyRepository>();
        var sut = new PolicyRequirementQuery(policyRepository, []);

        var exception = await Assert.ThrowsAsync<NotImplementedException>(()
            => sut.GetAsync<TestPolicyRequirement>(userId));
        Assert.Contains("No Policy Requirement found", exception.Message);
    }

    /// <summary>
    /// Intentionally simplified PolicyRequirement that just holds the Policy.OrganizationId for us to assert against.
    /// </summary>
    private class TestPolicyRequirement : IPolicyRequirement
    {
        public Guid OrganizationId { get; init; }
        public static TestPolicyRequirement Create(IEnumerable<PolicyDetails> policyDetails)
            => new() { OrganizationId = policyDetails.Single().OrganizationId };
    }
}