using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;

namespace Kit.Helpers;

public enum StackTraceModesJson
{
    None = 0,
    Text = 1,
    Html = 2
}

public enum StackTraceModesHtml
{
    None = 0,
    Text = 1,
    Html = 2,

    CommentText = 3,
    CommentHtml = 4,
}

public interface IConfigurationExceptionHandler
{
    StackTraceModesJson StackTraceModeJson { get; }
    StackTraceModesHtml StackTraceModeHtml { get; }
}

public class ConfigurationExceptionHandler : IConfigurationExceptionHandler
{
    public StackTraceModesJson StackTraceModeJson { get; private set; }
    public StackTraceModesHtml StackTraceModeHtml { get; private set; }

    public ConfigurationExceptionHandler(Microsoft.Extensions.Configuration.IConfiguration configuration)
    {
        var helper = new ConfigurationHelper(configuration, "ExceptionHandler");
        StackTraceModeJson = helper.GetAppSettingValue("StackTraceModeJson", required: false, defaultValue: StackTraceModesJson.None.ToString()).ParseToEnum<StackTraceModesJson>();
        StackTraceModeHtml = helper.GetAppSettingValue("StackTraceModeHtml", required: false, defaultValue: StackTraceModesHtml.None.ToString()).ParseToEnum<StackTraceModesHtml>();
    }
}

public static class ExceptionHandlerMiddlewareExt
{
    public static IServiceCollection InitExceptionHandlerMiddleware(this IServiceCollection builder, IConfigurationExceptionHandler configuration)
    {
        ExceptionHandlerMiddleware.Init(configuration);
        return builder;
    }
}

public class ExceptionHandlerMiddleware
{
    private static IConfigurationExceptionHandler? _exceptionHandler;
    private static StackTraceModesJson _stackTraceModeJson => _exceptionHandler?.StackTraceModeJson ?? StackTraceModesJson.None;
    private static StackTraceModesHtml _stackTraceModeHtml => _exceptionHandler?.StackTraceModeHtml ?? StackTraceModesHtml.None;

    private readonly RequestDelegate _next;

    public static void Init(IConfigurationExceptionHandler exceptionHandler)
    {
        _exceptionHandler = exceptionHandler;
    }

    public ExceptionHandlerMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task Invoke(HttpContext context)
    {
        try
        {
            await _next(context);
        }
        catch (Exception ex)
        {
            int statusCode = 0;
            string errorMessage = string.Empty;
            string errorStackTrace = string.Empty;
            string? redirectUrl = null;
            bool forceJson = false;

            switch (ex)
            {
                case AuthenticationException exception:
                    statusCode = StatusCodes.Status401Unauthorized;
                    errorMessage = exception.Message;
                    redirectUrl = exception.RedirectUrl;
                    forceJson = true;
                    break;

                case AuthorizationException exception:
                    statusCode = StatusCodes.Status403Forbidden;
                    errorMessage = exception.Message;
                    redirectUrl = exception.RedirectUrl;
                    forceJson = true;
                    break;

                case InvalidOperationException exception:
                    statusCode = StatusCodes.Status409Conflict;
                    errorMessage = exception.Message;
                    break;
                default:
                    statusCode = StatusCodes.Status500InternalServerError;
                    errorMessage = "На сервере произошла неизвестная ошибка" + Environment.NewLine + ex.Message;
                    break;
            }

            context.Response.StatusCode = statusCode;

            if (forceJson || context.NeedResponseJsonResult(ex))
            {
                switch (_stackTraceModeJson)
                {
                    case StackTraceModesJson.Text:
                        errorStackTrace = ex?.GetInfoAsPlainText() ?? string.Empty;
                        break;
                    case StackTraceModesJson.Html:
                        errorStackTrace = ex?.GetInfoAsHtml() ?? string.Empty;
                        break;
                }

                string json = new JsonResultError
                {
                    ErrorMessage = errorMessage,
                    ErrorStackTrace = errorStackTrace,
                    RedirectUrl = redirectUrl
                }.JsonSerialize(enableCyrillic: true, propertyNamingPolicy: System.Text.Json.JsonNamingPolicy.CamelCase);
                context.Response.ContentType = "application/json; charset=utf-8";
                await context.Response.WriteAsync(json, System.Text.Encoding.UTF8);
            }
            else
            {
                switch (_stackTraceModeHtml)
                {
                    case StackTraceModesHtml.Text:
                        errorStackTrace = ex.GetInfoAsPlainText() ?? string.Empty;
                        break;
                    case StackTraceModesHtml.Html:
                        errorStackTrace = ex.GetInfoAsHtml() ?? string.Empty;
                        break;
                    case StackTraceModesHtml.CommentText:
                        errorStackTrace = "<!-- " + ex.GetInfoAsPlainText() ?? string.Empty + " -->";
                        break;
                    case StackTraceModesHtml.CommentHtml:
                        errorStackTrace = "<!-- " + ex.GetInfoAsHtml() ?? string.Empty + " -->";
                        break;
                }

                errorMessage += Environment.NewLine + errorStackTrace;
                context.Response.ContentType = "text/html; charset=utf-8";
                await context.Response.WriteAsync(errorMessage, System.Text.Encoding.UTF8);
            }
        }
    }
}