using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using Billing.Models;
using Billing.UI;
using Xamarin.Essentials;
using Resource = Billing.Languages.Resource;

namespace Billing.Store
{
    public class StoreHelper
    {
        public static readonly string PersonalFolder = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
        public static readonly string CacheFolder = FileSystem.CacheDirectory;

        private const string accountFile = "accounts.xml";
        private const string billFile = "bills.xml";
        private const string categoryFile = "categories.xml";

        private static StoreHelper instance;
        private static StoreHelper Instance => instance ??= new StoreHelper();

        public static List<Account> GetAccounts() => Instance.GetAccountsInternal();
        public static void WriteAccounts(IEnumerable<Account> accounts) => Instance.WriteAccountsInternal(accounts);
        public static List<Bill> GetBills() => Instance.GetBillsInternal();
        public static void WriteBills(IEnumerable<Bill> bills) => Instance.WriteBillsInternal(bills);
        public static List<Category> GetCategories() => Instance.GetCategoriesInternal();
        public static void WriteCategories(IEnumerable<Category> categories) => Instance.WriteCategoriesInternal(categories);

        private StoreHelper() { }

        private List<Account> GetAccountsInternal()
        {
            return GetList<Account>(Path.Combine(PersonalFolder, accountFile));
        }

        private void WriteAccountsInternal(IEnumerable<Account> accounts)
        {
            var filename = Path.Combine(PersonalFolder, accountFile);
            WriteList(filename, accounts);
        }

        private List<Bill> GetBillsInternal()
        {
            return GetList<Bill>(Path.Combine(PersonalFolder, billFile));
        }

        private void WriteBillsInternal(IEnumerable<Bill> bills)
        {
            var filename = Path.Combine(PersonalFolder, billFile);
            WriteList(filename, bills);
        }

        private List<Category> GetCategoriesInternal()
        {
            var list = GetList<Category>(Path.Combine(PersonalFolder, categoryFile));
            if (list == null || list.Count == 0)
            {
                list = new List<Category>
                {
                    // sample categories
                    new() { Id = 1, Name = Resource.Clothing, Icon = "clothes" },
                    new() { Id = 2, Name = Resource.Food, Icon = "food" },
                    new() { Id = 4, Name = Resource.Daily, Icon = "daily" },
                    new() { Id = 5, Name = Resource.Trans, Icon = "trans" },
                    new() { Id = 6, Name = Resource.Entertainment, Icon = "face" },
                    new() { Id = 7, Name = Resource.Learn, Icon = "learn" },
                    new() { Id = 8, Name = Resource.Medical, Icon = "medical" },
                    new() { Id = 9, Name = Resource.OtherSpending, Icon = "plus" },

                    new() { Id = 10, Type = CategoryType.Income, Name = Resource.Earnings, Icon = "#brand#btc" },
                    new() { Id = 20, Type = CategoryType.Income, Name = Resource.OtherIncome, Icon = "plus" },

                    // sub-categories
                    new() { Id = 100, ParentId = 1, Name = Resource.Jewellery, Icon = "gem" },
                    new() { Id = 101, ParentId = 1, Name = Resource.Cosmetics, Icon = "makeup" },
                    new() { Id = 102, ParentId = 2, Name = Resource.Brunch, Icon = "brunch" },
                    new() { Id = 103, ParentId = 2, Name = Resource.Dinner, Icon = "dinner" },
                    new() { Id = 104, ParentId = 2, Name = Resource.Drinks, Icon = "drink" },
                    new() { Id = 105, ParentId = 2, Name = Resource.Fruit, Icon = "fruit" },
                    new() { Id = 106, ParentId = 4, Name = Resource.UtilityBill, Icon = "bill" },
                    new() { Id = 107, ParentId = 4, Name = Resource.PropertyFee, Icon = "fee" },
                    new() { Id = 108, ParentId = 4, Name = Resource.Rent, Icon = "rent" },
                    new() { Id = 109, ParentId = 4, Name = Resource.Maintenance, Icon = "maintenance" },
                    new() { Id = 110, ParentId = 5, Name = Resource.LightRail, Icon = "rail" },
                    new() { Id = 111, ParentId = 5, Name = Resource.Taxi, Icon = "taxi" },
                    new() { Id = 112, ParentId = 6, Name = Resource.Fitness, Icon = "fitness" },
                    new() { Id = 113, ParentId = 6, Name = Resource.Party, Icon = "party" },
                    new() { Id = 200, ParentId = 10, Type = CategoryType.Income, Name = Resource.Salary, Icon = "#brand#buffer" },
                    new() { Id = 201, ParentId = 10, Type = CategoryType.Income, Name = Resource.Bonus, Icon = "dollar" },
                };
                Task.Run(() => WriteCategoriesInternal(list));
            }
            return list;
        }

        private void WriteCategoriesInternal(IEnumerable<Category> categories)
        {
            var filename = Path.Combine(PersonalFolder, categoryFile);
            WriteList(filename, categories);
        }

        #region Helper

        private void WriteList<T>(string filename, IEnumerable<T> list) where T : IModel, new()
        {
            if (list == null)
            {
                return;
            }
            try
            {
                using var stream = File.Open(filename, FileMode.Create);
                list.ToStream(stream);
            }
            catch (Exception ex)
            {
                Helper.Error("file.write", $"failed to write file: {filename}, error: {ex.Message}");
            }
        }

        private List<T> GetList<T>(string file) where T : IModel, new()
        {
            try
            {
                if (File.Exists(file))
                {
                    using var stream = File.OpenRead(file);
                    var list = ModelExtensionHelper.FromStream<T>(stream);
                    return list;
                }
            }
            catch (Exception ex)
            {
                Helper.Error("file.read", $"failed to read file: {file}, error: {ex.Message}");
            }
            return default;
        }

        #endregion
    }
}
