using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.DependencyInjection;
using Npgsql;
using Tiku.Application.Assets;
using Tiku.Application.Auth;
using Tiku.Application.Backoffice;
using Tiku.Application.Catalog;
using Tiku.Application.Commerce;
using Tiku.Application.Content;
using Tiku.Application.Growth;
using Tiku.Application.Jobs;
using Tiku.Application.Learning;
using Tiku.Application.Notifications;
using Tiku.Application.QuestionBanks;
using Tiku.Application.Profile;
using Tiku.Application.Points;
using Tiku.Application.PlatformAdmin;
using Tiku.Application.PlatformBilling;
using Tiku.Application.Scoreline;
using Tiku.Application.Storage;
using Tiku.Application.StudyContent;
using Tiku.Application.TenantAdmin;
using Tiku.Application.Security;
using Tiku.Application.Tenancy;
using Tiku.Infrastructure.Assets;
using Tiku.Infrastructure.Auth;
using Tiku.Infrastructure.Backoffice;
using Tiku.Infrastructure.Catalog;
using Tiku.Infrastructure.Commerce;
using Tiku.Infrastructure.Content;
using Tiku.Infrastructure.Growth;
using Tiku.Infrastructure.Jobs;
using Tiku.Infrastructure.Learning;
using Tiku.Infrastructure.Notifications;
using Tiku.Infrastructure.Persistence;
using Tiku.Infrastructure.Profile;
using Tiku.Infrastructure.Points;
using Tiku.Infrastructure.PlatformAdmin;
using Tiku.Infrastructure.PlatformBilling;
using Tiku.Infrastructure.QuestionBanks;
using Tiku.Infrastructure.Scoreline;
using Tiku.Infrastructure.Security;
using Tiku.Infrastructure.Storage;
using Tiku.Infrastructure.StudyContent;
using Tiku.Infrastructure.TenantAdmin;
using Tiku.Infrastructure.Tenancy;
using Tiku.Domain.Identity;
using StackExchange.Redis;
using Tiku.Infrastructure.Observability;

namespace Tiku.Infrastructure;

public static class DependencyInjection
{
    public static IServiceCollection AddInfrastructure(
        this IServiceCollection services,
        string connectionString,
        Action<DbContextOptionsBuilder>? configureDatabase = null)
    {
        ArgumentException.ThrowIfNullOrWhiteSpace(connectionString);

        services.AddSingleton(_ => NpgsqlDataSource.Create(connectionString));
        services.AddScoped<TenantIsolationSaveChangesInterceptor>();
        services.AddSingleton<DatabasePerformanceInterceptor>();
        services.AddDbContext<TikuDbContext>((serviceProvider, options) =>
        {
            var dataSource = serviceProvider.GetRequiredService<NpgsqlDataSource>();
            options.UseNpgsql(dataSource, npgsql =>
                npgsql.MigrationsAssembly(typeof(TikuDbContext).Assembly.FullName));
            configureDatabase?.Invoke(options);
            options.AddInterceptors(
                serviceProvider.GetRequiredService<TenantIsolationSaveChangesInterceptor>(),
                serviceProvider.GetRequiredService<DatabasePerformanceInterceptor>());
        });
        services.AddIdentityCore<User>(options =>
            {
                options.Password.RequiredLength = 8;
                options.Password.RequireDigit = false;
                options.Password.RequireLowercase = false;
                options.Password.RequireUppercase = false;
                options.Password.RequireNonAlphanumeric = false;
                options.Lockout.MaxFailedAccessAttempts = 5;
                options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(15);
                options.User.RequireUniqueEmail = false;
            })
            .AddEntityFrameworkStores<TikuDbContext>()
            .AddSignInManager()
            .AddDefaultTokenProviders()
            .AddPasswordValidator<LetterAndDigitPasswordValidator<User>>();
        services.Configure<PasswordHasherOptions>(options => options.IterationCount = 210_000);
        services.AddScoped<ITenantDirectory, TenantDirectory>();
        services.AddSingleton<IRedisSecurityStore, NullRedisSecurityStore>();
        services.AddMemoryCache();
        services.AddScoped<ITenantFrontendConfigService, TenantFrontendConfigService>();
        services.AddScoped<ITenantExternalProviderConfigService, TenantExternalProviderConfigService>();
        services.AddSingleton<ITenantRuntimeCacheInvalidator, TenantRuntimeCacheInvalidator>();
        services.AddSingleton<ITenantPublicCacheInvalidator, NullTenantPublicCacheInvalidator>();
        services.AddHttpClient<IDomainOwnershipVerifier, DnsDomainOwnershipVerifier>();
        services.AddHttpClient<IDomainGatewayProvisioner, HttpDomainGatewayProvisioner>();
        services.AddScoped<ITenantDomainLifecycleService, TenantDomainLifecycleService>();
        services.AddOptions<DomainLifecycleOptions>();
        services.AddSingleton<ITenantExecutionScope, TenantExecutionScope>();
        services.AddSingleton<IJwtKeyRing, JwtKeyRing>();
        services.AddScoped<ITokenService, TokenService>();
        services.AddScoped<AuthSessionStore>();
        services.AddScoped<IAuthSessionStore>(provider => provider.GetRequiredService<AuthSessionStore>());
        services.AddScoped<ISmsProvider, AliyunSmsProvider>();
        services.AddScoped<ISmsVerificationService, SmsVerificationService>();
        services.AddScoped<IWechatOAuthClient, WechatOAuthClient>();
        services.AddScoped<IAuthService, AuthService>();
        services.AddScoped<IIdentityProvider, SelfHostedIdentityProvider>();
        services.AddScoped<ICatalogQueryService, CatalogQueryService>();
        services.AddScoped<ITaxonomyService, TaxonomyService>();
        services.AddScoped<IContentNavigationQueryService, ContentNavigationQueryService>();
        services.AddScoped<IContentManagementService, ContentManagementService>();
        services.AddScoped<IDirectContentService, DirectContentService>();
        services.AddScoped<IQuestionBankQueryService, QuestionBankQueryService>();
        services.AddScoped<IPublicQuestionAccessPolicy, PublicQuestionAccessPolicy>();
        services.AddScoped<IQuestionReferenceService, QuestionReferenceService>();
        services.AddScoped<IProfileService, ProfileService>();
        services.AddScoped<ScorelineRecordQuery>();
        services.AddScoped<IScorelineQueryService, ScorelineQueryService>();
        services.AddScoped<IStudyContentQueryService, StudyContentQueryService>();
        services.AddScoped<IAssetQueryService, AssetQueryService>();
        services.AddScoped<IAssetAccessService, AssetAccessService>();
        services.AddScoped<IAssetManagementService, AssetManagementService>();
        services.AddScoped<IVideoPlaybackService, VideoPlaybackService>();
        services.AddScoped<ILearningActivityService, LearningActivityService>();
        services.AddScoped<ITenantAdminDirectService, TenantAdminDirectService>();
        services.AddScoped<IBackofficeService, BackofficeService>();
        services.AddScoped<IPlatformAdminService, PlatformAdminService>();
        services.AddScoped<IPlatformQuestionBankService, PlatformQuestionBankService>();
        services.AddScoped<IPlatformCrmAdminService, PlatformCrmAdminService>();
        services.AddScoped<IPlatformSmsAdminService, PlatformSmsAdminService>();
        services.AddScoped<IPlatformPaymentSettingsService, PlatformPaymentSettingsService>();
        services.AddScoped<IPlatformTenantPaymentAdminService, PlatformTenantPaymentAdminService>();
        services.AddScoped<ISaasCatalogAdminService, SaasCatalogAdminService>();
        services.AddScoped<ITenantBillingService, TenantBillingService>();
        services.AddScoped<IPlatformBillingAdminService, PlatformBillingAdminService>();
        services.AddScoped<IPlatformBillingPaymentGateway, PlatformBillingPaymentGateway>();
        services.AddScoped<IPlatformBillingNotificationService, PlatformBillingNotificationService>();
        services.AddScoped<IPlatformBillingSettlementService, PlatformBillingSettlementService>();
        services.AddScoped<ISaasSubscriptionLifecycleService, SaasSubscriptionLifecycleService>();
        services.AddOptions<SaasSubscriptionLifecycleOptions>();
        services.AddScoped<ITenantOnboardingService, TenantOnboardingService>();
        services.AddScoped<ICurrentAccessContext, CurrentAccessContext>();
        services.AddScoped<ITenantFeatureSnapshotProvider, TenantFeatureSnapshotProvider>();
        services.AddSingleton<TenantFeatureCacheInvalidator>();
        services.AddSingleton<ITenantFeatureCacheInvalidator>(provider => provider.GetRequiredService<TenantFeatureCacheInvalidator>());
        services.AddScoped<IFeatureAccessService, FeatureAccessService>();
        services.AddScoped<IFeatureUsageReconciliationService, FeatureUsageReconciliationService>();
        services.AddOptions<FeatureUsageReconciliationOptions>();
        services.AddScoped<IOperationAuditService, OperationAuditService>();
        services.AddScoped<IBackgroundJobService, BackgroundJobService>();
        services.AddScoped<ICommerceService, CommerceService>();
        services.AddScoped<ICommerceAdminService, CommerceAdminService>();
        services.AddScoped<IPointService, PointService>();
        services.AddScoped<IReferralQrcodeGenerator, ReferralQrcodeGenerator>();
        services.AddScoped<IReferralService, ReferralService>();
        services.AddScoped<ICrmService, CrmService>();
        services.AddScoped<ICommissionService, CommissionService>();
        services.AddScoped<INotificationProvider, InAppNotificationProvider>();
        services.AddSingleton<ITenantSecretProtector, TenantSecretProtector>();
        services.AddScoped<ITenantSecretService, TenantSecretService>();
        services.AddScoped<IPaymentProviderConfigService, PaymentProviderConfigService>();
        services.AddScoped<IPaymentProviderGateway, PaymentProviderGateway>();
        services.AddScoped<IPaymentProvider, ManualPaymentProvider>();
        services.AddScoped<IPaymentProvider, WechatPayProvider>();
        services.AddScoped<IPaymentProvider, AlipayProvider>();
        services.AddOptions<AliyunOssOptions>()
            .Validate(
                AliyunOssOptions.BeValid,
                "Aliyun OSS presign default expiration must be positive.");
        services.AddOptions<ObjectStorageOptions>()
            .Validate(
                ObjectStorageOptions.BeValid,
                "Storage options must include a default provider, default bucket and positive max upload bytes.");
        services.AddSingleton<IObjectStorageService, AliyunOssObjectStorageService>();

        return services;
    }

    public static IServiceCollection AddRedisSecurity(
        this IServiceCollection services,
        string connectionString,
        string environmentName)
    {
        ArgumentException.ThrowIfNullOrWhiteSpace(connectionString);
        var options = ConfigurationOptions.Parse(connectionString);
        options.AbortOnConnectFail = false;
        options.ClientName = $"tiku-{environmentName.ToLowerInvariant()}";
        services.AddSingleton<IConnectionMultiplexer>(_ => ConnectionMultiplexer.Connect(options));
        services.AddSingleton(provider => new RedisSecurityStore(
            provider.GetRequiredService<IConnectionMultiplexer>(),
            environmentName,
            provider.GetRequiredService<Microsoft.Extensions.Logging.ILogger<RedisSecurityStore>>()));
        services.AddSingleton<IRedisSecurityStore>(provider => provider.GetRequiredService<RedisSecurityStore>());
        services.AddStackExchangeRedisCache(cache => cache.ConfigurationOptions = options);
        return services;
    }

}
