using System.IO.Compression;
using Microsoft.AspNetCore.ResponseCompression;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Serilog;
using Tiku.Api.Caching;
using Tiku.Api.Options;
using Tiku.Application;
using Tiku.Application.Security;
using Tiku.Application.Tenancy;
using Tiku.Infrastructure;
using Tiku.Infrastructure.Security;

namespace Tiku.Api.Configuration;

public static class DependencyInjection
{
    public static WebApplicationBuilder AddApiServices(this WebApplicationBuilder builder)
    {
        builder.Services.AddSerilog((services, configuration) => configuration
                .ReadFrom.Configuration(builder.Configuration)
                .ReadFrom.Services(services)
                .Enrich.FromLogContext(),
            true);

        builder.Services.AddApiPresentation();
        builder.Services.AddHealthChecks();
        builder.Services.AddApiObservability(builder.Configuration, builder.Environment);
        builder.Services.AddApplication();
        builder.Services.AddNetworkConfiguration(builder.Configuration, builder.Environment);
        builder.Services.AddApiRateLimiting(builder.Configuration);

        var connectionString = OptionsValidation.ResolveDatabaseConnectionString(
            builder.Configuration,
            builder.Environment.IsDevelopment());
        builder.Services.AddInfrastructure(connectionString);
        var redisConnectionString =
            builder.Configuration.GetConnectionString("Redis") ?? builder.Configuration["REDIS_URL"];
        builder.Services.AddOptions<RedisSecurityConnectionOptions>()
            .Configure(options => options.ConnectionString = redisConnectionString ?? string.Empty)
            .Validate(
                options => !builder.Environment.IsProduction() || !string.IsNullOrWhiteSpace(options.ConnectionString),
                "Redis is required in Production.")
            .ValidateOnStart();
        builder.Services.AddOptions<AuthorizationCacheOptions>()
            .Bind(builder.Configuration.GetSection(AuthorizationCacheOptions.SectionName))
            .Validate(options => options.LocalSnapshotSeconds > 0 && options.DistributedStateSeconds > 0 &&
                                 options.DistributedSnapshotSeconds > 0 && options.JitterPercent is >= 0 and <= 50,
                "Authorization cache durations must be positive and jitter must be between 0 and 50 percent.")
            .ValidateOnStart();
        if (!string.IsNullOrWhiteSpace(redisConnectionString))
            builder.Services.AddRedisSecurity(redisConnectionString, builder.Environment.EnvironmentName);
        else if (builder.Environment.IsProduction())
            throw new InvalidOperationException(
                "Redis is required in Production. Configure ConnectionStrings:Redis or REDIS_URL.");

        builder.Services.AddOutputCache(options =>
        {
            options.AddPolicy("TenantPublic", new TenantPublicOutputCachePolicy());
            options.DefaultExpirationTimeSpan = TimeSpan.FromSeconds(60);
        });
        builder.Services.RemoveAll<ITenantPublicCacheInvalidator>();
        builder.Services.AddSingleton<ITenantPublicCacheInvalidator, TenantPublicCacheInvalidator>();
        if (builder.Environment.IsProduction() && !string.IsNullOrWhiteSpace(redisConnectionString))
            builder.Services.AddStackExchangeRedisOutputCache(options =>
            {
                options.Configuration = redisConnectionString;
                options.InstanceName = $"tiku:{builder.Environment.EnvironmentName.ToLowerInvariant()}:output:";
            });
        builder.Services.AddResponseCompression(options =>
        {
            options.EnableForHttps = true;
            options.Providers.Add<BrotliCompressionProvider>();
            options.Providers.Add<GzipCompressionProvider>();
            options.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(["application/json"]);
        });
        builder.Services.Configure<BrotliCompressionProviderOptions>(options =>
            options.Level = CompressionLevel.Fastest);
        builder.Services.Configure<GzipCompressionProviderOptions>(options => options.Level = CompressionLevel.Fastest);
        builder.Services.AddApiDataProtection(builder.Configuration, builder.Environment);
        builder.Services.AddExternalServiceOptions(builder.Configuration, builder.Environment);
        builder.Services.AddApiAuthenticationAndAuthorization(builder.Configuration, builder.Environment);

        return builder;
    }
}