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 _accountId;
        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, string password)
        {
            var loginService = new LoginService();
            var loginResult = await loginService.Login(username, password);
            if (!loginResult.Ok)
            {
                onError.Invoke(loginResult.Message, 3);
                return;
            }

            // save user id, and new action service
            _accountId = loginResult.User.Id;
            _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
            });
        }

        public async Task GetProperty()
        {
            var queryResult = await _as.QueryInfo(_accountId);
            if (!queryResult.Ok)
            {
                onError.Invoke(queryResult.Message, 3);
                return;
            }

            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
            });
        }

        public async Task GetInventory(InventoryType typ)
        {
            var result = typ switch
            {
                InventoryType.HeroInventory => await _as.GetHeroes(_accountId),
                InventoryType.PropInventory => await _as.GetProps(_accountId),
                _ => throw new ArgumentOutOfRangeException(nameof(typ), typ, null)
            };

            if (!result.Ok)
            {
                onError.Invoke(result.Message, 3);
                return;
            }

            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);
        }

        public async Task DrawCard(int count)
        {
            var drawResult = await _as.Draw(_accountId, count);
            if (!drawResult.Ok)
            {
                onError.Invoke(drawResult.Message, 3);
                return;
            }

            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);
        }

    }
}