#nullable enable
using System.Security.Claims;
using Bit.Api.SecretsManager.Controllers;
using Bit.Api.SecretsManager.Models.Response;
using Bit.Core.Context;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.SecretsManager.Entities;
using Bit.Core.SecretsManager.Models.Data;
using Bit.Core.SecretsManager.Queries.Interfaces;
using Bit.Core.SecretsManager.Repositories;
using Bit.Core.Test.SecretsManager.AutoFixture.ProjectsFixture;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using NSubstitute;
using Xunit;

namespace Bit.Api.Test.SecretsManager.Controllers;

[ControllerCustomize(typeof(CountsController))]
[SutProviderCustomize]
[ProjectCustomize]
[JsonDocumentCustomize]
public class CountsControllerTests
{
    [Theory]
    [BitAutoData]
    public async Task GetByOrganizationAsync_NoAccess_Throws(SutProvider<CountsController> sutProvider,
        Guid organizationId)
    {
        sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(organizationId).Returns(false);

        await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.GetByOrganizationAsync(organizationId));
    }

    [Theory]
    [BitAutoData]
    public async Task GetByOrganizationAsync_ServiceAccountAccess_Throws(SutProvider<CountsController> sutProvider,
        Guid organizationId, Guid userId)
    {
        sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(organizationId).Returns(true);

        sutProvider.GetDependency<IAccessClientQuery>()
            .GetAccessClientAsync(Arg.Any<ClaimsPrincipal>(), organizationId)
            .Returns((AccessClientType.ServiceAccount, userId));

        await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.GetByOrganizationAsync(organizationId));
    }

    [Theory]
    [BitAutoData(AccessClientType.NoAccessCheck)]
    [BitAutoData(AccessClientType.User)]
    public async Task GetByOrganizationAsync_HasAccess_Success(AccessClientType accessClientType,
        SutProvider<CountsController> sutProvider, Guid organizationId, Guid userId,
        OrganizationCountsResponseModel expectedCountsResponseModel)
    {
        sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(organizationId).Returns(true);

        sutProvider.GetDependency<IAccessClientQuery>()
            .GetAccessClientAsync(Arg.Any<ClaimsPrincipal>(), organizationId).Returns((accessClientType, userId));

        sutProvider.GetDependency<IProjectRepository>()
            .GetProjectCountByOrganizationIdAsync(organizationId, userId, accessClientType)
            .Returns(expectedCountsResponseModel.Projects);

        sutProvider.GetDependency<ISecretRepository>()
            .GetSecretsCountByOrganizationIdAsync(organizationId, userId, accessClientType)
            .Returns(expectedCountsResponseModel.Secrets);

        sutProvider.GetDependency<IServiceAccountRepository>()
            .GetServiceAccountCountByOrganizationIdAsync(organizationId, userId, accessClientType)
            .Returns(expectedCountsResponseModel.ServiceAccounts);

        var response = await sutProvider.Sut.GetByOrganizationAsync(organizationId);

        Assert.Equal(expectedCountsResponseModel.Projects, response.Projects);
        Assert.Equal(expectedCountsResponseModel.Secrets, response.Secrets);
        Assert.Equal(expectedCountsResponseModel.ServiceAccounts, response.ServiceAccounts);
    }

    [Theory]
    [BitAutoData]
    public async Task GetByProjectAsync_ProjectNotFound_Throws(SutProvider<CountsController> sutProvider,
        Guid projectId)
    {
        sutProvider.GetDependency<IProjectRepository>().GetByIdAsync(projectId).Returns(default(Project));

        await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.GetByProjectAsync(projectId));
    }

    [Theory]
    [BitAutoData]
    public async Task GetByProjectAsync_NoAccess_Throws(SutProvider<CountsController> sutProvider, Project project)
    {
        sutProvider.GetDependency<IProjectRepository>().GetByIdAsync(project.Id).Returns(project);
        sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(project.OrganizationId).Returns(false);

        await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.GetByProjectAsync(project.Id));
    }

    [Theory]
    [BitAutoData]
    public async Task GetByProjectAsync_ServiceAccountAccess_Throws(SutProvider<CountsController> sutProvider,
        Guid userId, Project project)
    {
        sutProvider.GetDependency<IProjectRepository>().GetByIdAsync(project.Id).Returns(project);
        sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(project.OrganizationId).Returns(true);

        sutProvider.GetDependency<IAccessClientQuery>()
            .GetAccessClientAsync(Arg.Any<ClaimsPrincipal>(), project.OrganizationId)
            .Returns((AccessClientType.ServiceAccount, userId));

        await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.GetByProjectAsync(project.Id));
    }

    [Theory]
    [BitAutoData(AccessClientType.NoAccessCheck)]
    [BitAutoData(AccessClientType.User)]
    public async Task GetByProjectAsync_HasAccess_Success(AccessClientType accessClientType,
        SutProvider<CountsController> sutProvider, Guid userId, Project project,
        ProjectCountsResponseModel expectedProjectCountsResponseModel)
    {
        sutProvider.GetDependency<IProjectRepository>().GetByIdAsync(project.Id).Returns(project);
        sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(project.OrganizationId).Returns(true);
        sutProvider.GetDependency<IAccessClientQuery>()
            .GetAccessClientAsync(Arg.Any<ClaimsPrincipal>(), project.OrganizationId)
            .Returns((accessClientType, userId));

        sutProvider.GetDependency<IProjectRepository>()
            .GetProjectCountsByIdAsync(project.Id, userId, accessClientType)
            .Returns(new ProjectCounts
            {
                Secrets = expectedProjectCountsResponseModel.Secrets,
                People = expectedProjectCountsResponseModel.People,
                ServiceAccounts = expectedProjectCountsResponseModel.ServiceAccounts
            });

        var response = await sutProvider.Sut.GetByProjectAsync(project.Id);

        Assert.Equal(expectedProjectCountsResponseModel.Secrets, response.Secrets);
        Assert.Equal(expectedProjectCountsResponseModel.People, response.People);
        Assert.Equal(expectedProjectCountsResponseModel.ServiceAccounts, response.ServiceAccounts);
    }

    [Theory]
    [BitAutoData]
    public async Task GetByServiceAccountAsync_ServiceAccountNotFound_Throws(SutProvider<CountsController> sutProvider,
        Guid serviceAccountId)
    {
        sutProvider.GetDependency<IServiceAccountRepository>().GetByIdAsync(serviceAccountId)
            .Returns(default(ServiceAccount));

        await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.GetByServiceAccountAsync(serviceAccountId));
    }

    [Theory]
    [BitAutoData]
    public async Task GetByServiceAccountAsync_NoAccess_Throws(SutProvider<CountsController> sutProvider,
        ServiceAccount serviceAccount)
    {
        sutProvider.GetDependency<IServiceAccountRepository>().GetByIdAsync(serviceAccount.Id)
            .Returns(serviceAccount);
        sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(serviceAccount.OrganizationId).Returns(false);

        await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.GetByServiceAccountAsync(serviceAccount.Id));
    }

    [Theory]
    [BitAutoData]
    public async Task GetByServiceAccountAsync_ServiceAccountAccess_Throws(SutProvider<CountsController> sutProvider,
        Guid userId, ServiceAccount serviceAccount)
    {
        sutProvider.GetDependency<IServiceAccountRepository>().GetByIdAsync(serviceAccount.Id).Returns(serviceAccount);
        sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(serviceAccount.OrganizationId).Returns(true);

        sutProvider.GetDependency<IAccessClientQuery>()
            .GetAccessClientAsync(Arg.Any<ClaimsPrincipal>(), serviceAccount.OrganizationId)
            .Returns((AccessClientType.ServiceAccount, userId));

        await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.GetByServiceAccountAsync(serviceAccount.Id));
    }

    [Theory]
    [BitAutoData(AccessClientType.NoAccessCheck)]
    [BitAutoData(AccessClientType.User)]
    public async Task GetByServiceAccountAsync_HasAccess_Success(AccessClientType accessClientType,
        SutProvider<CountsController> sutProvider, Guid userId, ServiceAccount serviceAccount,
        ServiceAccountCountsResponseModel expectedServiceAccountCountsResponseModel)
    {
        sutProvider.GetDependency<IServiceAccountRepository>().GetByIdAsync(serviceAccount.Id)
            .Returns(serviceAccount);
        sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(serviceAccount.OrganizationId).Returns(true);
        sutProvider.GetDependency<IAccessClientQuery>()
            .GetAccessClientAsync(Arg.Any<ClaimsPrincipal>(), serviceAccount.OrganizationId)
            .Returns((accessClientType, userId));

        sutProvider.GetDependency<IServiceAccountRepository>()
            .GetServiceAccountCountsByIdAsync(serviceAccount.Id, userId, accessClientType)
            .Returns(new ServiceAccountCounts
            {
                Projects = expectedServiceAccountCountsResponseModel.Projects,
                People = expectedServiceAccountCountsResponseModel.People,
                AccessTokens = expectedServiceAccountCountsResponseModel.AccessTokens
            });

        var response = await sutProvider.Sut.GetByServiceAccountAsync(serviceAccount.Id);

        Assert.Equal(expectedServiceAccountCountsResponseModel.Projects, response.Projects);
        Assert.Equal(expectedServiceAccountCountsResponseModel.People, response.People);
        Assert.Equal(expectedServiceAccountCountsResponseModel.AccessTokens, response.AccessTokens);
    }
}