using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations.Interfaces;
using Bit.Core.Billing.Organizations.Services;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Core.Settings;

namespace Bit.Core.AdminConsole.OrganizationFeatures.Organizations.Update;

public class OrganizationUpdateCommand(
    IOrganizationService organizationService,
    IOrganizationRepository organizationRepository,
    IGlobalSettings globalSettings,
    IOrganizationBillingService organizationBillingService
) : IOrganizationUpdateCommand
{
    public async Task<Organization> UpdateAsync(OrganizationUpdateRequest request)
    {
        var organization = await organizationRepository.GetByIdAsync(request.OrganizationId);
        if (organization == null)
        {
            throw new NotFoundException();
        }

        if (globalSettings.SelfHosted)
        {
            return await UpdateSelfHostedAsync(organization, request);
        }

        return await UpdateCloudAsync(organization, request);
    }

    private async Task<Organization> UpdateCloudAsync(Organization organization, OrganizationUpdateRequest request)
    {
        // Store original values for comparison
        var originalName = organization.Name;
        var originalBillingEmail = organization.BillingEmail;

        // Apply updates to organization
        // These values may or may not be sent by the client depending on the operation being performed.
        // Skip any values not provided.
        if (request.Name is not null)
        {
            organization.Name = request.Name;
        }

        if (request.BillingEmail is not null)
        {
            organization.BillingEmail = request.BillingEmail.ToLowerInvariant().Trim();
        }

        organization.BackfillPublicPrivateKeys(request.Keys);

        await organizationService.ReplaceAndUpdateCacheAsync(organization, EventType.Organization_Updated);

        // Update billing information in Stripe if required
        await UpdateBillingAsync(organization, originalName, originalBillingEmail);

        return organization;
    }

    /// <summary>
    /// Self-host cannot update the organization details because they are set by the license file.
    /// However, this command does offer a soft migration pathway for organizations without public and private keys.
    /// If we remove this migration code in the future, this command and endpoint can become cloud only.
    /// </summary>
    private async Task<Organization> UpdateSelfHostedAsync(Organization organization, OrganizationUpdateRequest request)
    {
        organization.BackfillPublicPrivateKeys(request.Keys);
        await organizationService.ReplaceAndUpdateCacheAsync(organization, EventType.Organization_Updated);
        return organization;
    }

    private async Task UpdateBillingAsync(Organization organization, string originalName, string? originalBillingEmail)
    {
        // Update Stripe if name or billing email changed
        var shouldUpdateBilling = originalName != organization.Name ||
                                  originalBillingEmail != organization.BillingEmail;

        if (!shouldUpdateBilling)
        {
            return;
        }

        await organizationBillingService.UpdateOrganizationNameAndEmail(organization);
    }
}
