using DatabaseCore;
using ModuleTools.Attributes;
using ModuleTools.Extensions;
using ModuleTools.Interfaces;
using ModuleTools.Models;
using SecurityBusinessLogic.BindingModels;
using SecurityBusinessLogic.Interfaces;
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization.Json;

namespace SecurityDatabaseImplementation.Implementations
{
	/// <summary>
	/// Реализация IBackupService для сохранения в JSON через JsonContract
	/// </summary>
	public class BackupJsonContractService : IBackupService
	{
		public OperationResultModel CreateBackUp(BackupBindingModel model)
		{
			try
			{
				var asm = typeof(DatabaseManager).Assembly;
				MethodInfo method = GetType().GetTypeInfo().GetDeclaredMethod("SaveToFile");
				foreach (var t in asm.GetExportedTypes())
				{
					if (t.IsClass && t.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEntitySecurityExtenstion<>)) &&
						t.GetCustomAttribute<EntityDescriptionAttribute>() != null)
					{
						MethodInfo generic = method.MakeGenericMethod(t);
						generic.Invoke(this, new object[] { model.FolderName, model.FullData, t });
					}
				}
				if (model.CreateArchive)
				{
					var fileName = $"{model.FolderName}.zip";
					if (File.Exists(fileName))
					{
						File.Delete($"{model.FolderName}.zip");
					}
					ZipFile.CreateFromDirectory(model.FolderName, fileName);
				}
			}
			catch (Exception ex)
			{
				return OperationResultModel.Error(ex);
			}
			return OperationResultModel.Success(null);
		}

		public OperationResultModel RestoreBackUp(BackupBindingModel model)
		{
			try
			{
				if (model.ArchiveFileName.IsNotEmpty())
				{ 
					if(model.FolderName.IsEmpty())
					{
						model.FolderName = $"{Path.GetDirectoryName(model.ArchiveFileName)}{Path.GetFileNameWithoutExtension(model.ArchiveFileName)}";
					}
					ZipFile.ExtractToDirectory(model.ArchiveFileName, model.FolderName);
				}
				var asm = typeof(DatabaseManager).Assembly;
				#region  вытаскиваем все типы-сущности (они должны быть унаследованы от IEntitySecurityExtenstion и иметь атрибут EntityDescription)
				List<Type> types = new();
				foreach (var t in asm.GetExportedTypes())
				{
					if (t.IsClass && t.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEntitySecurityExtenstion<>)) &&
						t.GetCustomAttribute<EntityDescriptionAttribute>() != null)
					{
						types.Add(t);
					}
				}
				#endregion

				#region  формиурем зависимости (в каком порядке загружать данные)
				Dictionary<Type, int> typesWithLevel = new();
				while (types.Count > 0)
				{
					for (int i = 0; i < types.Count; ++i)
					{
						var depends = types[i].GetCustomAttributes<EntityDependencyAttribute>();
						if ((depends == null || !depends.Any()) && !typesWithLevel.ContainsKey(types[i]))
						{
							typesWithLevel.Add(types[i], 0);
							types.RemoveAt(i--);
							continue;
						}

						int? level = null;
						foreach (var depend in depends)
						{
							var type = typesWithLevel.Keys.FirstOrDefault(x => x.Name == depend.ClassName);
							if (type != null)
							{
								if (!level.HasValue)
								{
									level = typesWithLevel[type];
								}
								else if (level < typesWithLevel[type])
								{
									level = typesWithLevel[type];
								}
							}
							else
							{
								level = null;
								break;
							}
						}

						if (level.HasValue)
						{
							typesWithLevel.Add(types[i], level.Value + 1);
							types.RemoveAt(i--);
							continue;
						}
					}
				}
				#endregion

				#region Удаляем записи сначала из тех, на которые никкто не ссылается и в последнюю оередь, те, на которые все ссылаются
				var deleteOrder = typesWithLevel.OrderByDescending(x => x.Value);
				MethodInfo delMethod = GetType().GetTypeInfo().GetDeclaredMethod("DeleteFromDB");
				foreach (var delElem in deleteOrder)
				{
					if (File.Exists(string.Format("{0}/{1}.json", model.FolderName, delElem.Key.Name)))
					{
						MethodInfo generic = delMethod.MakeGenericMethod(delElem.Key);
						generic.Invoke(this, null);
					}
				}
				#endregion

				#region Заполняем в порядке - сначала те, у которых нет родителей, потом их потомство
				MethodInfo method = GetType().GetTypeInfo().GetDeclaredMethod("LoadFromFile");
				foreach (var delElem in typesWithLevel.OrderBy(x => x.Value))
				{
					MethodInfo generic = method.MakeGenericMethod(delElem.Key);
					generic.Invoke(this, new object[] { model.FolderName, delElem.Key });
				}
				#endregion
			}
			catch (Exception ex)
			{
				return OperationResultModel.Error(ex);
			}
			return OperationResultModel.Success(null);
		}

		/// <summary>
		/// Сохранение списка сущности из БД в файл (вызывается через рефлексию)
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="folderName"></param>
		/// <param name="allowFullData"></param>
		/// <param name="t"></param>
		private void SaveToFile<T>(string folderName, bool allowFullData, Type t) where T : class, IEntitySecurityExtenstion<T>, new()
		{
			using var context = DatabaseManager.GetContext;
			var records = context.Set<T>().Select(x => x.SecurityCheck(x, allowFullData));
			DataContractJsonSerializer jsonFormatter = new(typeof(List<T>));
			using FileStream fs = new(string.Format("{0}/{1}.json", folderName, t.Name), FileMode.OpenOrCreate);
			jsonFormatter.WriteObject(fs, records);
		}

		/// <summary>
		/// Отчистка записей сущности в БД (вызывается через рефлексию)
		/// </summary>
		/// <typeparam name="T"></typeparam>
		private void DeleteFromDB<T>() where T : class, new()
		{
			using var context = DatabaseManager.GetContext;
			context.Set<T>().RemoveRange(context.Set<T>());
			context.SaveChanges();
		}

		/// <summary>
		/// Загрузка списка сущности из файла в БД (вызывается через рефлексию)
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="folderName"></param>
		/// <param name="t"></param>
		private void LoadFromFile<T>(string folderName, Type t) where T : class, new()
		{
			using var context = DatabaseManager.GetContext;
			if (File.Exists(string.Format("{0}/{1}.json", folderName, t.Name)))
			{
				DataContractJsonSerializer jsonFormatter = new(typeof(List<T>));
				using FileStream fs = new(string.Format("{0}/{1}.json", folderName, t.Name), FileMode.Open);
				List<T> records = (List<T>)jsonFormatter.ReadObject(fs);
				context.Set<T>().AddRange(records);
				context.SaveChanges();
			}
		}
	}
}