using e_suite.Service.Performance.Interfaces;
using e_suite.Service.Performance.Repository;
using Newtonsoft.Json;

namespace e_suite.Service.Performance.Implementations;

public class PerformanceReportProcessor : IPerformanceReportProcessor
{
    private const long MAX_PARAM_LENGTH = 16384; //16KB

    private readonly IPerformanceRepository _performanceRepository;

    public PerformanceReportProcessor(IPerformanceRepository performanceRepository)
    {
        _performanceRepository = performanceRepository;
    }

    public async Task ProcessReportAsync(PerformanceCapture performanceCapture)
    {
        await ProcessPeformanceReportAsync(performanceCapture);
    }

    public static Dictionary<string, string> PrepareParameters(PerformanceCapture performanceCapture)
    {
        var actionParameters = new Dictionary<string, string>();
        if (performanceCapture.ActionParameters != null)
        {
            foreach (var parameter in performanceCapture.ActionParameters)
            {
                var value = "<null>";
                if (parameter.Value != null)
                {
                    var type = parameter.Value.GetType();
                    value = parameter.Value.ToString();
                    if (value == type.FullName)
                    {
                        try
                        {
                            var settings = new JsonSerializerSettings
                            {
                                ReferenceLoopHandling = ReferenceLoopHandling.Ignore
                            };
                            value = JsonConvert.SerializeObject(parameter.Value, settings);
                        }
                        catch (Exception)
                        {
                            value = "<Json Serialisation failed>";
                        }
                    }

                    if (value!.Length > MAX_PARAM_LENGTH)
                    {
                        value = "<Value too big>";
                    }
                }

                actionParameters.Add(parameter.Key, value);
            }
        }

        return actionParameters;
    }

    public static PerformanceReportTimer ProcessPerformanceMarkers(PerformanceMarker rootMarker)
    {
        var result = new PerformanceReportTimer
        {
            Name = rootMarker.Name,
            TimePoint = rootMarker.Markers[0].ElapsedNanoSeconds
        };

        var lapStart = result.TimePoint;

        var markerIndex = 0;
        var childIndex = 0;
        PerformanceReportTimer performanceReportTimer;
        while (markerIndex < rootMarker.Markers.Count || childIndex < rootMarker.Count)
        {
            Marker? marker = null;
            if (markerIndex < rootMarker.Markers.Count)
                marker = rootMarker.Markers[markerIndex];

            PerformanceMarker childPerformanceMarker = null!;
            if (childIndex < rootMarker.Count)
                childPerformanceMarker = rootMarker[childIndex];

            if (MarkerBeforeChild(marker!, childPerformanceMarker))
            {
                performanceReportTimer = new PerformanceReportTimer
                {
                    Name = marker!.Name,
                    TimePoint = marker.ElapsedNanoSeconds,
                    TotalTimeNS = marker.ElapsedNanoSeconds - lapStart
                };
                markerIndex++;
            }
            else
            {
                performanceReportTimer = ProcessPerformanceMarkers(childPerformanceMarker);
                childIndex++;
            }
            result.Timings.Add(performanceReportTimer);

            lapStart = performanceReportTimer.TimePoint;
        }

        result.TotalTimeNS = rootMarker.Markers[^1].ElapsedNanoSeconds - result.TimePoint;
        return result;
    }
    private static bool MarkerBeforeChild(Marker marker, PerformanceMarker childPerformanceMarker)
    {
        if (marker == null)
            return false;

        if (childPerformanceMarker == null)
            return true;

        return marker.ElapsedNanoSeconds < childPerformanceMarker.Markers[0].ElapsedNanoSeconds;
    }

    private static void Retry(int attempts, Action action)
    {
        var attempt = 0;
        while (attempt < attempts)
        {
            try
            {
                action();
                break;
            }
            catch (Exception)
            {
                attempt++;
                if (attempt >= attempts)
                    throw;
            }
        }
    }
    private static PerformanceReport PreparePerformanceReport(PerformanceCapture performanceCapture)
    {
        var report = new PerformanceReport(
            performanceCapture.Host,
            controllerName: performanceCapture.ControllerName.ToString(),
            actionName: performanceCapture.ActionName.ToString(),
            requestType: performanceCapture.RequestType.ToUpperInvariant(),
            actionParameters: PrepareParameters(performanceCapture),
            startDateTime: performanceCapture.StartDateTime,
            timings: ProcessPerformanceMarkers(performanceCapture.Markers));

        return report;
    }

    private async Task<bool> SaveSlowPerformanceMetricAsync(PerformanceCapture performanceCapture)
    {
        var threshold = await _performanceRepository.GetSlowPerformanceThresholdMsAsync(performanceCapture);
        return performanceCapture.Stopwatch.Elapsed.TotalMilliseconds > threshold;
    }
    private async Task ProcessPeformanceReportAsync(PerformanceCapture performanceCapture)
    {
        if (await SaveSlowPerformanceMetricAsync(performanceCapture))
        {
            var performanceReport = PreparePerformanceReport(performanceCapture);
            Retry(5, () => { _performanceRepository.SaveReportAsync(performanceReport); });
        }
    }
}
