using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace Adaptation.FileHandlers.Priority;

#nullable enable

public class Aggregation
{

    [JsonConstructor]
    public Aggregation(double inverseAverage,
                       int valueCount,
                       double fibonacciAverage,
                       int? inverseValue,
                       int valueMaximum,
                       int valueMinimum,
                       Notification[] notifications,
                       int valueSum)
    {
        InverseAverage = inverseAverage;
        ValueCount = valueCount;
        FibonacciAverage = fibonacciAverage;
        InverseValue = inverseValue;
        ValueMaximum = valueMaximum;
        ValueMinimum = valueMinimum;
        Notifications = notifications;
        ValueSum = valueSum;
    }

    public double InverseAverage { get; } // [JsonPropertyName("InverseAverage")]
    public int ValueCount { get; } // [JsonPropertyName("ValueCount")]
    public double FibonacciAverage { get; } // [JsonPropertyName("Fibonacci")]
    public int? InverseValue { get; } // [JsonPropertyName("InverseValue")]
    public int ValueMaximum { get; } // [JsonPropertyName("ValueMaximum")]
    public int ValueMinimum { get; } // [JsonPropertyName("ValueMinimum")]
    public Notification[] Notifications { get; } // [JsonPropertyName("Notifications")]
    public int ValueSum { get; } // [JsonPropertyName("ValueSum")]

    private static ReadOnlyDictionary<int, Aggregation> GetKeyValuePairs(Settings settings, Dictionary<int, List<Notification>> keyValuePairs)
    {
        Dictionary<int, Aggregation> results = new();
        int? inverseValue;
        double inverseAverage;
        Aggregation aggregation;
        double fibonacciAverage;
        List<int> collection = new();
        int averageFromInverseCeiling;
        List<int> inverseCollection = new();
        List<int> fibonacciCollection = new();
        foreach (KeyValuePair<int, List<Notification>> keyValuePair in keyValuePairs)
        {
            collection.Clear();
            inverseCollection.Clear();
            fibonacciCollection.Clear();
            foreach (Notification notification in keyValuePair.Value)
            {
                collection.Add(notification.Value);
                if (notification.Inverse is null)
                    continue;
                inverseCollection.Add(notification.Inverse.Value);
                if (notification.Fibonacci is null)
                    continue;
                fibonacciCollection.Add(notification.Fibonacci.Value);
            }
            if (inverseCollection.Count == 0 || fibonacciCollection.Count == 0)
                continue;
            inverseAverage = Math.Round(inverseCollection.Average(), settings.Digits);
            averageFromInverseCeiling = (int)Math.Ceiling(inverseAverage);
            inverseValue = Notification.GetInverse(averageFromInverseCeiling);
            fibonacciAverage = Math.Round(fibonacciCollection.Average(), settings.Digits);
            aggregation = new(inverseAverage: inverseAverage,
                              valueCount: collection.Count,
                              fibonacciAverage: fibonacciAverage,
                              inverseValue: inverseValue,
                              valueMaximum: collection.Max(),
                              valueMinimum: collection.Min(),
                              notifications: keyValuePair.Value.ToArray(),
                              valueSum: collection.Sum());
            results.Add(keyValuePair.Key, aggregation);
        }
        return new(results);
    }

    private static ReadOnlyCollection<Notification> GetNotifications(Settings settings, string directory)
    {
        List<Notification> results = new();
        string? key;
        string text;
        string[] files;
        Notification? notification;
        List<Notification>? collection;
        Dictionary<string, List<Notification>> keyValuePairs = new();
        string[] directories = Directory.GetDirectories(directory, "*", SearchOption.TopDirectoryOnly);
        foreach (string subDirectory in directories)
        {
            keyValuePairs.Clear();
            files = Directory.GetFiles(subDirectory, settings.SourceFileFilter, SearchOption.TopDirectoryOnly);
            foreach (string file in files)
            {
                text = File.ReadAllText(file);
                if (string.IsNullOrEmpty(text) || text[0] == '[')
                    continue;
                notification = JsonSerializer.Deserialize(text, NotificationSourceGenerationContext.Default.Notification);
                if (notification is null || notification.Id == 0)
                    continue;
                key = !string.IsNullOrEmpty(notification.Username) ? notification.Username : notification.RemoteIpAddress;
                if (string.IsNullOrEmpty(key))
                    continue;
                if (!keyValuePairs.TryGetValue(key, out collection))
                {
                    keyValuePairs.Add(key, new());
                    if (!keyValuePairs.TryGetValue(key, out collection))
                        throw new Exception();
                }
                collection.Add(notification);
            }
            foreach (KeyValuePair<string, List<Notification>> keyValuePair in keyValuePairs)
            {
                if (keyValuePair.Value.Count == 1)
                    results.Add(keyValuePair.Value[0]);
                else
                {
                    notification = keyValuePair.Value.Select(record => new KeyValuePair<long, Notification>(record.Time, record)).OrderBy(pair => pair.Key).Last().Value;
                    results.Add(notification);
                }
            }
        }
        return new(results);
    }

    private static ReadOnlyDictionary<int, Aggregation> GetKeyValuePairs(Settings settings, string directory)
    {
        ReadOnlyDictionary<int, Aggregation> results;
        List<Notification>? collection;
        Dictionary<int, List<Notification>> keyValuePairs = new();
        ReadOnlyCollection<Notification> notifications = GetNotifications(settings, directory);
        foreach (Notification notification in notifications)
        {
            if (!keyValuePairs.TryGetValue(notification.Id, out collection))
            {
                keyValuePairs.Add(notification.Id, new());
                if (!keyValuePairs.TryGetValue(notification.Id, out collection))
                    throw new Exception();
            }
            collection.Add(notification);
        }
        results = GetKeyValuePairs(settings, keyValuePairs);
        return results;
    }

    internal static ReadOnlyDictionary<string, ReadOnlyDictionary<int, Aggregation>> GetKeyValuePairsAndWriteFiles(Settings settings)
    {
        Dictionary<string, ReadOnlyDictionary<int, Aggregation>> results = new();
        string json;
        string jsonOld;
        string jsonFile;
        string directoryName;
        ReadOnlyDictionary<int, Aggregation> keyValuePairs;
        if (!Directory.Exists(settings.SourceFileLocation))
            _ = Directory.CreateDirectory(settings.SourceFileLocation);
        if (!Directory.Exists(settings.TargetFileLocation))
            _ = Directory.CreateDirectory(settings.TargetFileLocation);
        string[] directories = Directory.GetDirectories(settings.SourceFileLocation, "*", SearchOption.TopDirectoryOnly);
        foreach (string directory in directories)
        {
            directoryName = Path.GetFileName(directory);
            keyValuePairs = GetKeyValuePairs(settings, directory);
            jsonFile = Path.Combine(settings.TargetFileLocation, $"{directoryName}.json");
            json = JsonSerializer.Serialize(keyValuePairs, AggregationReadOnlyDictionarySourceGenerationContext.Default.ReadOnlyDictionaryInt32Aggregation);
            // keyValuePairs = JsonSerializer.Deserialize(json, AggregationReadOnlyDictionarySourceGenerationContext.Default.ReadOnlyDictionaryInt32Aggregation);
            jsonOld = File.Exists(jsonFile) ? File.ReadAllText(jsonFile) : string.Empty;
            if (json != jsonOld)
                File.WriteAllText(jsonFile, json);
            results.Add(directoryName, keyValuePairs);
        }
        return new(results);
    }

    internal static ReadOnlyDictionary<int, Aggregation> GetKeyValuePairs(Settings settings, Notification notification)
    {
        ReadOnlyDictionary<int, Aggregation> results;
        Dictionary<int, List<Notification>> keyValuePairs = new() { { notification.Id, new Notification[] { notification }.ToList() } };
        results = GetKeyValuePairs(settings, keyValuePairs);
        return results;
    }

}

[JsonSourceGenerationOptions(WriteIndented = true)]
[JsonSerializable(typeof(Aggregation))]
internal partial class AggregationSourceGenerationContext : JsonSerializerContext
{
}

[JsonSourceGenerationOptions(WriteIndented = true)]
[JsonSerializable(typeof(Aggregation[]))]
internal partial class AggregationCollectionSourceGenerationContext : JsonSerializerContext
{
}

[JsonSourceGenerationOptions(WriteIndented = true)]
[JsonSerializable(typeof(ReadOnlyDictionary<int, Aggregation>))]
internal partial class AggregationReadOnlyDictionarySourceGenerationContext : JsonSerializerContext
{
}