using System.Text.Json;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace ScadaLink.InboundAPI;

/// <summary>
/// WP-1: POST /api/{methodName} endpoint registration.
/// WP-2: Method routing and parameter validation.
/// WP-3: Script execution on central.
/// WP-5: Error handling — 401, 403, 400, 500.
/// </summary>
public static class EndpointExtensions
{
    public static IEndpointRouteBuilder MapInboundAPI(this IEndpointRouteBuilder endpoints)
    {
        endpoints.MapPost("/api/{methodName}", HandleInboundApiRequest)
            // InboundAPI-006 / InboundAPI-008: active-node gating + request body
            // size cap are enforced by the endpoint filter before the handler runs.
            .AddEndpointFilter<InboundApiEndpointFilter>();
        return endpoints;
    }

    private static async Task<IResult> HandleInboundApiRequest(
        HttpContext httpContext,
        string methodName)
    {
        var logger = httpContext.RequestServices.GetRequiredService<ILogger<ApiKeyValidator>>();
        var validator = httpContext.RequestServices.GetRequiredService<ApiKeyValidator>();
        var executor = httpContext.RequestServices.GetRequiredService<InboundScriptExecutor>();
        var routeHelper = httpContext.RequestServices.GetRequiredService<RouteHelper>();
        var options = httpContext.RequestServices.GetRequiredService<IOptions<InboundApiOptions>>().Value;

        // WP-1: Extract and validate API key
        var apiKeyValue = httpContext.Request.Headers["X-API-Key"].FirstOrDefault();
        var validationResult = await validator.ValidateAsync(apiKeyValue, methodName, httpContext.RequestAborted);

        if (!validationResult.IsValid)
        {
            // WP-5: Failures-only logging
            logger.LogWarning(
                "Inbound API auth failure for method {Method}: {Error} (status {StatusCode})",
                methodName, validationResult.ErrorMessage, validationResult.StatusCode);

            return Results.Json(
                new { error = validationResult.ErrorMessage },
                statusCode: validationResult.StatusCode);
        }

        var method = validationResult.Method!;

        // WP-2: Deserialize and validate parameters
        JsonElement? body = null;
        try
        {
            if (httpContext.Request.ContentLength > 0 || httpContext.Request.ContentType?.Contains("json") == true)
            {
                using var doc = await JsonDocument.ParseAsync(
                    httpContext.Request.Body, cancellationToken: httpContext.RequestAborted);
                body = doc.RootElement.Clone();
            }
        }
        catch (JsonException)
        {
            return Results.Json(
                new { error = "Invalid JSON in request body" },
                statusCode: 400);
        }

        var paramResult = ParameterValidator.Validate(body, method.ParameterDefinitions);
        if (!paramResult.IsValid)
        {
            return Results.Json(
                new { error = paramResult.ErrorMessage },
                statusCode: 400);
        }

        // WP-3: Execute the method's script
        var timeout = method.TimeoutSeconds > 0
            ? TimeSpan.FromSeconds(method.TimeoutSeconds)
            : options.DefaultMethodTimeout;

        var scriptResult = await executor.ExecuteAsync(
            method, paramResult.Parameters, routeHelper, timeout, httpContext.RequestAborted);

        if (!scriptResult.Success)
        {
            // InboundAPI-004: a client-aborted request is not a script failure.
            // Do not pollute the failure log (reserved for genuine script errors)
            // and do not attempt to write a 500 body to an already-gone connection.
            if (httpContext.RequestAborted.IsCancellationRequested)
            {
                return Results.Empty;
            }

            // WP-5: 500 for script failures, safe error message
            logger.LogWarning(
                "Inbound API script failure for method {Method}: {Error}",
                methodName, scriptResult.ErrorMessage);

            return Results.Json(
                new { error = scriptResult.ErrorMessage ?? "Internal server error" },
                statusCode: 500);
        }

        // Return the script result as JSON
        if (scriptResult.ResultJson != null)
        {
            return Results.Text(scriptResult.ResultJson, "application/json", statusCode: 200);
        }

        return Results.Ok();
    }
}
