using ColaFlow.Modules.Mcp.Contracts.JsonRpc;
using ColaFlow.Modules.Mcp.Domain.Exceptions;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using System.Text.Json;

namespace ColaFlow.Modules.Mcp.Infrastructure.Middleware;

/// <summary>
/// Global exception handler middleware for MCP requests
/// Catches all unhandled exceptions and converts them to JSON-RPC error responses
/// </summary>
public class McpExceptionHandlerMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<McpExceptionHandlerMiddleware> _logger;

    public McpExceptionHandlerMiddleware(
        RequestDelegate next,
        ILogger<McpExceptionHandlerMiddleware> logger)
    {
        _next = next;
        _logger = logger;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        try
        {
            await _next(context);
        }
        catch (McpException mcpEx)
        {
            await HandleMcpExceptionAsync(context, mcpEx);
        }
        catch (Exception ex)
        {
            await HandleUnexpectedExceptionAsync(context, ex);
        }
    }

    /// <summary>
    /// Handles known MCP exceptions by converting them to JSON-RPC error responses
    /// </summary>
    private async Task HandleMcpExceptionAsync(HttpContext context, McpException mcpEx)
    {
        var correlationId = context.Items["CorrelationId"] as string;
        var tenantId = context.Items["TenantId"]?.ToString();
        var apiKeyId = context.Items["ApiKeyId"]?.ToString();

        // Log the error with structured data
        _logger.LogError(mcpEx,
            "MCP Error: {ErrorCode} - {Message} | CorrelationId: {CorrelationId} | TenantId: {TenantId} | ApiKeyId: {ApiKeyId}",
            mcpEx.ErrorCode, mcpEx.Message, correlationId, tenantId, apiKeyId);

        // Create JSON-RPC error response
        var response = new JsonRpcResponse
        {
            JsonRpc = "2.0",
            Error = mcpEx.ToJsonRpcError(),
            Id = context.Items["McpRequestId"] // Preserve request ID from parsed request
        };

        // Set HTTP status code based on error type
        context.Response.StatusCode = mcpEx.GetHttpStatusCode();
        context.Response.ContentType = "application/json";

        // Write response
        await context.Response.WriteAsync(
            JsonSerializer.Serialize(response, new JsonSerializerOptions
            {
                PropertyNamingPolicy = JsonNamingPolicy.CamelCase
            }));
    }

    /// <summary>
    /// Handles unexpected exceptions (not MCP-specific) by converting them to InternalError
    /// IMPORTANT: Never expose internal exception details to clients
    /// </summary>
    private async Task HandleUnexpectedExceptionAsync(HttpContext context, Exception ex)
    {
        var correlationId = context.Items["CorrelationId"] as string;
        var tenantId = context.Items["TenantId"]?.ToString();
        var apiKeyId = context.Items["ApiKeyId"]?.ToString();

        // Log the full exception with stack trace
        _logger.LogError(ex,
            "Unexpected error in MCP Server | CorrelationId: {CorrelationId} | TenantId: {TenantId} | ApiKeyId: {ApiKeyId}",
            correlationId, tenantId, apiKeyId);

        // Create generic InternalError response (DO NOT expose exception details)
        var response = new JsonRpcResponse
        {
            JsonRpc = "2.0",
            Error = new JsonRpcError(
                JsonRpcErrorCode.InternalError,
                "Internal server error",
                null), // No error data to avoid leaking internals
            Id = context.Items["McpRequestId"]
        };

        context.Response.StatusCode = 500;
        context.Response.ContentType = "application/json";

        await context.Response.WriteAsync(
            JsonSerializer.Serialize(response, new JsonSerializerOptions
            {
                PropertyNamingPolicy = JsonNamingPolicy.CamelCase
            }));
    }
}
