using log4net;
using Nancy;
using Nancy.Extensions;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Text.Json;

#nullable enable

#pragma warning disable CA1822

namespace Adaptation.FileHandlers.Priority;

public class WeightedShortestJobFirstModule : NancyModule
{

    public WeightedShortestJobFirstModule()
    {
        Get("/api/v1/ado/", _ =>
        {
            string json;
            ILog log = LogManager.GetLogger(typeof(WeightedShortestJobFirstModule));
            log.Info($"Enter-{nameof(GetKeyValuePairs)}");
            try
            {
                string query = Request.Url.Query;
                IDictionary<string, IEnumerable<string>> collection = Nancy.Helpers.HttpUtility.ParseQueryString(query).ToDictionary();
                KeyValuePair<string, WorkItem>? workItem = GetWorkItem(collection);
                json = workItem is null ? string.Empty : JsonSerializer.Serialize(workItem, KeyValuePairStringWorkItemSourceGenerationContext.Default.KeyValuePairStringWorkItem);
            }
            catch (Exception ex)
            {
                log.Fatal($"Exception-{nameof(GetKeyValuePairs)}{Environment.NewLine}{ex.Message}{Environment.NewLine}{Environment.NewLine}{ex.StackTrace}");
                throw;
            }
            log.Info($"Return-{nameof(GetKeyValuePairs)}");
            return json;
        });
        base.Post("/api/v1/ado/", _ =>
        {
            Notification notification;
            ILog log = LogManager.GetLogger(typeof(WeightedShortestJobFirstModule));
            log.Info($"Enter-{nameof(Post)}");
            try
            {
                string body = Request.Body.AsString();
                DynamicDictionary form = Request.Form;
                Dictionary<string, object> keyValuePairs = form.ToDictionary();
                notification = GetNotification(body, keyValuePairs);
            }
            catch (Exception ex)
            {
                log.Fatal($"Exception-{nameof(Post)}{Environment.NewLine}{ex.Message}{Environment.NewLine}{Environment.NewLine}{ex.StackTrace}");
                throw;
            }
            log.Info($"Return-{nameof(Post)}");
            return notification.Time.ToString();
        });
    }

    private static Dictionary<string, string?> GetKeyValuePairs(IDictionary<string, IEnumerable<string>> collection)
    {
        Dictionary<string, string?> results = new();
        string[] array;
        foreach (KeyValuePair<string, IEnumerable<string>> keyValuePair in collection)
        {
            array = keyValuePair.Value.ToArray();
            if (array.Length != 1)
                continue;
            if (array.Length == 1 && array[0] == "null")
                results.Add(keyValuePair.Key, null);
            else
                results.Add(keyValuePair.Key, array[0]);
        }
        return results;
    }

    internal static Notification GetNotification(string body, Dictionary<string, object> keyValuePairs)
    {
        Notification result;
        if (FileRead.Queue is null)
            throw new NullReferenceException(nameof(FileRead.Queue));
        if (FileRead.Settings is null)
            throw new NullReferenceException(nameof(FileRead.Settings));
        if (FileRead.WorkItems is null)
            throw new NullReferenceException(nameof(FileRead.WorkItems));
        string? json;
        if (!string.IsNullOrEmpty(body) && body[0] == '{')
        {
            File.WriteAllText(".json", body);
            result = JsonSerializer.Deserialize(body, NotificationSourceGenerationContext.Default.Notification) ?? throw new NullReferenceException();
        }
        else
        {
            json = JsonSerializer.Serialize(keyValuePairs);
            File.WriteAllText(".json", json);
            result = JsonSerializer.Deserialize(json, NotificationSourceGenerationContext.Default.Notification) ?? throw new NullReferenceException();
        }
        if (!string.IsNullOrEmpty(result.Id))
            FileWriteAllText(FileRead.Settings, result);
        json = PopulatedWorkItemsAndGetJson(FileRead.Settings, FileRead.WorkItems);
        if (!string.IsNullOrEmpty(json))
            WriteJson(FileRead.Settings, json);
        if (!string.IsNullOrEmpty(result.SessionId))
        {
            string key = GetKey(result);
            Queue<KeyValuePair<string, WorkItem>>? queue;
            lock (FileRead.Queue)
            {
                if (!FileRead.Queue.TryGetValue(key, out queue))
                {
                    FileRead.Queue.Add(key, new());
                    if (!FileRead.Queue.TryGetValue(key, out queue))
                        throw new Exception();
                }
            }
            WorkItem? workItem = GetWorkItem(FileRead.WorkItems, result);
            if (workItem is not null)
            {
                lock (FileRead.Queue)
                {
                    foreach (KeyValuePair<string, Queue<KeyValuePair<string, WorkItem>>> keyValuePair in FileRead.Queue)
                        keyValuePair.Value.Enqueue(new(result.Page, workItem));
                }
            }
        }
        return result;
    }

    internal static KeyValuePair<string, WorkItem>? GetWorkItem(IDictionary<string, IEnumerable<string>> collection)
    {
        KeyValuePair<string, WorkItem>? result;
        Dictionary<string, string?> keyValuePairs = GetKeyValuePairs(collection);
        Notification notification = Notification.Get(keyValuePairs);
        if (FileRead.Queue is null)
            result = null;
        else
        {
            Queue<KeyValuePair<string, WorkItem>>? queue;
            string key = GetKey(notification);
            lock (FileRead.Queue)
            {
                if (!FileRead.Queue.TryGetValue(key, out queue))
                {
                    FileRead.Queue.Add(key, new());
                    if (!FileRead.Queue.TryGetValue(key, out queue))
                        throw new Exception();
                }
                result = queue.Count == 0 ? null : queue.Dequeue();
            }
        }
        return result;
    }

    private static string GetKey(Notification notification) =>
        $"{notification.SessionId}-{notification.MachineId}-{notification.Username}";

    private static void FileWriteAllText(Settings settings, Notification notification)
    {
        if (string.IsNullOrEmpty(notification.Id))
            throw new NullReferenceException(nameof(notification.Id));
        string json = JsonSerializer.Serialize(notification, NotificationSourceGenerationContext.Default.Notification);
        string directory = Path.Combine(settings.SourceFileLocation, notification.Page, notification.Id.ToString());
        if (!Directory.Exists(directory))
            _ = Directory.CreateDirectory(directory);
        string checkFile = Path.Combine(directory, $"{notification.Time}.json");
        File.WriteAllText(checkFile, json);
    }

    internal static string? PopulatedWorkItemsAndGetJson(Settings settings, Dictionary<int, WorkItem> workItems)
    {
        string? result = null;
        ReadOnlyDictionary<int, WorkItem?> keyValuePairs = WorkItem.GetKeyValuePairs(settings);
        int useCount = (from l in keyValuePairs where l.Value.CostOfDelay is not null select true).Count();
        double prioritySize = useCount / settings.Priorities;
        double priorityGroupSize = useCount / settings.PriorityGroups;
        WorkItem[] sorted = (from l in keyValuePairs
                             where l.Value is not null
                             orderby l.Value.Site is not null,
                                     l.Value.Site descending,
                                     l.Value.CostOfDelay is not null,
                                     l.Value.CostOfDelay descending,
                                     l.Value.BusinessValue?.FibonacciAverage is not null,
                                     l.Value.BusinessValue?.FibonacciAverage descending,
                                     l.Key
                             select l.Value).ToArray();
        lock (workItems)
        {
            int j = 0;
            WorkItem w;
            double value;
            int lastId = -1;
            int? sortBeforeId;
            WorkItem workItem;
            int? sortPriority;
            workItems.Clear();
            int? sortPriorityGroup;
            for (int i = 0; i < sorted.Length; i++)
            {
                w = sorted[i];
                if (w.CostOfDelay is null)
                {
                    sortBeforeId = null;
                    sortPriority = null;
                    sortPriorityGroup = null;
                }
                else
                {
                    j += 1;
                    sortBeforeId = lastId;
                    value = (j / prioritySize) + 1;
                    sortPriority = (int)Math.Floor(value);
                    if (sortPriority > settings.Priorities)
                        sortPriority = settings.Priorities;
                    value = (j / priorityGroupSize) + 1;
                    sortPriorityGroup = (int)Math.Floor(value);
                    if (sortPriorityGroup > settings.PriorityGroups)
                        sortPriorityGroup = settings.PriorityGroups;
                }
                workItem = WorkItem.GetWorkItem(w, i, sortBeforeId, sortPriority, sortPriorityGroup);
                workItems.Add(workItem.Id, workItem);
                lastId = w.Id;
            }
            result = JsonSerializer.Serialize(workItems, WorkItemDictionarySourceGenerationContext.Default.DictionaryInt32WorkItem);
        }
        return result;
    }

    private static WorkItem? GetWorkItem(Dictionary<int, WorkItem> workItems, Notification notification)
    {
        WorkItem? result;
        if (string.IsNullOrEmpty(notification.Id) || !int.TryParse(notification.Id, out int id))
            result = null;
        else
        {
            lock (workItems)
            {
                if (!workItems.TryGetValue(id, out result))
                    throw new Exception();
            }
        }
        return result;
    }

    internal static void WriteJson(Settings settings, string json)
    {
        string jsonFile = Path.Combine(settings.ParentDirectory, "{}.json");
        string jsonFileWith = Path.Combine(settings.ParentDirectory, "{[]}.json");
        string jsonOld = File.Exists(jsonFileWith) ? File.ReadAllText(jsonFileWith) : string.Empty;
        if (json != jsonOld)
        {
            File.WriteAllText(jsonFileWith, json);
            Dictionary<int, WorkItem> w = JsonSerializer.Deserialize(json.Replace($"\"{nameof(Aggregation.Notifications)}\":", "\"ignore\":"), WorkItemDictionarySourceGenerationContext.Default.DictionaryInt32WorkItem) ?? throw new Exception();
            json = JsonSerializer.Serialize(w, WorkItemDictionarySourceGenerationContext.Default.DictionaryInt32WorkItem);
            File.WriteAllText(jsonFile, json);
        }
    }

}