using System.Net;
using System.Text.Json;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Sentry;
using Managing.Core.Exceptions;

namespace Managing.Core.Middleawares;

public class GlobalErrorHandlingMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<GlobalErrorHandlingMiddleware> _logger;
    
    public GlobalErrorHandlingMiddleware(RequestDelegate next, ILogger<GlobalErrorHandlingMiddleware> logger)
    {
        _next = next;
        _logger = logger;
    }
    
    public async Task Invoke(HttpContext context)
    {
        try
        {
            await _next(context);
        }
        catch (Exception ex)
        {
            await HandleExceptionAsync(context, ex);
        }
    }
    
    private Task HandleExceptionAsync(HttpContext context, Exception exception)
    {
        HttpStatusCode status;
        string errorMessage;
        
        // Determine the appropriate status code based on exception type
        status = exception switch
        {
            // 400 Bad Request
            ArgumentException => HttpStatusCode.BadRequest,
            ValidationException => HttpStatusCode.BadRequest,
            FormatException => HttpStatusCode.BadRequest,
            InvalidOperationException => HttpStatusCode.BadRequest,
            
            // 401 Unauthorized
            UnauthorizedAccessException => HttpStatusCode.Unauthorized,
            
            // 403 Forbidden
            ForbiddenException => HttpStatusCode.Forbidden,
            
            // 404 Not Found
            KeyNotFoundException => HttpStatusCode.NotFound,
            FileNotFoundException => HttpStatusCode.NotFound,
            DirectoryNotFoundException => HttpStatusCode.NotFound,
            NotFoundException => HttpStatusCode.NotFound,
            
            // 408 Request Timeout
            TimeoutException => HttpStatusCode.RequestTimeout,
            
            // 409 Conflict
            ConflictException => HttpStatusCode.Conflict,
            
            // 429 Too Many Requests
            RateLimitExceededException => HttpStatusCode.TooManyRequests,
            
            // 501 Not Implemented
            NotImplementedException => HttpStatusCode.NotImplemented,
            
            // 503 Service Unavailable
            ServiceUnavailableException => HttpStatusCode.ServiceUnavailable,
            
            // 500 Internal Server Error (default)
            _ => HttpStatusCode.InternalServerError
        };
        
        // Log the error with appropriate severity based on status code
        var isServerError = (int)status >= 500;
        
        if (isServerError)
        {
            _logger.LogError(exception, "Server Error: {StatusCode} on {Path}", (int)status, context.Request.Path);
        }
        else
        {
            _logger.LogWarning(exception, "Client Error: {StatusCode} on {Path}", (int)status, context.Request.Path);
        }
        
        // Capture exception in Sentry with request context
        var sentryId = SentrySdk.CaptureException(exception, scope =>
        {
            // Add HTTP request information
            scope.SetTag("http.method", context.Request.Method);
            scope.SetTag("http.url", context.Request.Path);
            
            // Add request details
            scope.SetExtra("query_string", context.Request.QueryString.ToString());
            
            // Add custom tags to help with filtering
            scope.SetTag("error_type", exception.GetType().Name);
            scope.SetTag("status_code", ((int)status).ToString());
            scope.SetTag("host", context.Request.Host.ToString());
            scope.SetTag("path", context.Request.Path.ToString());
            
            // Add any correlation IDs if available
            if (context.Request.Headers.TryGetValue("X-Correlation-ID", out var correlationId))
            {
                scope.SetTag("correlation_id", correlationId.ToString());
            }
            
            // Additional context based on exception type
            if (exception is ValidationException)
            {
                scope.SetTag("error_category", "validation");
            }
            else if (exception is NotFoundException)
            {
                scope.SetTag("error_category", "not_found");
            }
            
            // Add additional context from exception data if available
            foreach (var key in exception.Data.Keys)
            {
                if (key is string keyStr && exception.Data[key] != null)
                {
                    scope.SetExtra(keyStr, exception.Data[key].ToString());
                }
            }
            
            // Add breadcrumb for the request
            scope.AddBreadcrumb(
                message: $"Request to {context.Request.Path}",
                category: "request",
                level: BreadcrumbLevel.Info
            );
        });
        
        // Use a more user-friendly error message in production
        if (Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") == "Production")
        {
            // For 5xx errors, use a generic message
            if (isServerError)
            {
                errorMessage = "An unexpected error occurred. Our team has been notified.";
            }
            else
            {
                // For 4xx errors, keep the original message since it's likely helpful for the user
                errorMessage = exception.Message;
            }
        }
        else
        {
            errorMessage = exception.Message;
        }
        
        // Create the error response
        var errorResponse = new ErrorResponse
        {
            StatusCode = (int)status,
            Message = errorMessage,
            TraceId = sentryId.ToString()
        };
        
        // Only include stack trace in development environment
        if (Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") != "Production")
        {
            errorResponse.StackTrace = exception.StackTrace;
        }
        
        var result = JsonSerializer.Serialize(errorResponse, new JsonSerializerOptions
        {
            PropertyNamingPolicy = JsonNamingPolicy.CamelCase
        });
        
        context.Response.ContentType = "application/json";
        context.Response.StatusCode = (int)status;
        return context.Response.WriteAsync(result);
    }
    
    // Custom error response class
    private class ErrorResponse
    {
        public int StatusCode { get; set; }
        public string Message { get; set; }
        public string TraceId { get; set; }
        public string StackTrace { get; set; }
    }
}