using System.ComponentModel.DataAnnotations;
using e_suite.API.Common;
using e_suite.API.Common.exceptions;
using e_suite.Database.Core.Extensions.Exceptions;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;

namespace eSuite.API.Middleware;

/// <summary>
/// Exception capture middleware
/// </summary>
public class ExceptionCapture
{
    private readonly RequestDelegate _next;
    private readonly IExceptionLogManager _exceptionLogManager;

    /// <summary>
    /// Default constructor
    /// </summary>
    /// <param name="next"></param>
    /// <param name="exceptionLogManager"></param>
    public ExceptionCapture(RequestDelegate next, IExceptionLogManager exceptionLogManager)
    {
        _next = next;
        _exceptionLogManager = exceptionLogManager;
    }

    /// <summary>
    /// Called to wrap the controller method with general graceful error handling
    /// </summary>
    /// <param name="context"></param>
    /// <returns></returns>
    public async Task InvokeAsync(HttpContext context)
    {
        try
        {
            await _next.Invoke(context);
        }
        catch (GuidMismatchException ex)
        {
            await BadRequestResponse(context, ex);
        }
        catch (NotFoundException ex)
        {
            await NotFoundResponse(context, ex);
        }
        catch (ExistsException ex)
        {
            await BadRequestResponse(context, ex);
        }
        catch (InvalidOperationException ex)
        {
            await BadRequestResponse(context, ex);
        }
        catch( ValidationException ex)
        {
            await BadRequestResponse(context, ex);
        }
        catch (ArgumentException ex)
        {
            await BadRequestResponse(context, ex);
        }
        catch (InvalidReferenceObjectId ex)
        {
            await BadRequestResponse(context, ex);
        }
        catch (TokenInvalidException ex)
        {
            await BadRequestResponse(context, ex);
        }
        catch (InvalidEmailException ex)
        {
            await BadRequestResponse(context, ex);
        }
        catch (MinimumRangeException ex)
        {
            await BadRequestResponse(context, ex);
        }
        catch (MaximumRangeException ex)
        {
            await BadRequestResponse(context, ex);
        }
        catch (OperationCanceledException ex)
        {
            await BadRequestResponse(context, ex, "Request cancelled");
        }
        catch (Exception ex)
        {
            var supportingData = new
            {
                User = context.User.Identity != null ? context.User.Identity.Name : string.Empty,
                context.Request.Query,
                context.Request.Method,
                context.Request.Path,
                context.Request.Headers,
                context.Request.Cookies,
                context.Request.QueryString,
                context.Items
            }.ToJson();
                
            var exceptionId = await _exceptionLogManager.LogException(ex, "e-suite API", supportingData, CancellationToken.None);
            await InternalServerErrorResponse(context, ex, exceptionId);
        }
    }

    private static async Task BadRequestResponse(HttpContext context, Exception ex, string title = "Bad request")
    {
        var problemDetails = new ProblemDetails
        {
            Title = title,
            Detail = ex.Message
        };

        context.Response.StatusCode = StatusCodes.Status400BadRequest;
        context.Response.ContentType = "application/json";

        await context.Response.WriteAsync(problemDetails.ToJson());
    }

    private static async Task NotFoundResponse(HttpContext context, Exception ex, string title = "Not found")
    {
        var problemDetails = new ProblemDetails
        {
            Title = title,
            Detail = ex.Message
        };

        context.Response.StatusCode = StatusCodes.Status404NotFound;
        context.Response.ContentType = "application/json";

        await context.Response.WriteAsync(problemDetails.ToJson());
    }

    private static async Task InternalServerErrorResponse(HttpContext context, Exception ex, long exceptionId, string title = "Internal Server Error")
    {
        var problemDetails = new ProblemDetails
        {
            Title = title,
            Detail = ex.Message,
            Instance = exceptionId.ToString()
        };

        context.Response.StatusCode = StatusCodes.Status500InternalServerError;
        context.Response.ContentType = "application/json";

        await context.Response.WriteAsync(problemDetails.ToJson());
    }
}

/// <summary>
/// 
/// </summary>
public static class ObjectExtensions
{
    /// <summary>
    /// Serialize the current object to Json
    /// </summary>
    /// <param name="value"></param>
    /// <returns></returns>
    public static string ToJson(this object value)
    {
        var options = new JsonSerializerSettings
        {
            Formatting = Formatting.Indented,
            ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
            NullValueHandling = NullValueHandling.Include
        };
        return JsonConvert.SerializeObject(value, options);
    }
}