using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.Data.SqlClient;
using Microsoft.Extensions.Primitives;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using MTWorkHR.Core.UnitOfWork;
using MTWorkHR.Application.Services;
using MTWorkHR.Core.Global;
using Microsoft.Extensions.Logging;

namespace MTWorkHR.Application.Middlewares
{
    public class LoggingMiddleware
    {
        private readonly RequestDelegate _next;

        private readonly ILogger<LoggingMiddleware> _logger;


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

        }

        public async Task Invoke(HttpContext context, IUnitOfWorkLog unitOfWork)
        {
            try
            {
                await _next(context);
            }
            catch (Exception error)
            {
                //Get target controller info
                _logger.LogError(error, "An error occurred during request processing");

                var controllerActionDescriptor = context?
                    .GetEndpoint()?
                    .Metadata
                    .GetMetadata<ControllerActionDescriptor>();

                //get controllerName & actionName
                var controllerName = controllerActionDescriptor?.ControllerName;
                var actionName = controllerActionDescriptor?.ActionName;

                //get QueryString
                var req = context?.Request;
                var QueryString = req?.QueryString.Value?.ToString();

                // get request body
                string bodyStr;
                req.EnableBuffering();
                req.Body.Seek(0, SeekOrigin.Begin);
                req.Body.Position = 0;
                using (StreamReader reader
                          = new StreamReader(req.Body, Encoding.UTF8, true, 1024, true))
                {
                    bodyStr = await reader.ReadToEndAsync();
                }


                //**Get Log service and entity info by refliction

                //Get target log entity by the traget controller name
                Type? entityType = Type.GetType("MTWorkHR.Core.Entities." + "User" + "Log, MTWorkHR.Core");

                var logServiceWithGenericType = typeof(LogService<>).MakeGenericType(entityType);

                dynamic service = Activator.CreateInstance(logServiceWithGenericType, new object[] { unitOfWork });

                var msg = error is AppException ? ((AppException)error).ErrorMessage : error.Message;
                var errorNum = error is AppException ? ((AppException)error).ErrorNumber : 0;
               // int errorNum = 0;
               // int.TryParse(errorNo, out errorNum); 
                dynamic logEnitity = Activator.CreateInstance(entityType, new object[] {
                    controllerName+"/"+actionName, QueryString, bodyStr, DateTime.Now, "",GetLocalIPAddress(), GetServerIp(context), GetUserExternalIp(context), "", "", msg, error?.InnerException?.Message });


                //finally call service.create to insert the log
                await service.Create(logEnitity);
                //****

                switch (error)
                {
                    case AppException e:
                        {
                            context.Response.Clear();
                            context.Response.ContentType = "text/plain";
                            context.Response.StatusCode = StatusCodes.Status400BadRequest;
                            context.Response.StatusCode = e.ErrorNumber switch
                            {
                                ExceptionEnum.EmailNotExist => StatusCodes.Status401Unauthorized,
                                ExceptionEnum.NotAuthorized => StatusCodes.Status401Unauthorized,
                                ExceptionEnum.WrongCredentials => StatusCodes.Status401Unauthorized,
                                _ => StatusCodes.Status400BadRequest
                            };
                            await context.Response.WriteAsJsonAsync(
                             new BadRequestResult
                             {
                                 ErrorMsg = errorNum ==0 ? "Internal Server Error" :  "" + ((AppException)error).ErrorMessage + "",
                                 ErrorNo = (int)errorNum
                             });
                            return;
                        }
                    default:
                        {
                            context.Response.Clear();
                            context.Response.ContentType = "text/plain";
                            context.Response.StatusCode = StatusCodes.Status500InternalServerError;
                            await context.Response.WriteAsync( "Internal Server Error" );
                            return;

                        }
                }
            }
        }
        public static string GetServerIp(HttpContext context)
        {
            try
            {
                IPAddress ipAddressString = context.Connection.LocalIpAddress;
                string REMOTE_ADDR = context.GetServerVariable("REMOTE_ADDR");
                string LOCAL_ADDR = context.GetServerVariable("LOCAL_ADDR");
                string SERVER_ADDR = context.GetServerVariable("SERVER_ADDR"); 
                string REMOTE_HOST = context.GetServerVariable("REMOTE_HOST"); 

                string result = "LocalIpAddress: "+ipAddressString.ToString();
                result += "  REMOTE_ADDR: " + REMOTE_ADDR + "  LOCAL_ADDR:" + LOCAL_ADDR
                    + "  SERVER_ADDR:" + SERVER_ADDR + "  REMOTE_HOST:" + REMOTE_HOST;

                return result;
            }
            catch (Exception e)
            {
                return "";
            }
        }
        public static string GetLocalIPAddress()
        {
            try
            {
                var host = Dns.GetHostEntry(Dns.GetHostName());
                foreach (var ip in host.AddressList)
                {

                    if (ip.AddressFamily == AddressFamily.InterNetwork)
                    {
                        return ip.ToString();
                    }
                }
                return "";
            }
            catch (Exception e)
            {
                return "";
            }
        }

        public static string GetUserExternalIp(HttpContext context)
        {
            try
            {
                IPAddress remoteIpAddress = context.Connection.RemoteIpAddress;
                string result = "";
                if (remoteIpAddress != null)
                {
                    // If we got an IPV6 address, then we need to ask the network for the IPV4 address 
                    // This usually only happens when the browser is on the same machine as the server.
                    if (remoteIpAddress.AddressFamily == System.Net.Sockets.AddressFamily.InterNetworkV6)
                    {
                        remoteIpAddress = System.Net.Dns.GetHostEntry(remoteIpAddress).AddressList
                            .First(x => x.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork);
                    }
                    result = remoteIpAddress.ToString();
                }
                return result;
            }
            catch (Exception e)
            {
                return "";
            }
        }
    }

    public class BadRequestResult
    {
        public string ErrorMsg { get; set; }
        public int ErrorNo { get; set; }

    }
}