using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.AspNetCore.RateLimiting;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Microsoft.IdentityModel.Tokens;
using Nexus.Api.Data;
using Nexus.Api.Integrations;
using Nexus.Api.RateLimiting;
using Nexus.Api.Repositories;
using Nexus.Api.Routing;
using Nexus.Api.Services;
using System.IdentityModel.Tokens.Jwt;
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, authorization, and antiforgery.
    /// </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();
        services.AddAntiforgery(options =>
        {
            options.HeaderName = "X-CSRF-TOKEN";
            options.Cookie.Name = "nexus-csrf";
            options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
            options.Cookie.HttpOnly = false;
        });

        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/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
                {
                    error = "rate_limit_exceeded",
                    message = $"Too many attempts. Try again in {retryAfterSeconds} second(s).",
                    remaining = 0,
                    retryAfterSeconds
                };

                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)
    {
        services.Configure<ForwardedHeadersOptions>(options =>
        {
            options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
            options.KnownIPNetworks.Clear();
            options.KnownProxies.Clear();
        });

        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)
    {
        services.AddHttpClient<IAgentRuntime, OpenClawRuntime>(client =>
        {
            client.BaseAddress = new(configuration["Integrations:OpenClaw:BaseUrl"]
                ?? "http://127.0.0.1:18789");
            client.Timeout = TimeSpan.FromSeconds(120);
        });

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

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

        return services;
    }

    /// <summary>
    /// Registers application domain services (transient, scoped, singleton).
    /// </summary>
    public static IServiceCollection AddNexusApplicationServices(this IServiceCollection services)
    {
        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.AddSingleton<IAgentConfigService, AgentConfigService>();
        services.AddSingleton<IMemoryService, MemoryService>();
        services.AddSingleton<IIncidentService, IncidentService>();
        services.AddSingleton<IDocService, DocService>();
        services.AddScoped<INotificationService, NotificationService>();
        services.AddScoped<ICalendarService, CalendarService>();

        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>();

        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"])
            .AddCheck("runtime", () => HealthCheckResult.Healthy("Runtime configured"), tags: ["runtime"]);

        return services;
    }
}
