using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.RateLimiting;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Microsoft.Extensions.Http.Resilience;
using Microsoft.IdentityModel.Tokens;
using ModelContextProtocol.AspNetCore;
using Nexus.Api.Data;
using Nexus.Api.Integrations;
using Nexus.Api.Http;
using Nexus.Api.RateLimiting;
using Nexus.Api.Repositories;
using Nexus.Api.Routing;
using Nexus.Api.Services;
using System.IdentityModel.Tokens.Jwt;
using System.Net;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading.RateLimiting;

namespace Nexus.Api.Extensions;

/// <summary>
/// Extension methods for registering Nexus application services in the DI container.
/// </summary>
public static class ServiceCollectionExtensions
{
    /// <summary>
    /// Configures JWT authentication and authorization.
    /// </summary>
    public static IServiceCollection AddNexusAuth(this IServiceCollection services, IConfiguration configuration)
    {
        var jwtKey = configuration["Jwt:Key"];
        var jwtIssuer = configuration["Jwt:Issuer"] ?? "nexus";
        var jwtAudience = configuration["Jwt:Audience"] ?? "nexus-web";
        if (string.IsNullOrWhiteSpace(jwtKey) || Encoding.UTF8.GetByteCount(jwtKey) < 32)
            throw new InvalidOperationException("Jwt:Key must be configured with at least 32 bytes.");

        services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
            .AddJwtBearer(options =>
            {
                options.MapInboundClaims = false;
                options.TokenValidationParameters = new TokenValidationParameters
                {
                    ValidateIssuer = true,
                    ValidateAudience = true,
                    ValidateLifetime = true,
                    ValidateIssuerSigningKey = true,
                    ValidIssuer = jwtIssuer,
                    ValidAudience = jwtAudience,
                    IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtKey)),
                    NameClaimType = JwtRegisteredClaimNames.Sub,
                    RoleClaimType = System.Security.Claims.ClaimTypes.Role,
                    ClockSkew = TimeSpan.FromSeconds(30)
                };
            });

        services.AddAuthorization(options =>
        {
            options.FallbackPolicy = new AuthorizationPolicyBuilder()
                .RequireAuthenticatedUser()
                .Build();
        });
        return services;
    }

    /// <summary>
    /// Configures rate limiting policies (auth and agents).
    /// </summary>
    public static IServiceCollection AddNexusRateLimiting(this IServiceCollection services)
    {
        services.AddRateLimiter(options =>
        {
            options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;

            options.OnRejected = async (context, ct) =>
            {
                context.HttpContext.Response.StatusCode = StatusCodes.Status429TooManyRequests;
                context.HttpContext.Response.Headers.ContentType = "application/problem+json";

                var retryAfterSeconds = 60;

                // Try to read retry-after info from the metadata
                if (context.Lease.TryGetMetadata(MetadataName.RetryAfter, out var retryAfter))
                {
                    retryAfterSeconds = (int)retryAfter.TotalSeconds;
                }

                // Set standard headers
                context.HttpContext.Response.Headers.RetryAfter = retryAfterSeconds.ToString();
                context.HttpContext.Response.Headers["X-RateLimit-Remaining"] = "0";
                context.HttpContext.Response.Headers["X-RateLimit-Reset"] =
                    DateTimeOffset.UtcNow.AddSeconds(retryAfterSeconds).ToUnixTimeSeconds().ToString();

                var body = new ProblemDetails
                {
                    Type = "https://httpstatuses.com/429",
                    Title = "Rate limit exceeded",
                    Status = StatusCodes.Status429TooManyRequests,
                    Detail = $"Too many attempts. Try again in {retryAfterSeconds} second(s)."
                };
                body.Extensions["code"] = NexusProblemCodes.RateLimited;
                body.Extensions["remaining"] = 0;
                body.Extensions["retryAfterSeconds"] = retryAfterSeconds;
                body.Extensions["traceId"] =
                    System.Diagnostics.Activity.Current?.Id
                    ?? context.HttpContext.TraceIdentifier;

                await context.HttpContext.Response.WriteAsJsonAsync(body, ct);
            };

            options.AddPolicy("auth", context => RateLimitPartition.GetFixedWindowLimiter(
                context.Connection.RemoteIpAddress?.ToString() ?? "unknown",
                _ => new FixedWindowRateLimiterOptions
                {
                    PermitLimit = 5,
                    Window = TimeSpan.FromMinutes(1),
                    QueueLimit = 0,
                    AutoReplenishment = true
                }));

            options.AddPolicy("agents", context => RateLimitPartition.GetFixedWindowLimiter(
                context.Connection.RemoteIpAddress?.ToString() ?? "unknown",
                _ => new FixedWindowRateLimiterOptions
                {
                    PermitLimit = 30,
                    Window = TimeSpan.FromMinutes(1),
                    QueueLimit = 0,
                    AutoReplenishment = true
                }));
        });

        return services;
    }

    /// <summary>
    /// Configures forwarded headers for reverse proxy scenarios.
    /// </summary>
    public static IServiceCollection AddNexusForwardedHeaders(
        this IServiceCollection services,
        IConfiguration configuration)
    {
        services.Configure<ForwardedHeadersOptions>(options =>
        {
            options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
            var forwardLimit = configuration.GetValue<int?>("ForwardedHeaders:ForwardLimit") ?? 1;
            if (forwardLimit is < 1 or > 5)
                throw new InvalidOperationException("ForwardedHeaders:ForwardLimit must be between 1 and 5.");
            options.ForwardLimit = forwardLimit;

            foreach (var configuredProxy in configuration
                         .GetSection("ForwardedHeaders:KnownProxies")
                         .Get<string[]>() ?? [])
            {
                if (string.IsNullOrWhiteSpace(configuredProxy))
                    continue;

                if (!IPAddress.TryParse(configuredProxy, out var proxy))
                    throw new InvalidOperationException(
                        $"ForwardedHeaders:KnownProxies contains invalid IP address '{configuredProxy}'.");

                options.KnownProxies.Add(proxy);
            }

            foreach (var configuredNetwork in configuration
                         .GetSection("ForwardedHeaders:KnownNetworks")
                         .Get<string[]>() ?? [])
            {
                if (string.IsNullOrWhiteSpace(configuredNetwork))
                    continue;

                if (!System.Net.IPNetwork.TryParse(configuredNetwork, out var network))
                    throw new InvalidOperationException(
                        $"ForwardedHeaders:KnownNetworks contains invalid CIDR '{configuredNetwork}'.");

                options.KnownIPNetworks.Add(network);
            }
        });

        return services;
    }

    /// <summary>
    /// Configures Swagger and JSON serialization options.
    /// </summary>
    public static IServiceCollection AddNexusSwagger(this IServiceCollection services)
    {
        services.AddEndpointsApiExplorer();
        services.AddSwaggerGen();
        services.ConfigureHttpJsonOptions(options =>
            options.SerializerOptions.Converters.Add(new JsonStringEnumConverter()));

        return services;
    }

    /// <summary>
    /// Registers the Entity Framework Core DbContext with Npgsql.
    /// </summary>
    public static IServiceCollection AddNexusDatabase(this IServiceCollection services, IConfiguration configuration)
    {
        services.AddDbContext<NexusDbContext>(options =>
            options.UseNpgsql(configuration.GetConnectionString("Nexus"))
                   .ConfigureWarnings(w => w.Ignore(
                       Microsoft.EntityFrameworkCore.Diagnostics.RelationalEventId.PendingModelChangesWarning)));

        return services;
    }

    /// <summary>
    /// Registers typed and named HTTP clients for OpenClaw integration.
    /// </summary>
    public static IServiceCollection AddNexusHttpClients(this IServiceCollection services, IConfiguration configuration)
    {
        var runtimeReadClient = services.AddHttpClient<IAgentRuntime, OpenClawRuntime>(client =>
        {
            client.BaseAddress = new(configuration["Integrations:OpenClaw:BaseUrl"]
                ?? "http://127.0.0.1:18789");
            client.Timeout = Timeout.InfiniteTimeSpan;
        });
        AddOpenClawReadResilience(runtimeReadClient);

        var gatewayReadClient = services.AddHttpClient("gateway", client =>
        {
            client.BaseAddress = new(configuration["Integrations:OpenClaw:BaseUrl"]
                ?? "http://127.0.0.1:18789");
            client.Timeout = Timeout.InfiniteTimeSpan;
        });
        AddOpenClawReadResilience(gatewayReadClient);

        var historyReadClient = services.AddHttpClient<IOpenClawGatewayClient, OpenClawGatewayClient>(client =>
        {
            client.BaseAddress = new(configuration["Integrations:OpenClaw:BaseUrl"]
                ?? "http://127.0.0.1:18789");
            client.Timeout = Timeout.InfiniteTimeSpan;
        });
        AddOpenClawReadResilience(historyReadClient);

        return services;
    }

    private static void AddOpenClawReadResilience(IHttpClientBuilder client)
    {
        client.AddStandardResilienceHandler(options =>
        {
            options.RateLimiter.DefaultRateLimiterOptions.PermitLimit = 4;
            options.RateLimiter.DefaultRateLimiterOptions.QueueLimit = 0;
            options.AttemptTimeout.Timeout = TimeSpan.FromSeconds(10);
            options.TotalRequestTimeout.Timeout = TimeSpan.FromSeconds(30);
            options.Retry.MaxRetryAttempts = 2;
            options.Retry.Delay = TimeSpan.FromMilliseconds(250);
            options.Retry.UseJitter = true;
            options.Retry.DisableForUnsafeHttpMethods();
            options.CircuitBreaker.FailureRatio = 0.5;
            options.CircuitBreaker.MinimumThroughput = 4;
            options.CircuitBreaker.SamplingDuration = TimeSpan.FromSeconds(30);
            options.CircuitBreaker.BreakDuration = TimeSpan.FromSeconds(30);
        });
    }

    /// <summary>
    /// Registers application domain services (transient, scoped, singleton).
    /// </summary>
    public static IServiceCollection AddNexusApplicationServices(
        this IServiceCollection services,
        bool includeHostedServices = true,
        bool includeMcp = true)
    {
        if (includeMcp)
        {
            services.AddMcpServer()
                .WithHttpTransport(options => options.Stateless = true)
                .WithTools<NexusMcpTools>();
        }

        services.AddOptions<StaleTaskRecoveryOptions>()
            .BindConfiguration(StaleTaskRecoveryOptions.SectionName);
        services.AddOptions<AgentProvisioningOptions>()
            .BindConfiguration(AgentProvisioningOptions.SectionName);
        services.AddHttpContextAccessor();
        services.AddSingleton<LoginAttemptTracker>();
        services.AddTransient<ModelRoutingService>();
        services.AddScoped<IAuthService, AuthService>();
        services.AddScoped<IAgentService, AgentService>();
        services.AddScoped<IDashboardService, DashboardService>();
        services.AddScoped<IProjectService, ProjectService>();
        services.AddScoped<ITaskService, TaskService>();
        services.AddScoped<IOperationsService, OperationsService>();
        services.AddScoped<ITeamService, TeamService>();
        services.AddScoped<IMemoryService, MemoryService>();
        services.AddScoped<IIncidentService, IncidentService>();
        services.AddScoped<IDocService, DocService>();
        services.AddSingleton<ILiveUpdateService, LiveUpdateService>();
        services.AddSingleton<DomainEventStreamService>();
        services.AddSingleton<IDomainEventStreamService>(serviceProvider =>
            serviceProvider.GetRequiredService<DomainEventStreamService>());
        services.AddScoped<INotificationService, NotificationService>();
        services.AddScoped<ICalendarService, CalendarService>();
        services.AddScoped<IOpenClawControlService, OpenClawControlService>();
        services.AddScoped<IOpenClawAgentConfigurationService, OpenClawAgentConfigurationService>();
        services.AddScoped<IOpenClawSetupService, OpenClawSetupService>();
        services.AddSingleton<IOpenClawWizardService, OpenClawWizardService>();
        services.AddSingleton<IOpenClawManagementState, OpenClawManagementState>();
        services.AddSingleton<IOpenClawWriteGate, OpenClawWriteGate>();
        services.AddSingleton<IOpenClawEventProjectionService, OpenClawEventProjectionService>();
        services.AddSingleton<IOpenClawRunGateway, OpenClawRunGateway>();
        services.AddScoped<IOpenClawRunService, OpenClawRunService>();
        services.AddScoped<IOpenClawChatService, OpenClawChatService>();
        services.AddScoped<IAgentProposalService, AgentProposalService>();
        services.AddSingleton<AgentProvisioningSignal>();
        services.AddScoped<IStaleTaskRecoveryService, StaleTaskRecoveryService>();

        // ── Gateway WebSocket Connector ──
        services.AddOptions<GatewayConnectorOptions>()
            .BindConfiguration(GatewayConnectorOptions.SectionName);
        services.AddSingleton<IOpenClawDeviceIdentityStore, OpenClawDeviceIdentityStore>();
        services.AddSingleton<
            IOpenClawOperationAuditStore,
            PostgresOpenClawOperationAuditStore>();
        services.AddSingleton<IGatewayConnector, GatewayConnector>();

        if (includeHostedServices)
        {
            services.AddHostedService<OpenClawManagementStateInitializer>();
            services.AddHostedService(serviceProvider =>
                serviceProvider.GetRequiredService<DomainEventStreamService>());
            services.AddHostedService<AgentProvisioningWorker>();
            services.AddHostedService<StaleTaskRecoveryBackgroundService>();
            services.AddHostedService(serviceProvider =>
                (GatewayConnector)serviceProvider.GetRequiredService<IGatewayConnector>());
            services.AddHostedService<OpenClawEventSubscriptionCoordinator>();
            services.AddHostedService<OpenClawRunEventReconciler>();
        }

        // ── Backend Bridge (Agent-Command-Service) ──
        services.AddScoped<ITaskBridgeService, TaskBridgeService>();

        return services;
    }

    /// <summary>
    /// Registers data repositories.
    /// </summary>
    public static IServiceCollection AddNexusRepositories(this IServiceCollection services)
    {
        services.AddScoped<IUserRepository, UserRepository>();
        services.AddScoped<IProjectRepository, ProjectRepository>();
        services.AddScoped<ITaskRepository, TaskRepository>();
        services.AddScoped<IActivityRepository, ActivityRepository>();
        services.AddScoped<IOpenClawRunRepository, OpenClawRunRepository>();
        services.AddScoped<IOpenClawConnectionProfileRepository, OpenClawConnectionProfileRepository>();

        return services;
    }

    /// <summary>
    /// Configures health checks (PostgreSQL connectivity and runtime status).
    /// </summary>
    public static IServiceCollection AddNexusHealthChecks(this IServiceCollection services, IConfiguration configuration)
    {
        services.AddHealthChecks()
            .AddNpgSql(
                configuration.GetConnectionString("Nexus")!,
                name: "postgresql",
                tags: ["database", "ready"])
            .AddCheck("runtime", () => HealthCheckResult.Healthy("Runtime configured"), tags: ["runtime"]);

        return services;
    }
}
