using NebulaAuth.Model.Entities;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using SteamLib;
using SteamLib.Account;
using SteamLib.Exceptions;
using SteamLib.SteamMobile;
using SteamLib.Utility.MaFiles;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.DirectoryServices.ActiveDirectory;
using System.IO;
using System.Linq;

namespace NebulaAuth.Model;

public static class Storage
{
    public const string MAFILE_F = "maFiles";
    public const string REMOVED_F = "maFiles_removed";

    public static string MafileFolder { get; } = Path.GetFullPath(MAFILE_F);
    public static string RemovedMafileFolder { get; } = Path.GetFullPath(REMOVED_F);

    public static ObservableCollection<Mafile> MaFiles { get; } = new();

    public static readonly int DuplicateFound;
    static Storage()
    {
        if (Directory.Exists(MafileFolder) == false)
        {
            Directory.CreateDirectory(MafileFolder);
        }

        if (Directory.Exists(RemovedMafileFolder) == false)
        {
            Directory.CreateDirectory(RemovedMafileFolder);
        }

        var files = Directory.GetFiles(MafileFolder);
        var hashNames = new HashSet<string>();
        var hashIds = new HashSet<SteamId>();
        var comparer = new MafileNameComparer(Settings.Instance.UseAccountNameAsMafileName);
        var ordered = files.Order(comparer).ToList();
        foreach (var file in ordered)
        {
            if (Path.GetExtension(file).Equals(".mafile", StringComparison.OrdinalIgnoreCase) == false) continue;
            try
            {
                var data = ReadMafile(file);

                if (hashNames.Contains(data.AccountName) || (data.SessionData != null && hashIds.Contains(data.SessionData.SteamId)))
                {
                    DuplicateFound++;
                    Shell.Logger.Error("Duplicate mafile {file}", Path.GetFileName(file));
                    continue;
                }
                hashNames.Add(data.AccountName);
                if (data.SessionData != null) hashIds.Add(data.SessionData.SteamId);
                MaFiles.Add(data);
            }
            catch (Exception ex)
            {
                Shell.Logger.Error(ex, "Can't load mafile {file}", Path.GetFileName(file));
            }
        }

        MaFiles = new ObservableCollection<Mafile>(MaFiles.OrderBy(m => m.AccountName));
    }


    public static void AddNewMafile(string path, bool overwrite)
    {
        Mafile data;
        try
        {
            data = ReadMafile(path);
        }
        catch (Exception ex)
        {
            Shell.Logger.Warn(ex, "Can't load mafile");
            throw new FormatException("File data is not valid", ex);
        }

        if (string.IsNullOrWhiteSpace(data.AccountName))
            throw new FormatException("File data is not valid. Missing AccountName");

        try
        {
            var code = SteamGuardCodeGenerator.GenerateCode(data.SharedSecret);
        }
        catch (Exception ex)
        {
            throw new FormatException("Can't generate code on this mafile", ex);
        }


        if (data.SessionData == null)
            throw new SessionInvalidException("File data is not valid. Missing SessionData")
            {
                Data = { { "mafile", data } }
            };


        if (overwrite == false && File.Exists(CreatePathForMafile(data)))
        {
            throw new IOException("File already exist and overwrite is False");
        }


        SaveMafile(data);
    }


    public static Mafile ReadMafile(string path)
    {
        var str = File.ReadAllText(path);
        var mafile = MafileSerializer.Deserialize(str, out var mafileData);
        if (mafileData.IsExtended == false)
            throw new FormatException("Mafile is not extended data");


        var props = mafileData.UnusedProperties ?? new Dictionary<string, JProperty>();
        var proxy = GetPropertyValue<MaProxy>("Proxy", props);
        var group = GetPropertyValue<string>("Group", props);
        var password = GetPropertyValue<string>("Password", props);
        return Mafile.FromMobileDataExtended((MobileDataExtended)mafile, proxy, group, password);
    }

    public static string SerializeMafile(Mafile data)
    {
        var props = new Dictionary<string, object?>
        {
            {nameof(Mafile.Proxy), data.Proxy},
            {nameof(Mafile.Group), data.Group},
            {nameof(Mafile.Password), data.Password}
        };
        return SerializeMafile(data, props);
    }

    public static string SerializeMafile(MobileDataExtended data, Dictionary<string, object?>? properties)
    {
        if (Settings.Instance.LegacyMode)
        {
            return MafileSerializer.SerializeLegacy(data, Formatting.Indented, properties);
        }
        else
        {
            return MafileSerializer.Serialize(data);
        }
    }


    private static T? GetPropertyValue<T>(string name, Dictionary<string, JProperty> dictionary)
    {
        if (dictionary.TryGetValue(name, out var prop) == false) return default;
        var value = prop.Value;
        try
        {
            return value.ToObject<T>();
        }
        catch (Exception ex)
        {
            Shell.Logger.Warn(ex, "Can't deserialize property {name}", name);
            return default;
        }
    }

    public static void SaveMafile(Mafile data)
    {
        var path = CreatePathForMafile(data);
        var str = SerializeMafile(data);
        File.WriteAllText(Path.GetFullPath(path), str);

        var existed = MaFiles.SingleOrDefault(m => m.AccountName == data.AccountName);
        if (existed != null)
        {
            var index = MaFiles.IndexOf(existed);
            MaFiles[index] = data;
        }
        else
        {
            MaFiles.Add(data);
        }
    }

    public static void UpdateMafile(Mafile data)
    {
        var path = CreatePathForMafile(data);
        var str = SerializeMafile(data);
        File.WriteAllText(Path.GetFullPath(path), str);
    }

    public static void RemoveMafile(Mafile data)
    {
        var path = CreatePathForMafile(data);
        if (File.Exists(path))
        {
            File.Delete(path);
        }
        MaFiles.Remove(data);
    }
    public static void MoveToRemoved(Mafile data)
    {
        var path = CreatePathForMafile(data);
        var copyPath = Path.Combine(REMOVED_F, data.SessionData!.SteamId + ".mafile");
        var copyPathCompleted = copyPath;
        var i = 0;
        while (File.Exists(copyPathCompleted))
        {
            i++;
            copyPathCompleted = copyPath + $" ({i})";
        }
        File.Copy(path, copyPathCompleted, false);
        File.Delete(path);
        MaFiles.Remove(data);
    }

    private static string CreatePathForMafile(Mafile data)
    {
        string fileName;
        if (Settings.Instance.UseAccountNameAsMafileName)
        {
            fileName = CreateFileNameWithAccountName(data.AccountName);
        }
        else
        {
            if(data.SessionData == null)
                throw new NullReferenceException("SessionData was null can't retrieve SteamId"); //FIXME: think about better way to handle

            fileName = CreateFileNameWithSteamId(data.SessionData.SteamId);
        }

        return Path.Combine(MafileFolder, fileName);
    }

    private static string CreateFileNameWithAccountName(string accountName) => accountName + ".mafile";
    private static string CreateFileNameWithSteamId(SteamId steamId) => steamId.Steam64.Id + ".mafile";

    public static string? TryFindMafilePath(Mafile data)
    //FIXME: write mode to mafile instead of searching it. Search sometimes represents not actual mafile (in case of duplicate steamId + accountName)
    {
        var pathFileName = Path.Combine(MafileFolder, CreateFileNameWithAccountName(data.AccountName));
        string? pathSteamId = null;
        if (data.SessionData != null)
        {
            pathSteamId = Path.Combine(MafileFolder, CreateFileNameWithSteamId(data.SessionData.SteamId));
        }

        var steamIdExist = pathSteamId != null && File.Exists(pathSteamId);
        var accountNameExist = File.Exists(pathFileName);

        if (steamIdExist && accountNameExist)
        {
            return Settings.Instance.UseAccountNameAsMafileName ? pathFileName : pathSteamId;
        }
        if (steamIdExist ^ accountNameExist)
        {
            return steamIdExist ? pathSteamId : pathFileName;
        }
        return null;
    }

    public static bool ValidateCanSave(Mafile data)
    {
       return Settings.Instance.UseAccountNameAsMafileName || data.SessionData != null;
    }
}

internal class MafileNameComparer : IComparer<string>
{
    public bool MafileNameMode { get; }
    private const string MAF_64_START = "765";
    private static readonly IComparer<string> _defaultComparer = Comparer<string>.Default;
    public MafileNameComparer(bool mafileNameMode)
    {
        MafileNameMode = mafileNameMode;
    }


    public int Compare(string? x, string? y)
    {
        if (x == null && y == null) return 0;
        if (x == null) return -1;
        if (y == null) return 1;


        bool xisSteamId = Path.GetFileName(x).StartsWith(MAF_64_START);
        bool yisSteamId = Path.GetFileName(y).StartsWith(MAF_64_START);

        if (xisSteamId ^ yisSteamId)
        {
            if (MafileNameMode)
            {
                return xisSteamId ? 1 : -1;
            }
            else
            {
                return yisSteamId ? 1 : -1;
            }
        }

        return _defaultComparer.Compare(x, y);
    }

}