using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Configuration;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.AI;
using GeminiDotnet;
using GeminiDotnet.Extensions.AI;
using NexusReader.Data.Persistence;

using NexusReader.Application.Abstractions.Messaging;
using NexusReader.Application.Abstractions.Persistence;
using NexusReader.Application.Abstractions.Services;
using NexusReader.Infrastructure.Persistence;
using NexusReader.Infrastructure.RealTime;
using NexusReader.Infrastructure.Services;
using NexusReader.Infrastructure.Configuration;
using Polly;
using Polly.Retry;
using NexusReader.Domain.Entities;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Authorization;
using NexusReader.Application.Security.Authorization;
using Qdrant.Client;
using Neo4j.Driver;
using Hangfire;
using Hangfire.PostgreSql;

namespace NexusReader.Infrastructure;

public static class DependencyInjection
{
    public static IServiceCollection AddInfrastructure(this IServiceCollection services, IConfiguration configuration)
    {
        var pgConnectionString = configuration.GetConnectionString("PostgresConnection");
        if (!string.IsNullOrEmpty(pgConnectionString))
        {
            services.AddDbContextFactory<AppDbContext>(options =>
                options.UseNpgsql(pgConnectionString),
                ServiceLifetime.Scoped);

            // Also register a scoped DbContext for repositories that need it
            services.AddDbContext<AppDbContext>(options =>
                options.UseNpgsql(pgConnectionString));
        }
        else
        {
            var sqliteConnectionString = configuration.GetConnectionString("SqliteConnection") ?? "Data Source=nexus.db";
            services.AddDbContextFactory<AppDbContext>(options =>
                options.UseSqlite(sqliteConnectionString),
                ServiceLifetime.Scoped);

            services.AddDbContext<AppDbContext>(options =>
                options.UseSqlite(sqliteConnectionString));
        }

        // Qdrant Client registration
        var qdrantUrl = configuration.GetConnectionString("QdrantConnection") ?? "http://localhost:6334";
        services.AddSingleton<QdrantClient>(sp => new QdrantClient(new Uri(qdrantUrl)));

        // Neo4j Driver registration (supports optional authentication)
        var neo4jUrl = configuration.GetConnectionString("Neo4jConnection") ?? "bolt://localhost:7687";
        var neo4jUser = configuration["Neo4j:Username"];
        var neo4jPass = configuration["Neo4j:Password"];
        var neo4jAuth = !string.IsNullOrEmpty(neo4jUser)
            ? AuthTokens.Basic(neo4jUser, neo4jPass ?? string.Empty)
            : AuthTokens.None;
        services.AddSingleton<IDriver>(sp => GraphDatabase.Driver(neo4jUrl, neo4jAuth));

        // Hangfire registration
        if (!string.IsNullOrEmpty(pgConnectionString))
        {
            services.AddHangfire(config => config
                .UseRecommendedSerializerSettings()
                .UsePostgreSqlStorage(options => options.UseNpgsqlConnection(pgConnectionString)));
            services.AddHangfireServer();
        }

        services.Configure<AiSettings>(configuration.GetSection(AiSettings.SectionName));
        services.Configure<StripeSettings>(configuration.GetSection(StripeSettings.SectionName));
        var aiSettings = configuration.GetSection(AiSettings.SectionName).Get<AiSettings>() ?? new AiSettings();

        if (string.IsNullOrWhiteSpace(aiSettings.ApiKey) || aiSettings.ApiKey == "PLACEHOLDER")
        {
            Console.WriteLine("[Infrastructure] WARNING: AI API Key is missing or placeholder!");
        }

        services.AddResiliencePipeline("ai-retry", builder =>
        {
            builder.AddRetry(new RetryStrategyOptions
            {
                ShouldHandle = new PredicateBuilder().Handle<Exception>(ex =>
                    ex.Message.Contains("429") || 
                    ex.Message.Contains("Too Many Requests") || 
                    ex.Message.Contains("quota") ||
                    ex.Message.Contains("503") ||
                    ex.Message.Contains("ServiceUnavailable") ||
                    ex.Message.Contains("demand")),
                BackoffType = DelayBackoffType.Exponential,
                UseJitter = true,
                MaxRetryAttempts = aiSettings.RetryAttempts,
                Delay = TimeSpan.FromSeconds(2)
            });
        });

        services.AddChatClient(new GeminiChatClient(new GeminiClientOptions
        {
            ApiKey = aiSettings.ApiKey,
            ModelId = aiSettings.Model
        }));

        services.AddEmbeddingGenerator(new GeminiEmbeddingGenerator(new GeminiClientOptions
        {
            ApiKey = aiSettings.ApiKey,
            ModelId = aiSettings.EmbeddingModel ?? "gemini-embedding-001"
        }));

        // Application-layer service implementations
        services.AddScoped<IKnowledgeService, KnowledgeService>();
        services.AddTransient<IEpubReader, EpubReaderService>();
        services.AddTransient<IEpubMetadataExtractor, EpubMetadataExtractor>();
        services.AddTransient<IEpubExtractor, EpubExtractor>();

        // Fix #4: Scoped instead of Singleton — BookStorageService uses path resolution
        // that is environment-specific and incompatible with Singleton lifetime in MAUI.
        services.AddScoped<IBookStorageService, BookStorageService>();

        // Fix #1: Ebook repository (scoped, matches AppDbContext lifetime)
        services.AddScoped<IEbookRepository, EbookRepository>();
        services.AddScoped<IQuizResultRepository, QuizResultRepository>();
        services.AddScoped<IConceptsMapReadRepository, ConceptsMapReadRepository>();

        // Fix #2: SignalR broadcaster (scoped, wraps IHubContext which is itself a singleton wrapper)
        services.AddScoped<ISyncBroadcaster, SignalRSyncBroadcaster>();

        services.AddAuthorizationCore(options =>
        {
            options.AddPolicy("ProUser", policy => policy.Requirements.Add(new ProUserRequirement()));
        });

        services.AddScoped<IAuthorizationHandler, ProUserHandler>();
        services.AddScoped<IInfrastructureMarker, InfrastructureMarker>();

        return services;
    }

    public static System.Reflection.Assembly Assembly => typeof(DependencyInjection).Assembly;
}

public interface IInfrastructureMarker { }
internal class InfrastructureMarker : IInfrastructureMarker { }
