using EVCB_OCPP.WEBAPI.Helpers;
using EVCB_OCPP.WEBAPI.Models;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Extensions;
using System.Net.Http;
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using System.IO;
using System.Threading;
using EVCB_OCPP.WEBAPI.Models.WebAPI;
using Newtonsoft.Json;
using System.Linq;
using System.Net;
using Microsoft.Extensions.Configuration;
using Microsoft.Data.SqlClient;

namespace EVCB_OCPP.WEBAPI.Middleware;

public class APILogMiddleware
{
    private readonly RequestDelegate _next;
    private readonly string apiLogConnectionString;

    public APILogMiddleware(RequestDelegate next, IConfiguration configuration)
    {
        _next = next;
        apiLogConnectionString = configuration.GetConnectionString("APILogDBContext");
    }

    public async Task Invoke(HttpContext context)
    {
        var request = context.Request;
        string uri = request.GetDisplayUrl().ToString();
        CancellationToken cancellationToken = context?.RequestAborted ?? CancellationToken.None;

        var apiLogEntry = CreateApiLogEntryWithRequestData(request);

        context.Request.Body.Position = 0;
        await new StreamReader(context.Request.Body).ReadToEndAsync()
            .ContinueWith(task =>
            {
                apiLogEntry.RequestContentBody = task.Result;

            }, cancellationToken);

        var originalBodyStream = context.Response.Body;
        var fakeResponseBody = new MemoryStream();
        context.Response.Body = fakeResponseBody;

        var task =  _next(context);
        await task;

        fakeResponseBody.Seek(0, SeekOrigin.Begin);
        await fakeResponseBody.CopyToAsync(originalBodyStream);

        if (request.GetDisplayUrl().Contains("api/v1/file"))
        {
            await fakeResponseBody.DisposeAsync();
            return;
        }

        if (task.IsFaulted)
        {
            var result = new CPOOuterResponse() { StatusCode = (int)CPO_StatusCode.UnexpectedError, StatusMessage = CPO_StatusMessage.ERROR_MSG_Global_UNEXPECTEDERROR };

            apiLogEntry.ResponseContentBody = JsonConvert.SerializeObject(result, EVCBConfiguration.JSONSERIALIZER_FORMAT);
            apiLogEntry.ResponseContentType = "";
            apiLogEntry.ResponseHeaders = "";

            apiLogEntry.ErrorOn = DateTime.Now;
            apiLogEntry.ErrorMsg = task.Exception.ToString().Replace("'", " ");

            apiLogEntry.ResponseStatusCode = (int)HttpStatusCode.InternalServerError;
            apiLogEntry.ResponseTimestamp = DateTime.Now;
            WriteLog(apiLogEntry);
            //return request.CreateResponse(HttpStatusCode.InternalServerError, result);
            await context.Response.WriteAsJsonAsync(result);
            return;
        }
        else
        {
            //var response = task.Result;
            var response = context.Response;

            if (response.Body != null)
            {
                fakeResponseBody.Seek(0, SeekOrigin.Begin);
                var bodyData = await new StreamReader(response.Body).ReadToEndAsync();
                response.Body.Position = 0;

                apiLogEntry.ResponseContentBody = bodyData;
                apiLogEntry.ResponseContentType = response.Headers.ContentType;
                apiLogEntry.ResponseHeaders = WebApiHelper.SerializeHeaders(response.Headers);
            }

            int errorMessageIndex = apiLogEntry.ResponseContentBody.IndexOf(",\"ErrorDetail\"");
            string endTag = apiLogEntry.ResponseContentBody.Last().ToString();

            if (errorMessageIndex > 0)
            {
                string errorMsg = apiLogEntry.ResponseContentBody.Substring(errorMessageIndex).Remove(0, 16);
                apiLogEntry.ResponseContentBody = apiLogEntry.ResponseContentBody.Remove(errorMessageIndex).Remove(0, 1);
                apiLogEntry.ErrorOn = DateTime.Now;
                apiLogEntry.ErrorMsg = errorMsg.Remove(errorMsg.Count() - 2).Replace("'", " ");

                //response = request.CreateResponse(response.StatusCode, JsonConvert.DeserializeObject("{" + apiLogEntry.ResponseContentBody + "}"));
                await response.WriteAsync("{" + apiLogEntry.ResponseContentBody + "}");
            }

            apiLogEntry.ResponseStatusCode = (int)response.StatusCode;
            apiLogEntry.ResponseTimestamp = DateTime.Now;
            WriteLog(apiLogEntry);

            await fakeResponseBody.DisposeAsync();

            return;
        }
    }

    private ApiLogEntry CreateApiLogEntryWithRequestData(HttpRequest request)
    {
        try
        {
            HttpContext context = request.HttpContext;
            //var routeData = request.GetRouteData();

            return new ApiLogEntry
            {
                Application = "[Api]",
                User = context.User.Identity.Name,
                Machine = Environment.MachineName,
                RequestContentType = context.Request.ContentType,
                //RequestRouteTemplate = routeData.Route.RouteTemplate,
                RequestRouteData = "",
                RequestIpAddress = context.Connection.RemoteIpAddress is null ? "" : context.Connection.RemoteIpAddress.ToString(),
                RequestMethod = request.Method,
                RequestHeaders = WebApiHelper.SerializeHeaders(request.Headers),
                RequestTimestamp = DateTime.Now,
                RequestUri = request.GetDisplayUrl()
            };
        }
        catch (Exception ex)
        {
            //  logger.Debug(" " + ex.ToString());
            return null;
        }

    }

    private void WriteLog(ApiLogEntry log)
    {
        try
        {
            if (log == null) return;
            using (var conn = new SqlConnection(apiLogConnectionString))
            {
                conn.Open();

                using (var cmd = new SqlCommand("[dbo].[uspInsertApiLog]", conn))
                {

                    cmd.CommandType = System.Data.CommandType.StoredProcedure;
                    cmd.Parameters.Add(new SqlParameter("Application", log.Application));
                    cmd.Parameters.Add(new SqlParameter("User", (string.IsNullOrEmpty(log.User) ? "" : log.User)));
                    cmd.Parameters.Add(new SqlParameter("Machine", log.Machine));
                    cmd.Parameters.Add(new SqlParameter("RequestIpAddress", log.RequestIpAddress));
                    cmd.Parameters.Add(new SqlParameter("RequestContentType", (string.IsNullOrEmpty(log.RequestContentType) ? "" : log.RequestContentType)));
                    cmd.Parameters.Add(new SqlParameter("RequestUri", log.RequestUri));
                    cmd.Parameters.Add(new SqlParameter("RequestMethod", log.RequestMethod));
                    //cmd.Parameters.Add(new SqlParameter("RequestRouteTemplate", log.RequestRouteTemplate));
                    cmd.Parameters.Add(new SqlParameter("RequestRouteTemplate", ""));
                    cmd.Parameters.Add(new SqlParameter("RequestRouteData", ""));//log.RequestRouteData
                    cmd.Parameters.Add(new SqlParameter("RequestHeaders", log.RequestHeaders));
                    cmd.Parameters.Add(new SqlParameter("RequestTimestamp", log.RequestTimestamp));

                    cmd.Parameters.Add(new SqlParameter("ResponseContentType", log.ResponseContentType));
                    cmd.Parameters.Add(new SqlParameter("ResponseStatusCode", log.ResponseStatusCode));
                    cmd.Parameters.Add(new SqlParameter("ResponseHeaders", log.ResponseHeaders));
                    cmd.Parameters.Add(new SqlParameter("ResponseTimestamp", log.ResponseTimestamp));

                    cmd.Parameters.Add(new SqlParameter("IsOutData", log.IsOutData));
                    cmd.Parameters.Add(new SqlParameter("ErrorMsg", log.ErrorMsg));
                    cmd.Parameters.Add(new SqlParameter("ErrorOn", log.ErrorOn));
                    cmd.Parameters.Add(new SqlParameter("RequestContentBody", log.RequestContentBody));
                    cmd.Parameters.Add(new SqlParameter("ResponseContentBody", log.ResponseContentBody));

                    var result = cmd.ExecuteNonQuery();
                }
            }


        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.ToString());
        }


    }
}
