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);
                }
            }
        }
    }

}