using Microsoft.Extensions.Options;
using Tiku.Application.Auth;
using Tiku.Application.Storage;
using Tiku.Infrastructure.Assets;
using Tiku.Infrastructure.Commerce;
using Tiku.Infrastructure.Storage;

namespace Tiku.Api.Configuration;

internal static class ExternalServiceOptionsExtensions
{
    internal static IServiceCollection AddExternalServiceOptions(
        this IServiceCollection services,
        IConfiguration configuration,
        IHostEnvironment environment)
    {
        services.Configure<ObjectStorageOptions>(
            configuration.GetSection(ObjectStorageOptions.SectionName));
        services.Configure<AliyunOssOptions>(
            configuration.GetSection(AliyunOssOptions.SectionName));
        services.Configure<S3CompatibleOptions>(
            configuration.GetSection(S3CompatibleOptions.SectionName));
        services.PostConfigure<ObjectStorageOptions>(options =>
        {
            options.DefaultProvider = configuration["STORAGE_DEFAULT_PROVIDER"] ?? options.DefaultProvider;
            if (!environment.IsProduction() &&
                options.DefaultProvider == ObjectStorageProviders.AliyunOss &&
                !HasConfiguredAliyunOss(configuration))
                options.DefaultProvider = ObjectStorageProviders.LocalDev;
            options.DefaultBucket = configuration["STORAGE_DEFAULT_BUCKET"] ?? options.DefaultBucket;
            options.PublicBaseUrl = configuration["STORAGE_PUBLIC_BASE_URL"] ?? options.PublicBaseUrl;
            options.AllowedMimePrefixes = SplitLegacyList(
                configuration["STORAGE_ALLOWED_MIME_PREFIXES"],
                options.AllowedMimePrefixes);
            options.AllowedMimeTypes = SplitLegacyList(
                configuration["STORAGE_ALLOWED_MIME_TYPES"],
                options.AllowedMimeTypes);
            options.RequireTenantPrefix = bool.TryParse(
                configuration["STORAGE_REQUIRE_TENANT_PREFIX"],
                out var requireTenantPrefix)
                ? requireTenantPrefix
                : options.RequireTenantPrefix;
            options.MaxUploadBytes = long.TryParse(
                configuration["STORAGE_MAX_UPLOAD_BYTES"],
                out var maxUploadBytes)
                ? maxUploadBytes
                : options.MaxUploadBytes;
        });
        services.PostConfigure<AliyunOssOptions>(options =>
        {
            options.Region = configuration["ALIYUN_OSS_REGION"] ?? options.Region;
            options.Endpoint = configuration["ALIYUN_OSS_ENDPOINT"] ?? options.Endpoint;
            options.AccessKeyId = configuration["ALIYUN_OSS_ACCESS_KEY_ID"] ?? options.AccessKeyId;
            options.AccessKeySecret = configuration["ALIYUN_OSS_ACCESS_KEY_SECRET"] ?? options.AccessKeySecret;
            options.SecurityToken = configuration["ALIYUN_OSS_STS_TOKEN"] ?? options.SecurityToken;
            options.UseInternalEndpoint = bool.TryParse(
                configuration["ALIYUN_OSS_INTERNAL"],
                out var useInternalEndpoint)
                ? useInternalEndpoint
                : options.UseInternalEndpoint;
        });
        services.PostConfigure<S3CompatibleOptions>(options =>
        {
            options.Endpoint = configuration["S3_ENDPOINT"] ?? options.Endpoint;
            options.AccessKey = configuration["S3_ACCESS_KEY"] ?? options.AccessKey;
            options.SecretKey = configuration["S3_SECRET_KEY"] ?? options.SecretKey;
            options.Region = configuration["S3_REGION"] ?? options.Region;
            options.Secure = bool.TryParse(configuration["S3_SECURE"], out var secure)
                ? secure
                : options.Secure;
        });
        services.AddOptions<ObjectStorageOptions>()
            .Validate(
                options => !environment.IsProduction() ||
                           options.DefaultProvider == ObjectStorageProviders.AliyunOss,
                "Production managed storage must use the configured Aliyun OSS provider.")
            .ValidateOnStart();
        services.AddOptions<AliyunOssOptions>()
            .Validate<IOptions<ObjectStorageOptions>>(
                (aliyun, storage) =>
                    !environment.IsProduction() ||
                    storage.Value.DefaultProvider != ObjectStorageProviders.AliyunOss ||
                    aliyun.IsConfigured,
                "Aliyun OSS credentials and region or endpoint are required when it is the default provider.")
            .ValidateOnStart();
        services.AddOptions<S3CompatibleOptions>()
            .Validate<IOptions<ObjectStorageOptions>>(
                (s3, storage) =>
                    storage.Value.DefaultProvider != ObjectStorageProviders.LocalDev || s3.IsConfigured,
                "S3-compatible endpoint and credentials are required when local_dev is the default provider.")
            .ValidateOnStart();
        services.AddOptions<ClamAvOptions>()
            .Bind(configuration.GetSection(ClamAvOptions.SectionName))
            .Validate(ClamAvOptions.BeValid, "ClamAV settings are invalid.")
            .Validate<IOptions<ObjectStorageOptions>>(
                (clamAv, storage) => clamAv.StreamMaxLength >= storage.Value.MaxUploadBytes,
                "ClamAV StreamMaxLength must be greater than or equal to the storage max upload size.")
            .ValidateOnStart();

        services.AddOptions<TenantSecretEncryptionOptions>()
            .Bind(configuration.GetSection(TenantSecretEncryptionOptions.SectionName))
            .PostConfigure(options =>
            {
                options.KeyId = configuration["TIKU_TENANT_SECRET_KEY_ID"] ?? options.KeyId;
                options.MasterKey = configuration["TIKU_TENANT_SECRET_MASTER_KEY"] ?? options.MasterKey;
            })
            .Validate(
                TenantSecretEncryptionOptions.BeValid,
                "Tenant secret encryption requires a key ID and a base64-encoded 32-byte master key.")
            .Validate(
                options => !environment.IsProduction() ||
                           !TenantSecretEncryptionOptions.IsDevelopmentDefault(options),
                "Production tenant secret encryption cannot use the development master key.")
            .ValidateOnStart();

        services.AddOptions<SmsSecurityOptions>()
            .Bind(configuration.GetSection(SmsSecurityOptions.SectionName))
            .PostConfigure(options =>
            {
                options.CodePepper = configuration["TIKU_SMS_CODE_PEPPER"] ?? options.CodePepper;
            })
            .Validate(
                SmsSecurityOptions.BeValid,
                "SMS security requires a pepper of at least 32 characters, exactly five verification attempts, " +
                "and positive tenant, phone, IP, and device rate limits.")
            .ValidateOnStart();

        return services;
    }

    private static string[] SplitLegacyList(string? value, string[] fallback)
    {
        return string.IsNullOrWhiteSpace(value)
            ? fallback
            : value.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
    }

    private static bool HasConfiguredAliyunOss(IConfiguration configuration)
    {
        return !string.IsNullOrWhiteSpace(configuration["ALIYUN_OSS_ACCESS_KEY_ID"] ??
                                          configuration["Storage:AliyunOss:AccessKeyId"]) &&
               !string.IsNullOrWhiteSpace(configuration["ALIYUN_OSS_ACCESS_KEY_SECRET"] ??
                                          configuration["Storage:AliyunOss:AccessKeySecret"]) &&
               (!string.IsNullOrWhiteSpace(configuration["ALIYUN_OSS_REGION"] ??
                                            configuration["Storage:AliyunOss:Region"]) ||
                !string.IsNullOrWhiteSpace(configuration["ALIYUN_OSS_ENDPOINT"] ??
                                            configuration["Storage:AliyunOss:Endpoint"]));
    }
}
