using System.Text;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;
using TeleWave.Application.Broadcast.Bumpers;
using TeleWave.Application.Broadcast.Scheduling;
using TeleWave.Application.Common;
using TeleWave.Application.Common.Interfaces;
using TeleWave.Application.Streaming;
using TeleWave.Domain.Broadcast.Scheduling;
using TeleWave.Infrastructure.Broadcast;
using TeleWave.Infrastructure.Identity;
using TeleWave.Infrastructure.Library;
using TeleWave.Infrastructure.Media;
using TeleWave.Infrastructure.Metadata;
using TeleWave.Infrastructure.Persistence;
using TeleWave.Infrastructure.Settings;
using TeleWave.Infrastructure.Streaming;
using TeleWave.Infrastructure.Support;

namespace TeleWave.Infrastructure;

/// <summary>
/// Регистрация сервисов инфраструктуры: EF Core (PostgreSQL), Identity/JWT-аутентификация.
/// </summary>
public static class DependencyInjection
{
    public static IServiceCollection AddInfrastructure(
        this IServiceCollection services,
        IConfiguration configuration
    )
    {
        services.AddDbContext<AppDbContext>(options =>
            options.UseNpgsql(
                configuration["ConnectionStrings:Default"]
                    ?? throw new InvalidOperationException(
                        "Строка подключения 'ConnectionStrings:Default' не сконфигурирована."
                    )
            )
        );
        services.AddScoped<IAppDbContext>(sp => sp.GetRequiredService<AppDbContext>());

        services
            .AddIdentityCore<AppUser>(options =>
            {
                options.User.RequireUniqueEmail = false;
                options.Password.RequiredLength = 8;
                options.Password.RequireDigit = true;
                options.Password.RequireUppercase = true;
                options.Password.RequireNonAlphanumeric = false;
                options.Lockout.MaxFailedAccessAttempts = 5;
                options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5);
                options.Lockout.AllowedForNewUsers = true;
            })
            .AddRoles<AppRole>()
            .AddEntityFrameworkStores<AppDbContext>()
            .AddSignInManager()
            .AddDefaultTokenProviders();

        services.Configure<JwtOptions>(configuration.GetSection(JwtOptions.SectionName));
        services.Configure<AdminSeedOptions>(
            configuration.GetSection(AdminSeedOptions.SectionName)
        );

        var jwtOptions =
            configuration.GetSection(JwtOptions.SectionName).Get<JwtOptions>()
            ?? throw new InvalidOperationException("Секция конфигурации 'Jwt' не задана.");

        // Fail-fast на подписывающем ключе: этот же ключ подписывает и JWT, и stream-токены
        // (StreamTokenService), поэтому placeholder/короткий ключ из appsettings.json = полный обход
        // авторизации (можно сфорджить admin-JWT). Лучше не стартовать вовсе, чем стартовать уязвимым.
        // HMAC-SHA256 требует ключ не короче размера хеша (32 байта), иначе он ослаблен нулевым паддингом.
        if (Encoding.UTF8.GetByteCount(jwtOptions.SigningKey) < 32)
            throw new InvalidOperationException(
                "Jwt:SigningKey должен быть не короче 32 байт. Задайте криптостойкий секрет через конфигурацию/переменную окружения Jwt__SigningKey."
            );
        if (jwtOptions.SigningKey.Contains("change-me", StringComparison.OrdinalIgnoreCase))
            throw new InvalidOperationException(
                "Jwt:SigningKey использует значение-заглушку из appsettings.json. Переопределите его криптостойким секретом (Jwt__SigningKey)."
            );

        services
            .AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
            .AddJwtBearer(options =>
            {
                options.TokenValidationParameters = new TokenValidationParameters
                {
                    ValidateIssuer = true,
                    ValidIssuer = jwtOptions.Issuer,
                    ValidateAudience = true,
                    ValidAudience = jwtOptions.Audience,
                    ValidateIssuerSigningKey = true,
                    IssuerSigningKey = new SymmetricSecurityKey(
                        Encoding.UTF8.GetBytes(jwtOptions.SigningKey)
                    ),
                    ValidateLifetime = true,
                    ClockSkew = TimeSpan.FromSeconds(30),
                };
            });

        services.AddAuthorization();

        services.AddScoped<IIdentityService, IdentityService>();
        services.AddScoped<IJwtTokenService, JwtTokenService>();
        services.AddScoped<IRefreshTokenService, RefreshTokenService>();
        services.AddScoped<IRoleService, RoleService>();
        services.AddScoped<ICurrentUser, CurrentUser>();
        services.AddScoped<ISiteSettings, SiteSettings>();
        services.AddScoped<DbInitializer>();
        services.AddScoped<GenreSeeder>();

        AddMedia(services, configuration);
        AddBroadcast(services, configuration);
        AddMetadata(services, configuration);

        return services;
    }

    /// <summary>Метаданные шоу/серий: провайдеры TMDb/OMDb, резолвер, локальное хранилище картинок.</summary>
    private static void AddMetadata(IServiceCollection services, IConfiguration configuration)
    {
        services.Configure<MetadataOptions>(configuration.GetSection(MetadataOptions.SectionName));
        services.AddHttpClient("metadata", client => client.Timeout = TimeSpan.FromSeconds(15));
        services.AddSingleton<IMetadataProvider, TmdbMetadataProvider>();
        services.AddSingleton<IMetadataProvider, OmdbMetadataProvider>();
        services.AddSingleton<IMetadataProviderResolver, MetadataProviderResolver>();
    }

    /// <summary>Планировщик расписания: генератор, источник случайности и фоновый сервис горизонта.</summary>
    private static void AddBroadcast(IServiceCollection services, IConfiguration configuration)
    {
        services.Configure<SchedulerOptions>(
            configuration.GetSection(SchedulerOptions.SectionName)
        );
        services.Configure<StreamingOptions>(
            configuration.GetSection(StreamingOptions.SectionName)
        );
        services.Configure<BumperOptions>(configuration.GetSection(BumperOptions.SectionName));

        services.AddSingleton<IRandomSource, SystemRandomSource>();
        services.AddSingleton<StreamTokenService>();
        services.AddSingleton<IBumperRenderer, FfmpegBumperRenderer>();
        services.AddHostedService<SchedulingBackgroundService>();
        services.AddHostedService<MaintenanceBackgroundService>();
    }

    /// <summary>Хранилище медиа, обработка ffmpeg, очередь и фоновые сервисы (очередь + inbox-сканер).</summary>
    private static void AddMedia(IServiceCollection services, IConfiguration configuration)
    {
        services.Configure<StorageOptions>(configuration.GetSection(StorageOptions.SectionName));
        services.Configure<DebugExportOptions>(
            configuration.GetSection(DebugExportOptions.SectionName)
        );
        services.AddSingleton<IDebugExportStore, DebugExportStore>();
        services.Configure<MediaOptions>(configuration.GetSection(MediaOptions.SectionName));

        services.AddSingleton<MediaPathResolver>();
        services.AddSingleton<IMediaStorage, FileSystemMediaStorage>();
        services.AddSingleton<IBumperTemplateStorage, BumperTemplateStorage>();
        // Синглтон: обход дерева дорогой, и кэш результата живёт в самой реализации.
        services.AddSingleton<IStorageInspector, FileSystemStorageInspector>();
        services.AddSingleton<IImageStore, ImageStore>();
        services.AddSingleton<IImageDownloader, ImageDownloader>();
        services.AddSingleton<IAudioProbe, FfprobeAudioProbe>();
        services.AddSingleton<IMediaProcessor, FfmpegMediaProcessor>();
        services.AddSingleton<IMediaProcessingQueue, MediaProcessingQueue>();
        services.AddSingleton<IMediaProcessingLimits, MediaProcessingLimits>();
        services.AddSingleton<IBumperRenderQueue, BumperRenderQueue>();

        services.AddHostedService<MediaProcessingBackgroundService>();
        services.AddHostedService<InboxScannerBackgroundService>();
        services.AddHostedService<UploadsCleanupBackgroundService>();
        services.AddHostedService<BumperRenderBackgroundService>();
    }
}
