using Books.Api.Authentication;
using Books.Api.EventFlow.Extensions;
using Books.Api.EventFlow.Infrastructure;
using Books.Api.GraphQL;
using Books.Api.Infrastructure;
using Books.Api.Logging;
using EventFlow;
using EventFlow.Configuration;
using EventFlow.Extensions;
using EventFlow.Hangfire.Extensions;
using EventFlow.PostgreSql.Connections;
using EventFlow.PostgreSql.Extensions;
using EventFlow.ReadStores;
using EventFlow.Subscribers;
using GraphQL;
using Hangfire;
using Hangfire.PostgreSql;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Npgsql;

namespace Books.Api;

public static class Startup
{
    public static void ConfigureServices(IServiceCollection services, IConfiguration config, IHostEnvironment? environment = null)
    {
        var connectionString = config.GetConnectionString("Default")
            ?? throw new InvalidOperationException("Connection string 'Default' not found");

        // Run database migrations (skipped in Test environment where migrations run separately with test connection string)
        var isTestEnvironment = environment?.EnvironmentName == "Test";
        if (!isTestEnvironment)
        {
            DatabaseMigrator.Migrate(connectionString);
        }

        // PostgreSQL data source
        var dataSource = new NpgsqlDataSourceBuilder(connectionString).Build();
        services.AddSingleton(dataSource);

        // Hangfire
        services.AddHangfire(c => c
            .SetDataCompatibilityLevel(CompatibilityLevel.Version_180)
            .UseSimpleAssemblyNameTypeSerializer()
            .UseRecommendedSerializerSettings()
            .UsePostgreSqlStorage(o => o.UseNpgsqlConnection(connectionString)));
        services.AddHangfireServer();

        // Scheduler abstraction over Hangfire
        services.AddSingleton<IScheduler, HangfireScheduler>();

        // EventFlow
        services.AddEventFlow(o => o
            .UsePostgreSqlEventStore()
            .ConfigurePostgreSql(PostgreSqlConfiguration.New.SetConnectionString(connectionString))
            .AddDefaults(typeof(Startup).Assembly)
            .AddReadModels()
            .Configure(c => c.IsAsynchronousSubscribersEnabled = true)
            .UseHangfireJobScheduler());

        // Resilience strategies
        services.AddSingleton<IDispatchToReadStoresResilienceStrategy, ReadStoresResilienceStrategy>();
        services.AddSingleton<IDispatchToSubscriberResilienceStrategy, DispatchToSubscriberResilienceStrategy>();

        // Read model repositories
        services.AddRepositories();

        // Logging decorators
        services.DecorateAsyncEventHandlersWithLogging();

        // GraphQL
        services.AddGraphQL(builder => builder
            .AddSchema<BooksSchema>()
            .AddSystemTextJson()
            .AddDataLoader()
            .AddGraphTypes(typeof(BooksSchema).Assembly)
            .AddErrorInfoProvider(opt => opt.ExposeExceptionDetails = true));

        // Memory cache for API key caching
        services.AddMemoryCache();

        // Controllers (for AuthController)
        services.AddControllers();

        // CORS
        SetupCors(services, config);

        // Authentication & Authorization
        SetupAuthentication(services, config);
    }

    private static void SetupCors(IServiceCollection services, IConfiguration config)
    {
        var allowedOrigins = config.GetSection("Cors:AllowedOrigins").Get<string[]>()
            ?? ["http://localhost:3000"];

        services.AddCors(options =>
        {
            options.AddDefaultPolicy(builder =>
            {
                builder.WithOrigins(allowedOrigins)
                    .AllowAnyMethod()
                    .AllowAnyHeader()
                    .AllowCredentials();
            });
        });
    }

    private static void SetupAuthentication(IServiceCollection services, IConfiguration config)
    {
        services.AddAuthorization();

        var keycloakClientSecret = config["Keycloak:ClientSecret"];

        // If Keycloak not configured, use cookie-only auth (for development)
        if (string.IsNullOrEmpty(keycloakClientSecret))
        {
            Console.WriteLine("[WARNING] Keycloak:ClientSecret not configured - OIDC authentication disabled");
            services.AddAuthentication(options =>
            {
                options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            })
            .AddCookie(options =>
            {
                options.Cookie.Name = ".Books.Auth";
                options.Cookie.HttpOnly = true;
                options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
                options.Cookie.SameSite = SameSiteMode.Lax;
                options.ExpireTimeSpan = TimeSpan.FromDays(30);
                options.SlidingExpiration = true;
            })
            .AddScheme<ApiKeyAuthenticationOptions, ApiKeyAuthenticationHandler>(
                ApiKeyDefaults.AuthenticationScheme, _ => { });
            return;
        }

        // Full authentication with OIDC
        services.AddAuthentication(options =>
        {
            options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
        })
        .AddCookie(options =>
        {
            options.Cookie.Name = ".Books.Auth";
            options.Cookie.HttpOnly = true;
            options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
            options.Cookie.SameSite = SameSiteMode.None;
            options.ExpireTimeSpan = TimeSpan.FromDays(30);
            options.SlidingExpiration = true;
        })
        .AddScheme<ApiKeyAuthenticationOptions, ApiKeyAuthenticationHandler>(
            ApiKeyDefaults.AuthenticationScheme, _ => { })
        .AddOpenIdConnect(options =>
        {
            options.GetClaimsFromUserInfoEndpoint = true;
            options.MetadataAddress = config["Keycloak:MetadataAddress"]
                ?? "https://id.tatic.io/auth/realms/master/.well-known/openid-configuration";
            options.ClientId = config["Keycloak:ClientId"] ?? "books";
            options.SaveTokens = true;
            options.ClientSecret = keycloakClientSecret;
            options.ResponseType = "code";
            options.Scope.Add("openid");
            options.Scope.Add("email");
            options.Scope.Add("profile");
            options.CallbackPath = "/callback";
            options.UsePkce = true;
        });
    }
}
