using AsbCloudApp.Data;
using AsbCloudApp.Services;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;

namespace AsbCloudInfrastructure.Services;


public class RequestTrackerService : IRequerstTrackerService
{
    const int fastRequestsCount = 1000;
    const int slowRequestsCount = 1000;
    const int errorRequestsCount = 1000;
    const int span = 100;

    const int fastLimitMs = 500;
    static readonly char[] stackTraceSeparators = "\r\n".ToCharArray();
    private readonly ConcurrentQueue<RequestLogDto> fastRequests = new ();
    private readonly ConcurrentQueue<RequestLogDto> slowRequests = new ();
    private readonly ConcurrentQueue<RequestLogDto> errorRequests = new ();
    private readonly ConcurrentDictionary<string, RequestLogUserDto> users = new ConcurrentDictionary<string, RequestLogUserDto>();

    private static IEnumerable<RequestLogDto> Get(IEnumerable<RequestLogDto> list, int? take)
    {
        IEnumerable<RequestLogDto> orderedlist = list.OrderByDescending(r => r.Date);
        if (take > 0)
            orderedlist = orderedlist.Take(take.Value);
        return orderedlist;
    }

    public IEnumerable<RequestLogUserDto> GetUsersStat(int? take)
    {
        IEnumerable<RequestLogUserDto> result = users.Values.OrderByDescending(u => u.LastDate);
        if (take > 0)
            result = result.Take(take.Value);
        return result;
    }

    public IEnumerable<RequestLogDto> GetAll(int? take)
    {
        var result = fastRequests
            .Union(slowRequests)
            .Union(errorRequests);

        return Get(result, take);
    }

    public IEnumerable<RequestLogDto> GetFast(int? take)
        => Get(fastRequests, take);

    public IEnumerable<RequestLogDto> GetSlow(int? take)
        => Get(slowRequests, take);

    public IEnumerable<RequestLogDto> GetError(int? take)
        => Get(errorRequests, take);

    public void RegisterRequest(RequestLogDto requestLog)
    {
        if (requestLog.Status < 200)
            return;
        requestLog.Date = DateTime.Now;
        if (requestLog.ElapsedMilliseconds > fastLimitMs)
            RegisterSlowRequest(requestLog);
        else
            RegisterFastRequest(requestLog);

        UpdateUserStat(requestLog);
    }

    private void RegisterFastRequest(RequestLogDto requestLog)
    {
        fastRequests.Enqueue(requestLog);
        if (fastRequests.Count > fastRequestsCount + span)
            while (fastRequests.Count > fastRequestsCount)
                fastRequests.TryDequeue(out _);
    }

    private void RegisterSlowRequest(RequestLogDto requestLog)
    {
        slowRequests.Enqueue(requestLog);
        if (slowRequests.Count > slowRequestsCount + span)
            while (slowRequests.Count > slowRequestsCount)
                slowRequests.TryDequeue(out _);
    }

    public void RegisterRequestError(RequestLogDto requestLog, Exception ex)
    {
        requestLog.Date = DateTime.Now;
        requestLog.ExceptionMessage = ex.InnerException?.InnerException?.Message
            ?? ex.InnerException?.Message
            ?? ex.Message;
        requestLog.ExceptionStack = ex.StackTrace?.Split(stackTraceSeparators)[0];
        errorRequests.Enqueue(requestLog);
        if (errorRequests.Count > errorRequestsCount + span)
            while (errorRequests.Count > errorRequestsCount)
                errorRequests.TryDequeue(out _);

        UpdateUserStat(requestLog);
    }

    private void UpdateUserStat(RequestLogDto requestLog)
    {
        if (!string.IsNullOrEmpty(requestLog.UserLogin))
        {
            var key = $"{requestLog.UserId}>{requestLog.UserIp}";
            if (!users.ContainsKey(key))
                users[key] = new RequestLogUserDto
                {
                    UserId = requestLog.UserId,
                    Ip = requestLog.UserIp,
                    Login = requestLog.UserLogin,
                };
            users[key].ElapsedMs += requestLog.ElapsedMilliseconds;
            users[key].LastDate = requestLog.Date;
            users[key].Requests++;
            if (!string.IsNullOrEmpty(requestLog.ExceptionMessage))
                users[key].Errors++;

            if (users.Count > 1000)
            {
                var count = 900 - users.Count;
                var toRemove = users.OrderBy(kv => kv.Value.LastDate).Take(count);
                foreach (var kv in toRemove)
                    users.TryRemove(kv);
            }
        }
    }
}