using inutralia.Abstractions;
using inutralia.Models;
using Newtonsoft.Json;
using Plugin.Settings;
using Plugin.Settings.Abstractions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace inutralia.API
{
    /// <summary>
    /// Servicio de datos que utiliza el plugin de Settings para almacenar modelos
    /// </summary>
    public class LocalDataService : IDataPersistenceService
    {
        // Accesor a los settings
        private static ISettings AppSettings => CrossSettings.Current;

#pragma warning disable CS1998 // Sabemos que no vamos a poner await en los métodos async, por lo que deshabilitamos el warning

        public async Task<bool> DeleteItemAsync<T> (T item) where T : IIdentifiableEntity
        {
            Type type = item.GetType ();
            string path = GetItemPath (item);
            if(AppSettings.Contains(path))
            {
                // Eliminar elemento
                AppSettings.Remove (path);

                // Eliminar de la lista de elementos
                var list = GetIdList (type);
                list.Remove (item.Id);
                SetIdList (type, list);
            } //endif

            return true;
        }

        public async Task<T> GetItemAsync<T> (int id) where T : IIdentifiableEntity
        {
            string path = string.Format ("Models/{0}/{1}", GetDataPath (typeof (T)), id);
            if(AppSettings.Contains(path))
            {
                var content = AppSettings.GetValueOrDefault<string> (path);
                return JsonConvert.DeserializeObject<T> (content);
            } //endif

            return default (T);
        }

        public async Task<bool> RefreshItemAsync<T> (T item) where T : IIdentifiableEntity
        {
            string path = GetItemPath (item);
            if(AppSettings.Contains(path))
            {
                var content = AppSettings.GetValueOrDefault<string> (path);
                JsonConvert.PopulateObject (content, item);
                return true;
            } //endif

            return false;
        }

        public async Task<List<T>> RefreshListAsync<T> () where T : IIdentifiableEntity
        {
            Type type = typeof (T);
            string basePath = GetDataPath (type);
            List<int> idList = GetIdList (type);
            List<T> retVal = new List<T> ();
            List<int> idsToRemove = new List<int> ();

            foreach(int id in idList)
            {
                string path = string.Format ("Models/{0}/{1}", basePath, id);
                if (AppSettings.Contains (path))
                {
                    var content = AppSettings.GetValueOrDefault<string> (path);
                    retVal.Add (JsonConvert.DeserializeObject<T> (content));
                }
                else
                {
                    idsToRemove.Add (id);
                } //endif
            } //endforeach

            if(idsToRemove.Count > 0)
            {
                // Borrar de la lista inicial los que haya que elilminar, y guardar la lista
                idList.RemoveAll ((i) => { return idsToRemove.Contains (i); });
                SetIdList (type, idList);
            } //endif

            return retVal;
        }

        public async Task<int?> UpdateItemIngredientAsync<T>(T item, bool isNew = false) where T : IIdentifiableEntity
        {
            if (isNew)
            {
                // Elemento nuevo: asignarle Id. Cogemos la lista de ids ...
                List<int> idList = GetIdList(item.GetType());

                // ... buscamos el máximo y le sumamos 1
                //item.Id = (idList.Count < 1) ? 1 : idList.Max () + 1;

                // ... añadimos el nuevo id a la lista
                idList.Add(item.Id);

                // ... y la guardamos
                SetIdList(item.GetType(), idList);
            }
            else if (!AppSettings.Contains(GetItemPath(item)))
            {
                // El elemento no es nuevo, pero no existe en los datos guardados
                return null;
            } //endif

            // Guardar elemento
            AppSettings.AddOrUpdateValue(GetItemPath(item), JsonConvert.SerializeObject(item));

            // Retornar su id
            return item.Id;
        }


        public async Task<int?> UpdateItemAsync<T> (T item, bool isNew = false) where T : IIdentifiableEntity
        {
            if(isNew)
            {
                // Elemento nuevo: asignarle Id. Cogemos la lista de ids ...
                List<int> idList = GetIdList (item.GetType ());

                // ... buscamos el máximo y le sumamos 1
                item.Id = (idList.Count < 1) ? 1 : idList.Max () + 1;

                // ... añadimos el nuevo id a la lista
                idList.Add (item.Id);

                // ... y la guardamos
                SetIdList (item.GetType (), idList);
            }
            else if (!AppSettings.Contains(GetItemPath(item)))
            {
                // El elemento no es nuevo, pero no existe en los datos guardados
                return null;
            } //endif

            // Guardar elemento
            AppSettings.AddOrUpdateValue (GetItemPath (item), JsonConvert.SerializeObject (item));

            // Retornar su id
            return item.Id;
        }

#pragma warning restore CS1998 // Sabemos que no vamos a poner await en los métodos async, por lo que deshabilitamos el warning

        private static List<int> GetIdList (Type t, List<int> listToFill = null)
        {
            // Crear lista si no nos pasan ninguna. Vaciarla.
            List<int> retVal = listToFill ?? new List<int> ();
            retVal.Clear ();

            // Leer lista de ids (sólo si existe)
            string path = string.Format ("ModelIdList/{0}", GetDataPath (t));
            if (AppSettings.Contains (path))
            {
                // Deserializar valor json
                string value = AppSettings.GetValueOrDefault<string> (path);
                JsonConvert.PopulateObject (value, retVal);
            } //endif

            return retVal;
        }

        private static void SetIdList (Type t, List<int> idList)
        {
            string path = string.Format ("ModelIdList/{0}", GetDataPath (t));

            if ((idList == null) || (idList.Count < 1))
            {
                // Si la lista está vacía, borrar la entrada de settings
                AppSettings.Remove (path);
            }
            else
            {
                // Serializar con JSON y guardar
                string value = JsonConvert.SerializeObject (idList);
                AppSettings.AddOrUpdateValue (path, value);
            } //endif
        }

        /// <summary>
        /// Retorna el path dentro del api para acceder a cierto tipo de entidad
        /// </summary>
        /// <param name="t">Tipo de dato de la entidad</typeparam>
        /// <returns>El path correspondiente dentro del API</returns>
        private static string GetDataPath (Type t)
        {
            // Si tiene el atributo DataPath, lo utilizamos
            DataPathAttribute attr = t.GetTypeInfo ().GetCustomAttribute<DataPathAttribute> ();
            if (attr != null)
                return attr.DataPath;

            // En caso contrario devolvemos el nombre de la clase en minúsculas y en plural
            return t.Name.ToLowerInvariant () + "s";
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="item"></param>
        /// <returns></returns>
        private static string GetItemPath<T> (T item) where T: IIdentifiableEntity
        {
            return string.Format ("Models/{0}/{1}", GetDataPath (item.GetType ()), item.Id);
        }


    }
}
