using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using MongoDB.Driver;
using Unity.UOS.Func.Stateless.Core.Attributes;
using UnityEngine;
using UOS.FuncStateless.MongoDB;

namespace CloudService
{
    public class Item
    {
        public int Type { get; set; }
        public int Count { get; set; }
        public int Level { get; set; }
        public string Name { get; set; }
    }

    public class QueryResult
    {
        public bool Ok { get; set; }
        public string Message { get; set; }
        public int Diamonds { get; set; }
        public int Coins { get; set; }
        public int LuckLevel { get; set; }
    }

    public class GetItemsResult
    {
        public bool Ok { get; set; }
        public string Message { get; set; }
        public Dictionary<string, Item> Items { get; set; }
    }

    public class DrawResult
    {
        public bool Ok { get; set; }
        public string Message { get; set; }
        public List<Item> Items { get; set; }
    }

    [CloudService]
    public class ActionService
    {
        [CloudFunc]
        public async Task<QueryResult> QueryInfo(string id)
        {
            var conn = await MongoConnectionManager.GetConnection();
            var database = conn.GetDatabase(MongoConfig.DBName);
            var collection = database.GetCollection<User>(MongoConfig.CollectionName);

            Debug.Log("call to query info");
            // var objectId = new ObjectId(id);
            var filter = Builders<User>.Filter.Eq("_id", id);
            var projection = Builders<User>.Projection
                .Include("Diamonds")
                .Include("Coins")
                .Include("LuckPoints")
                .Include("DrawCounts");

            var userDocument = await collection.Find(filter).Project(projection).FirstOrDefaultAsync();

            if (userDocument == null)
            {
                return new QueryResult
                {
                    Ok = false,
                    Message = "user not found"
                };
            }

            return new QueryResult
            {
                Ok = true,
                Diamonds = userDocument["Diamonds"].AsInt32,
                Coins = userDocument["Coins"].AsInt32,
                LuckLevel = Helper.CalcLuckLevel(userDocument["LuckPoints"].AsInt32, userDocument["DrawCounts"].AsInt32)
            };
        }

        [CloudFunc]
        public async Task<QueryResult> AddDiamonds(string id, int diff)
        {
            var conn = await MongoConnectionManager.GetConnection();
            var database = conn.GetDatabase(MongoConfig.DBName);
            var collection = database.GetCollection<User>(MongoConfig.CollectionName);
            var filter = Builders<User>.Filter.Eq("_id", id);
            var update = Builders<User>.Update.Inc(u => u.Diamonds, diff);
            var options = new FindOneAndUpdateOptions<User>
            {
                ReturnDocument = ReturnDocument.After,
                Projection = Builders<User>.Projection
                    .Include(u => u.Diamonds)
                    .Include(u => u.Coins)
            };

            var result = await collection.FindOneAndUpdateAsync(filter, update, options);

            if (result == null)
            {
                return new QueryResult
                {
                    Ok = false,
                    Message = "fail to add diamonds"
                };
            }

            return new QueryResult
            {
                Ok = true,
                Diamonds = result.Diamonds,
                Coins = result.Coins
            };
        }

        [CloudFunc]
        public async Task<QueryResult> AddCoins(string id, int diff)
        {
            var conn = await MongoConnectionManager.GetConnection();
            var database = conn.GetDatabase(MongoConfig.DBName);
            var collection = database.GetCollection<User>(MongoConfig.CollectionName);
            var filter = Builders<User>.Filter.Eq("_id", id);
            var update = Builders<User>.Update.Inc(u => u.Coins, diff);
            var options = new FindOneAndUpdateOptions<User>
            {
                ReturnDocument = ReturnDocument.After,
                Projection = Builders<User>.Projection
                    .Include(u => u.Diamonds)
                    .Include(u => u.Coins)
            };

            var result = await collection.FindOneAndUpdateAsync(filter, update, options);

            if (result == null)
            {
                return new QueryResult
                {
                    Ok = false,
                    Message = "fail to add coins"
                };
            }

            return new QueryResult
            {
                Ok = true,
                Diamonds = result.Diamonds,
                Coins = result.Coins
            };
        }

        [CloudFunc]
        public async Task<GetItemsResult> GetHeroes(string id)
        {
            var conn = await MongoConnectionManager.GetConnection();
            var database = conn.GetDatabase(MongoConfig.DBName);
            var collection = database.GetCollection<User>(MongoConfig.CollectionName);

            Debug.Log("call to get heroes");
            var filter = Builders<User>.Filter.Eq("_id", id);
            var projection = Builders<User>.Projection.Include(u => u.Heroes);

            var user = await collection.Find(filter).Project<User>(projection).FirstOrDefaultAsync();
            if (user == null)
            {
                return new GetItemsResult
                {
                    Ok = false,
                    Message = "fail to get heroes"
                };
            }

            return new GetItemsResult
            {
                Ok = true,
                Items = user.Heroes
            };
        }

        [CloudFunc]
        public async Task<GetItemsResult> GetProps(string id)
        {
            var conn = await MongoConnectionManager.GetConnection();
            var database = conn.GetDatabase(MongoConfig.DBName);
            var collection = database.GetCollection<User>(MongoConfig.CollectionName);

            Debug.Log("call to get props");
            var filter = Builders<User>.Filter.Eq("_id", id);
            var projection = Builders<User>.Projection.Include(u => u.Props);

            var user = await collection.Find(filter).Project<User>(projection).FirstOrDefaultAsync();
            if (user == null)
            {
                return new GetItemsResult
                {
                    Ok = false,
                    Message = "fail to get props"
                };
            }

            return new GetItemsResult
            {
                Ok = true,
                Items = user.Props
            };
        }

        [CloudFunc]
        public async Task<DrawResult> Draw(string id, int count)
        {
            var conn = await MongoConnectionManager.GetConnection();
            var database = conn.GetDatabase(MongoConfig.DBName);
            var collection = database.GetCollection<User>(MongoConfig.CollectionName);

            Debug.Log("call to draw");
            var filter = Builders<User>.Filter.Eq("_id", id);
            var user = await collection.Find(filter).FirstOrDefaultAsync();

            if (user == null)
            {
                return new DrawResult
                {
                    Ok = false,
                    Message = "user not found"
                };
            }

            if (count > user.Diamonds)
            {
                return new DrawResult
                {
                    Ok = false,
                    Message = "钻石不够抽卡",
                };
            }

            // start to draw
            var res = new List<Item>(count);
            var pool = user.DrawPool;
            if (pool == null || pool.Count == 0)
            {
                pool = Helper.NewDrawPool();
            }

            if (pool.Count < count)
            {
                var n = pool.Count;
                var needN = count - n;
                res.AddRange(pool);
                pool = Helper.NewDrawPool();

                var lastN = pool.Skip(pool.Count - needN).Take(needN).ToList();
                pool.RemoveRange(pool.Count - needN, needN);
                res.AddRange(lastN);
            }
            else
            {
                var lastCount = pool.Skip(pool.Count - count).Take(count).ToList();
                pool.RemoveRange(pool.Count - count, count);
                res.AddRange(lastCount);
                if (res.Any(e => e.Type == 0))
                {
                    pool = Helper.NewDrawPool();
                }
            }

            // update user's heroes, props, and other properties
            var heroes = user.Heroes;
            var props = user.Props;
            var coins = 0;
            var diamonds = 0;
            var luckPoints = 0;

            foreach (var e in res)
            {
                switch (e.Type)
                {
                    case 0:
                        if (!heroes.TryAdd(e.Name, e))
                            heroes[e.Name].Count += e.Count;
                        luckPoints += 100;
                        break;
                    case 1:
                        if (!props.TryAdd(e.Name, e))
                            props[e.Name].Count += e.Count;
                        luckPoints += 1;
                        break;
                    case 2:
                        switch (e.Name)
                        {
                            case "diamonds":
                                diamonds += e.Count;
                                luckPoints += e.Count;
                                break;
                            case "coins":
                                coins += e.Count;
                                luckPoints += (e.Count + 3000) / 10000;
                                break;
                        }
                        break;
                }
            }

            var update = Builders<User>.Update
                .Inc(u => u.Diamonds, -count + diamonds)
                .Inc(u => u.Coins, coins)
                .Inc(u => u.LuckPoints, luckPoints)
                .Inc(u => u.DrawCounts, count)
                .Set(u => u.DrawPool, pool)
                .Set(u => u.Heroes, heroes)
                .Set(u => u.Props, props);

            var result = await collection.UpdateOneAsync(filter, update);

            if (result.MatchedCount != 1)
            {
                return new DrawResult
                {
                    Ok = false,
                    Message = "fail to update new info"
                };
            }

            return new DrawResult
            {
                Ok = true,
                Items = res
            };
        }
    }
}
