using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using System.Threading.Tasks;
using Models;
using UnityEngine.Events;
using CloudService;
using Item = Models.Item;

namespace Unity.Func.Sample.Net
{
    public class NetworkManager : MonoBehaviour
    {
        public UnityEvent<Account> onLogin;
        public UnityEvent<Property> onProperty;
        public UnityEvent<List<Item>> onInventory;
        public UnityEvent<List<Item>> onHeroes;
        public UnityEvent<List<Item>> onDrawCard;
        public UnityEvent<string, int> onError;

        public static NetworkManager Instance { get; private set; }
        private string _saveId;
        private User _user;
        private ActionService _as;

        // Start is called before the first frame update
        private void Start()
        {
            if (Instance != null)
            {
                Destroy(this);
            }
            else
            {
                Instance = this;
            }
        }

        public async Task Login(string username)
        {
            try
            {
                var loginService = new LoginService();
                var loginResult = await loginService.Login(username);
                if (!loginResult.Ok)
                {
                    throw new Exception(loginResult.Message);
                }
                _saveId = loginResult.SaveId;
                _user = loginResult.User;
                _as = new ActionService();

                Debug.Log($"loginResult: {loginResult.User.Nickname}, {loginResult.User.Coins}, {loginResult.User.Diamonds}");

                // invoke ui update
                onLogin.Invoke(new Account
                {
                    Nickname = loginResult.User.Nickname,
                    Coins = loginResult.User.Coins,
                    Diamonds = loginResult.User.Diamonds
                });
            }
            catch (Exception e)
            {
                onError.Invoke(e.Message, 3);
            }
        }

        public async Task GetProperty()
        {
            try
            {
                // TODO: add local cache
                // var queryResult = await _as.QueryInfo(_saveId);
                // Debug.Log($"getProperty: " +
                //           $"diamonds={queryResult.Diamonds}, " +
                //           $"coins={queryResult.Coins}, " +
                //           $"luckLevel={queryResult.LuckLevel}");
                //
                // // invoke ui update
                // onProperty.Invoke(new Property
                // {
                //     Diamonds = queryResult.Diamonds,
                //     Coins = queryResult.Coins,
                //     LuckLevel = queryResult.LuckLevel
                // });
                onProperty.Invoke(new Property
                {
                    Diamonds = _user.Diamonds,
                    Coins = _user.Coins,
                    LuckLevel = CalcLuckLevel(_user.LuckPoints, _user.DrawCounts)
                });
            }
            catch (Exception e)
            {
                onError.Invoke(e.Message, 3);
            }
        }

        public async Task GetInventory(InventoryType typ)
        {
            try
            {
                // var result = typ switch
                // {
                //     InventoryType.HeroInventory => await _as.GetHeroes(_saveId),
                //     InventoryType.PropInventory => await _as.GetProps(_saveId),
                //     _ => throw new ArgumentOutOfRangeException(nameof(typ), typ, null)
                // };
                //
                // var listResult = new List<Item>(result.Items.Count);
                // listResult.AddRange(result.Items.Select(it => new Item
                // {
                //     Type = it.Value.Type switch
                //     {
                //         0 => ItemType.Hero,
                //         1 => ItemType.Prop,
                //         _ => ItemType.Other
                //     },
                //     Name = it.Value.Name,
                //     Count = it.Value.Count,
                //     Level = it.Value.Level
                // }));
                //
                // if (typ == InventoryType.HeroInventory)
                //     onHeroes.Invoke(listResult);
                // else
                //     onInventory.Invoke(listResult);

                var items = typ switch
                {
                    InventoryType.HeroInventory => _user.Heroes,
                    InventoryType.PropInventory => _user.Props,
                    _ => throw new ArgumentOutOfRangeException(nameof(typ), typ, null)
                };


                var listResult = new List<Item>(items.Count);
                listResult.AddRange(items.Select(it => new Item
                {
                    Type = it.Value.Type switch
                    {
                        0 => ItemType.Hero,
                        1 => ItemType.Prop,
                        _ => ItemType.Other
                    },
                    Name = it.Value.Name,
                    Count = it.Value.Count,
                    Level = it.Value.Level
                }));

                if (typ == InventoryType.HeroInventory)
                    onHeroes.Invoke(listResult);
                else
                    onInventory.Invoke(listResult);

            }
            catch (Exception e)
            {
                onError.Invoke(e.Message, 3);
            }
        }

        public async Task DrawCard(int count)
        {
            try
            {
                var drawResult = await _as.Draw(_saveId, count);
                // update cache
                if (!drawResult.Ok)
                {
                    throw new Exception(drawResult.Message);
                }
                _user = drawResult.User;
                var listResult = new List<Item>(drawResult.Items.Count);
                listResult.AddRange(drawResult.Items.Select(it => new Item
                {
                    Type = it.Type switch
                    {
                        0 => ItemType.Hero,
                        1 => ItemType.Prop,
                        _ => ItemType.Other
                    },
                    Name = it.Name,
                    Count = it.Count,
                    Level = it.Level
                }));

                onDrawCard.Invoke(listResult);
            }
            catch (Exception e)
            {
                onError.Invoke(e.Message, 3);
            }
        }

        private static int CalcLuckLevel(int points, int counts)
        {
            // 5 levels
            // level 4: 100/10 = 10
            // level 3: 100/30 = 3.3
            // level 2: 100/50 = 2
            // level 1: 100/70 = 1.4
            // level 0

            // default level 2
            if (counts == 0) return 2;
            var luckPt = (double)points / counts;
            return luckPt switch
            {
                >= 11 => 4,
                >= 3.6 => 3,
                >= 2.2 => 2,
                >= 1.5 => 1,
                _ => 0
            };
        }

    }
}