using MongoDB.Bson;
using MongoDB.Bson.Serialization;
using MongoDB.Driver;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace updateServer
{
    internal class UpdateManager
    {

        public static UpdateManager mInstance = null;

        public static UpdateManager getInstance()
        {

            if (mInstance == null)
            {
                mInstance = new UpdateManager();
            }

            return mInstance;

        }

        private UpdateManager()
        {
            init();
            ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12;
        }

        MongoClient mDBClient = null;

        IMongoDatabase mEventDataBase = null;

        string mReceivedEventToken = "";

        bool isUpdateWork = false;

        public string GameKey = "";
        public string GameName = "";


        public bool init()
        {

            try
            {

                mDBClient = new MongoClient(DEFINE.몽고DB_접속정보);

                mEventDataBase = mDBClient.GetDatabase("datalol");

                //mUpdateWorkerTable = new Dictionary<string, List<updateWorkObject>>();
                mUpdateWorkerTable = new Dictionary<string, updateWorkObject>();

                return true;
            }
            catch (Exception ex)
            {
#if (DEBUG)
                {
                    System.Windows.Forms.MessageBox.Show(ex.ToString());
                }
#endif
                return false;
            }

        }


        //internal Dictionary<string, List<updateWorkObject>> mUpdateWorkerTable = null;

        internal object mWorkerTableLocker = new object();

        internal Dictionary<string, updateWorkObject> mUpdateWorkerTable = null;

        internal void startUpdateEventRaw(string gameID, bool isProgress)
        {

            lock (mWorkerTableLocker)
            {
                //bool isRemoved = false;
                List<string> keys = new List<string>();
                foreach (string s in mUpdateWorkerTable.Keys) keys.Add(s);
                foreach (string s in keys)
                {
                    mUpdateWorkerTable[s].stopUpdateWork();
                    mUpdateWorkerTable.Remove(s);
                    //isRemoved = true;
                }

                //if (isRemoved) Program.mMainForm.Msg();



                if (mUpdateWorkerTable.ContainsKey(gameID))
                {
                    mUpdateWorkerTable[gameID].stopUpdateWork();
                    mUpdateWorkerTable.Remove(gameID);

                }
                mUpdateWorkerTable.Add(gameID, new updateWorkObject(gameID, isProgress));
                mUpdateWorkerTable[gameID].startUpdateWork();
            } 
        }

        internal void RemoveAllEventRaw()
        {

            lock (mWorkerTableLocker)
            {
                //bool isRemoved = false;
                List<string> keys = new List<string>();
                foreach (string s in mUpdateWorkerTable.Keys) keys.Add(s);
                foreach (string s in keys)
                {
                    mUpdateWorkerTable[s].stopUpdateWork();
                    mUpdateWorkerTable.Remove(s); 
                }
            }
        }

        public async Task RemoveBackup()
        {
            MongoClient dd = new MongoClient(DEFINE.몽고DB_접속정보);
            var collectionNames = dd.GetDatabase("datalol").ListCollectionNames().ToList();
            var mEventDataBaseTarget = dd.GetDatabase("datalol");
            

            foreach (string item in collectionNames)
            { 
                await mEventDataBaseTarget.GetCollection<BsonValue>(item)
                       .DeleteManyAsync(x => true);
            }
            
            mUpdateWorkerTable.Clear();
        }

        /// <summary>
        /// 라이엇 API에 경기중인 게임리스트를 요청한다.
        /// </summary>
        /// <param name="isTest"></param>
        /// <returns></returns>
        internal Dictionary<string, string> GameListUpdateWorker(bool isTest)
        {

            //string bufRequestURL = DEFINE.라이엇_게임리스트_REQUEST_URL + (isTest ? "platformGames" : "esportsGames") + "?state=in_progress";;
            string bufRequestURL = DEFINE.라이엇_게임리스트_REQUEST_URL + (isTest ? "platformGames" : "esportsGames") + "?state=in_progress"; ;
            //string bufRequestURL = DEFINE.라이엇_게임리스트_REQUEST_URL + "esportsGames" + "?state=finished"; ;

            string recvValue = requestRiotData(bufRequestURL);

            IEnumerable<BsonValue> bufGameList = null;

            List<string> updateRoomList = new List<string>();

            Dictionary<String, string> rtnValue = new Dictionary<string, string>();

            ///가져온 데이터가 null이 아니라면 데이터를 Game단위(Document)로 짤라서 Enumarable화 하고, 아니면 빈 결과를 리턴한다.
            if (recvValue != null)
            {
                bufGameList = BsonSerializer.Deserialize<BsonArray>(recvValue).Select(p => p.AsBsonDocument);
            }
            else
            {
                Program.mMainForm.updateGameRoomList(updateRoomList);
                return rtnValue;
            }


            //가져온 데이터에서 방제와 플랫폼게임ID를 파싱해서 KV페어로 만든다.
            foreach (BsonValue item in bufGameList)
            {
                string bufString = "";

                ///20210615테스트할부분 방을 새로만든부분이 있을경우 Document 순서에서 나중에 들어오므로 과거에 들어왔던 데이터를 버린다.
                ///혹시 모르니 안버린다.
                //if (rtnValue.ContainsValue(item["gameName"].ToString()))
                //{
                //    rtnValue.Remove(rtnValue.FirstOrDefault(x => x.Value == item["gameName"].ToString()).Key);
                //}

                if (isTest)
                {
                    rtnValue.Add(item["platformGameId"].ToString(), item["gameName"].ToString());

                    bufString = item["platformGameId"].ToString() + "_" + item["gameName"].ToString();

                    updateRoomList.Add(bufString);
                }
                else
                {

                    BsonDocument itemDocument = item.AsBsonDocument["platformGames"].AsBsonArray.Last().ToBsonDocument();

                    rtnValue.Add(itemDocument["platformGameId"].ToString(), itemDocument["gameName"].ToString());

                    bufString = itemDocument["platformGameId"].ToString() + "_" + itemDocument["gameName"].ToString();

                    updateRoomList.Add(bufString);
                }

#if (DEBUG)
                {
                    Console.WriteLine(bufString);
                }
#endif
            }
            ///완성된 방 정보를 UI에 업데이트한다.
            Program.mMainForm.updateGameRoomList(updateRoomList);

            return rtnValue;

        }


        /// <summary>
        /// 끝난경기의 정보는 방제만으로 알 수 없기때문에 선수를 검색해서 경기를 찾기위한 게임 구조 클래스
        /// </summary>
        internal class game
        {
            internal string gamename = "";
            internal List<string> playerList = new List<string>();
        }


        /// <summary>
        /// 라이엇 API에서 끝난경기의 정보를 받아와서 gamelist.txt파일로 저장한다.
        /// </summary>
        /// <returns></returns>
        internal void finishGameListUpdateWorker()
        {

            try
            {


            Dictionary<string, game> rtnValue = new Dictionary<string, game>();

            string bufRequestURL = DEFINE.라이엇_게임리스트_REQUEST_URL + "esportsGames" + "?state=finished"; ;

            string recvValue = requestRiotData(bufRequestURL);

            IEnumerable<BsonValue> bufGameList = null;


            if (recvValue != null)
            {
                bufGameList = BsonSerializer.Deserialize<BsonArray>(recvValue).Select(p => p["platformGames"]);
            }
            else
            {
                return;
            }


            foreach (BsonArray item in bufGameList)
            {
                BsonDocument selectItem = item.Last().ToBsonDocument();

                game bufGame = new game();

                bufGame.gamename = selectItem["gameName"].ToString();

                BsonArray players = selectItem["participants"].AsBsonArray;

                List<string> bufplayerList = new List<string>();

                foreach (BsonDocument itemp in players)
                {
                    bufplayerList.Add(itemp["summonerName"].ToString());
                }
                if (players.Count == 0)
                {
                    string dd = "";
                }

                bufGame.playerList = bufplayerList;

                rtnValue.Add(selectItem["platformGameId"].ToString(), bufGame);

                string players123 = "";
                foreach (string item2 in bufGame.playerList)
                {
                    players123 += item2 + "_";
                }

                File.AppendAllText("gameList.txt", selectItem["platformGameId"].ToString() + "     " + selectItem["gameName"].ToString() + "         " + players123 + Environment.NewLine);

            }



      

            }
            catch (Exception ex)
            {

                System.Windows.Forms.MessageBox.Show("오류발생 - " + ex.Message);

           
            }


        }


        /// <summary>
        /// 완성된 URL을 통해 라이엇에 데이터를 요청하여 BsonDocument Type으로 결과를 리턴받는다.
        /// </summary>
        /// <param name="requestURL"></param>
        /// <returns></returns>
        string requestRiotData(string requestURL)
        {

            try
            {

            ///20210809 SSL접속문제로 인해 서버인증기능을 끈 부분을 추가한다 이거 이래도 되나?
            //ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };

            ///가져온 URL에 맞춰 리퀘스트를 만든다.
            HttpWebRequest req = (HttpWebRequest)WebRequest.Create(requestURL);

            WebHeaderCollection hd = new WebHeaderCollection();

            hd.Add(DEFINE.RIOT_API_KEY);

            req.Headers = hd;

            WebResponse rsp = req.GetResponse();

            string result = "";

            ///가져온 데이터를 문자열화 하고, Bson형태로 파싱해서 리턴한다. 
            using (var reader = new StreamReader(rsp.GetResponseStream()))
            {
                result = reader.ReadToEnd();
            }

            if (result.Trim() == "[]")
            {
                return null;
            }
            
            return result;


            }
            catch (Exception ex)
            {
                return ex.ToString();
                
            }

        }


        /// <summary>
        /// EventRaw를 데이터를 업데이트 하기위한 UpdateWorker Class
        /// </summary>
        internal class updateWorkObject
        {

            string mGameID = "";
            bool mIsProgress = false;
            object tokenLocker = new object();

            string receviedEventToken = "";

            internal string mReceivedEventToken
            {
                get
                {

                    lock (tokenLocker)
                    {
                        return receviedEventToken;
                    }

                }
                set
                {

                    lock (tokenLocker)
                    {
                        receviedEventToken = value;
                    }

                }
            }

            Thread mUpdateWorker = null;
            internal bool isUpdateWork = false;

            internal updateWorkObject(string gameID, bool recvProgress)
            {
                this.mGameID = gameID;
                this.mIsProgress = recvProgress;
            }

            internal void startUpdateWork()
            {
                //mUpdateWorker = new Thread(updateTestDataWork);
                Program.mMainForm.Invoke(new System.Windows.Forms.MethodInvoker(() => { Program.mMainForm.removeWorkerList(); }));



                mUpdateWorker = new Thread(updateWork);
                //mUpdateWorker = new Thread(updateWorkTest);
                mUpdateWorker.IsBackground = true;
                isUpdateWork = true;
                mUpdateWorker.Start();

                
                Program.mMainForm.Invoke(new System.Windows.Forms.MethodInvoker(() => { Program.mMainForm.updateWorkerList(); }));

                //Program.mMainForm.Invoke(new System.Windows.Forms.MethodInvoker(() => { Program.mMainForm.Msg(); }));
            }

            int tokenCount = 0;

            internal void stopUpdateWork()
            {
                isUpdateWork = false;
            }


            void updateWork()
            {

                while (isUpdateWork)
                {
                    try
                    {

                        string bufRequestURL = DEFINE.라이엇_이벤트행데이터_REQUEST_URL + mGameID + "/events?paginationToken=" + mReceivedEventToken;

                        BsonDocument bufRecvPayload = BsonDocument.Parse(UpdateManager.getInstance().requestRiotData(bufRequestURL));

                        ///경기가 끝났음
                        if (bufRecvPayload.Contains("missingEventsStatus"))
                        {
                            if (bufRecvPayload["missingEventsStatus"].ToString() == "lost_permanently")
                            {
                                isUpdateWork = false;
                            }
                            else
                            {
                                mReceivedEventToken = bufRecvPayload["nextPageToken"].ToString();
                            }

                        }
                        else if (bufRecvPayload.Contains("nextPageToken"))
                        {
                            mReceivedEventToken = bufRecvPayload["nextPageToken"].ToString();
                        }
                        //다음페이지토큰이 들어오지 않는 상황에는 updatework를 종료하도록 유도함.
                        else if (!bufRecvPayload.Contains("nextPageToken"))
                        {
                            isUpdateWork = false;
                        }

                        ///20210516 이 방식대로 데이터를 쌓게 되면 데이터를 파싱하는데 너무 복잡하고 오래걸림
                        //UpdateManager.getInstance().mEventDataBase.GetCollection<BsonValue>((mIsProgress ? "in_progress_" : "") + "event_raw")
                        //    .UpdateOne(
                        //        x => x["nextPageToken"] == bufRecvPayload["nextPageToken"],
                        //        Builders<BsonValue>.Update.Set(x => x["events"], bufRecvPayload["events"]),
                        //        new UpdateOptions() { IsUpsert = true }
                        //    );

                        ///토큰에 할당된 이벤트로우를 배열로 가져옴
                        BsonArray bufDataList = bufRecvPayload["events"].AsBsonArray;


                        ///DB에 Duplicate를 할 수 있는 bulkwrite를 하기위한 WriteModel형식으로 만듬.
                        ///Bulkwrite를 선택하면서 DB에서 한번에 유지할수있는 경기의 수가 많이 줄어들었다.
                        Dictionary<string, List<WriteModel<BsonValue>>> bufDataTable = new Dictionary<string, List<WriteModel<BsonValue>>>();

                        foreach (BsonDocument item in bufDataList)
                        {

                            BsonDocument bufUpdateValue = new BsonDocument();

                            ///rfc461Schema에 해당 event의 종류가 들어있음.
                            string bufStatus = item["rfc461Schema"].ToString();

                            //DB에서 경기를 관리하기 위한 Key로 RequestGameID와 sequenceIndex를 사용.
                            bufUpdateValue.Add("RequestGameID", mGameID);

                            bufUpdateValue.Add(new BsonElement("sequenceIndex", Convert.ToInt32(item["sequenceIndex"])));

                            var gameFilter = Builders<BsonValue>.Filter.Eq(x => x["RequestGameID"], mGameID);

                            var sequanceFilter = Builders<BsonValue>.Filter.Eq(x => x["sequenceIndex"], item["sequenceIndex"]);

                            var Parentfilter = Builders<BsonValue>.Filter.And(gameFilter, sequanceFilter);



                            ///Value를 eventDocument에 탑재.
                            bufUpdateValue.Add("eventDocument", item);

                            UpdateOneModel<BsonValue> updateRaw = new UpdateOneModel<BsonValue>(
                                Parentfilter,
                                Builders<BsonValue>.Update.Set(x => x["eventDocument"], item)
                                )
                            { IsUpsert = true };

                            if (!bufDataTable.ContainsKey(bufStatus))
                            {
                                bufDataTable.Add(bufStatus, new List<WriteModel<BsonValue>>());
                            }


                            bufDataTable[bufStatus].Add(updateRaw);

                        }


                        foreach (var item in bufDataTable)
                        {

                            UpdateManager.getInstance().mEventDataBase.GetCollection<BsonValue>((mIsProgress ? "in_progress_" : "") + item.Key)
                                .BulkWriteAsync(item.Value);

                        }


                        Console.WriteLine(tokenCount + " : " + bufDataList.Count() + " : " + DateTime.Now.ToLongTimeString() + " : " + mReceivedEventToken);
                        tokenCount += 1;


                        Thread.Sleep(500);


                    }
                    catch (Exception ex)
                    {

                        Console.WriteLine(ex.ToString());

                        isUpdateWork = false;

                    }

                }


                //isUpdate가 false로 빠지는 경우 업데이트중인 게임 목록에서 해당 게임을 지운다.
                lock (UpdateManager.getInstance().mWorkerTableLocker)
                {
                    UpdateManager.getInstance().mUpdateWorkerTable.Remove(this.mGameID);
                }

                Program.mMainForm.Invoke(new System.Windows.Forms.MethodInvoker(() => { Program.mMainForm.updateWorkerList(); }));

            }


            void updateWorkTest()
            {

                while (isUpdateWork)
                {

                    ThreadPool.QueueUserWorkItem(o => { updateRoutineForTest(); });
                    Thread.Sleep(1000);

                }

                lock (UpdateManager.getInstance().mWorkerTableLocker)
                {
                    UpdateManager.getInstance().mUpdateWorkerTable.Remove(this.mGameID);
                }

                Program.mMainForm.Invoke(new System.Windows.Forms.MethodInvoker(() => { Program.mMainForm.updateWorkerList(); }));

            }

            //테스트를위한 업데이트루틴
            void updateRoutineForTest()
            {
                try
                {

                    string bufRequestURL = DEFINE.라이엇_이벤트행데이터_REQUEST_URL + mGameID + "/events?paginationToken=" + mReceivedEventToken;

                    BsonDocument bufRecvPayload = BsonDocument.Parse(UpdateManager.getInstance().requestRiotData(bufRequestURL));


                    ///경기가 끝났음
                    if (bufRecvPayload.Contains("missingEventsStatus"))
                    {
                        if (bufRecvPayload.Contains("lost_permanently"))
                        {
                            isUpdateWork = false;
                        }
                        else
                        {
                            mReceivedEventToken = bufRecvPayload["nextPageToken"].ToString();
                        }

                    }
                    else if (bufRecvPayload.Contains("nextPageToken"))
                    {
                        mReceivedEventToken = bufRecvPayload["nextPageToken"].ToString();
                    }
                    //다음페이지토큰이 들어오지 않는 상황에는 updatework를 종료하도록 유도함.
                    else if (!bufRecvPayload.Contains("nextPageToken"))
                    {
                        isUpdateWork = false;
                    }

                    ///20210516 이 방식대로 데이터를 쌓게 되면 데이터를 파싱하는데 너무 복잡하고 오래걸림
                    //UpdateManager.getInstance().mEventDataBase.GetCollection<BsonValue>((mIsProgress ? "in_progress_" : "") + "event_raw")
                    //    .UpdateOne(
                    //        x => x["nextPageToken"] == bufRecvPayload["nextPageToken"],
                    //        Builders<BsonValue>.Update.Set(x => x["events"], bufRecvPayload["events"]),
                    //        new UpdateOptions() { IsUpsert = true }
                    //    );

                    var filterGameID = Builders<BsonValue>.Filter.Eq(x => x["RequestGameID"], mGameID);

                    BsonArray bufDataList = bufRecvPayload["events"].AsBsonArray;


                    //var filter = Builders<BsonDocument>.Filter.Eq("_id", ObjectId.Parse(dbPro.Id));
                    //var update = Builders<BsonDocument>.Update.Push("tags", buildBsonArrayFromTags(pro.Tags));
                    //var result = collection.UpdateOne(filter, update);
                    //if (result.IsModifiedCountAvailable)
                    //{
                    //    if (result.ModifiedCount == 1)
                    //    {
                    //        return true;
                    //    }

                    //}

                    //벌크인서트를 위한 해시테이블
                    //Dictionary<string, BsonArray> bufBulkInsertTable = new Dictionary<string, BsonArray>();

                    //foreach (BsonDocument item in bufDataList)
                    //{

                    //    ///이벤트네임을 키값으로 하고 벌크인서트를 위해 데이터를 나눔.
                    //    string bufEventName = item["rfc461Schema"].ToString();

                    //    if (!bufBulkInsertTable.ContainsKey(bufEventName))
                    //    {
                    //        bufBulkInsertTable.Add(bufEventName, new BsonArray());
                    //    }

                    //    bufBulkInsertTable[bufEventName].Add(item);

                    //}

                    //                    Dictionary<string, List<WriteModel<BsonDocument>>> bulkModel = new Dictionary<string, List<WriteModel<BsonDocument>>>();


                    //                    //경기정보를위해 전체정보를 각각의 이벤트에 맞는 콜렉션에 저장함(누락을 막기위해 동기화).
                    //                    foreach (var item in bufDataList)
                    //                    {

                    //                        var bufFilter = Builders<BsonDocument>.Filter.Eq("RequestGameID", mGameID);
                    //                        var bufUpdate = Builders<BsonDocument>.Update.Push("events", item);

                    //                        UpdateOneModel<BsonDocument> updateRaw = new UpdateOneModel<BsonDocument>(
                    //                                bufFilter, bufUpdate) { IsUpsert = true };

                    //                        if (!bulkModel.ContainsKey(item["rfc461Schema"].ToString()))
                    //                        {
                    //                            bulkModel.Add(item["rfc461Schema"].ToString(), new List<WriteModel<BsonDocument>>());
                    //                        }

                    //                        bulkModel[item["rfc461Schema"].ToString()].Add(updateRaw);

                    ////                        var resultOne = collectionEvent.UpdateMany(bufFilter, bufUpdate, new UpdateOptions() { IsUpsert = true });

                    //                    }

                    //                    foreach (var item in bulkModel)
                    //                    {
                    //                        var result =  UpdateManager.getInstance().mEventDataBase.GetCollection<BsonDocument>(item.Key).BulkWrite(item.Value);


                    //                    }


                    foreach (var item in bufDataList)
                    {
                        var bufFilter = Builders<BsonDocument>.Filter.Eq("RequestGameID", mGameID);
                        var bufUpdate = Builders<BsonDocument>.Update.Push("events", item);
                        var collectionEvent = UpdateManager.getInstance().mEventDataBase.GetCollection<BsonDocument>(item["rfc461Schema"].ToString());

                        var resultOne = collectionEvent.UpdateOne(bufFilter, bufUpdate, new UpdateOptions() { IsUpsert = true });
                    }


                    ////로그를위해 전체정보를 eventraws콜렉션에 저장함(비동기).
                    //var collectionAll = UpdateManager.getInstance().mEventDataBase.GetCollection<BsonDocument>("eventraws");
                    //var filterAll = Builders<BsonDocument>.Filter.Eq("RequestGameID", mGameID);

                    //foreach (var item in collection)
                    //{

                    //}

                    //var updateAll = Builders<BsonDocument>.Update.Push("events", bufDataList);
                    //var result2 = collectionAll.UpdateManyAsync(filterAll, updateAll, new UpdateOptions() { IsUpsert = true });

                    Console.WriteLine(tokenCount + " : " + bufDataList.Count() + " : " + DateTime.Now.ToLongTimeString() + " : " + mReceivedEventToken);
                    tokenCount += 1;


                    Thread.Sleep(1000);


                }
                catch (Exception ex)
                {

                    Console.WriteLine(ex.ToString());

                    isUpdateWork = false;

                }
            }

        }



    }
}
