﻿using System;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using System.Text;

namespace GLogUnity
{
	public class GLogChannelGURaaSLogger
	{
		const string GRG_URL = "https://grg.service.guraas.com";

		private bool freshMessage = true;
		private bool completedMessage = false;

		private StringBuilder logLine = new StringBuilder();
		private SessionInfo activeSession;

		private List<string> cachedMessages;
		private bool HasCache => cachedMessages.Count > 0;

		private string logFilePath;
		private string cacheFilePath;

		private int awaitingResults;
		private bool quitting = false;

		/// <summary>Open is called when ever a channel is initialized</summary>
		public GLogChannelGURaaSLogger(string logFilePath, string cacheFilePath)
		{
			Application.wantsToQuit += ApplicationWantsToQuit;

			this.logFilePath = logFilePath;
			this.cacheFilePath = cacheFilePath;

			FileInfo logPath = new FileInfo(logFilePath);
			FileInfo cachePath = new FileInfo(cacheFilePath);

			// check if old cache or log files exists and upload any remaining data
			if (cachePath.Exists && cachePath.Length > 0)
			{
				RecoverCache();
			}
			else
			{
				cachedMessages = new List<string>(0);
			}

			if (logPath.Exists && logPath.Length > 0)
			{
				RecoverLog();
			}

			//  Ensure backup file directory exists
			logPath.Directory.Create();
			logLine.Length = 0;
		}

		/// <summary>Shutdown is called when ever a channel is closed</summary>
		public void Shutdown()
		{
			if (HasCache)
				WriteCacheToFile();
			if (logLine.Length > 0)
				WriteLogToFile();
		}
		
		public void Log(LogInfo log)
		{
			if (!freshMessage)
				logLine.Append(',');
			logLine.Append(log.GetJSON());
			freshMessage = false;
		}

		/// <summary>Starts a new game session. 
		/// <remarks>If a new game session is already active, it closes that session. See <see cref="CloseSession"/></remarks>
		/// </summary>
		public void StartSession(SessionInfo session)
		{
			activeSession = session;
			AddInitialInfoToLog();
		}

		/// <summary>Close the current game session, and pushes/flushed all the cached logs</summary>
		public void CloseSession(SessionInfo session)
		{
			if (activeSession == null) return;
			activeSession = session;

			AddClosingInfoToLog(true);
			completedMessage = true;
			Flush();
			activeSession = null;
		}

		/// <summary>Flush send session to remote server</summary>
		public void Flush()
		{
			if (activeSession == null || activeSession.gameId == null || activeSession.gameId.Equals(""))
			{
				return;
			}

			if (completedMessage)
			{
				// Session ended correctly just submit and clear the file

				Upload(activeSession.gameId, logLine.ToString());
				// Clear the log line
				logLine.Length = 0;
			}
			else
			{
				// Regular Submit (session is still open)
				AddClosingInfoToLog(false);

				Upload(activeSession.gameId, logLine.ToString());
				// Clear the log line
				logLine.Length = 0;

				// add the initial session information
				AddInitialInfoToLog();
			}
		}

		void AddInitialInfoToLog()
		{
			string logString = activeSession.GetMessageStartJSON();

			logLine.Length = 0;
			logLine.Append(logString);
			freshMessage = true;
			completedMessage = false;
		}

		void AddClosingInfoToLog(bool addSessionEndLog)
		{
			if (addSessionEndLog && !freshMessage)
				logLine.Append(',');
			logLine.Append(activeSession.GetMessageEndJSON(addSessionEndLog));
		}

		private void RecoverLog()
		{
			StreamReader reader = new StreamReader(logFilePath);

			System.DateTime now = System.DateTime.Now;
			string logString = reader.ReadToEnd();
			int index = logString.IndexOf('|');
			string gameId = logString.Substring(0, index);
			logString = logString.Substring(index + 1);

			//Add closing section to log
			if (logString.EndsWith("}"))
			{
				logString += $",{{\"time\":\"{now.ToString("yyyy-MM-dd HH:mm:ss")}\",\"tag1\":\"{GLog.VerboseLevelString(VerboseLevel.EVENT)}\",\"tag2\":\"GLogChannelGURaaS\",\"tag3\":\"Remote\",\"tag4\":\"Data Recovery\",\"data\":\"Data from a previous session was recovered and submitted.\"}}],\"end\":\"{now.ToString("yyyy-MM-dd HH:mm:ss")}\"}}";
			}
			else
			{
				logString += $"{{\"time\":\"{now.ToString("yyyy-MM-dd HH:mm:ss")}\",\"tag1\":\"{GLog.VerboseLevelString(VerboseLevel.EVENT)}\",\"tag2\":\"GLogChannelGURaaS\",\"tag3\":\"Remote\",\"tag4\":\"Data Recovery\",\"data\":\"Data from a previous session was recovered and submitted.\"}}],\"end\":\"{now.ToString("yyyy-MM-dd HH:mm:ss")}\"}}";
			}

			Upload(gameId, logString);
			reader.Close();

			//Reopen to clear
			StreamWriter writer = new StreamWriter(logFilePath, false);
			writer.Close();
		}

		private void RecoverCache()
		{
			StreamReader reader = new StreamReader(cacheFilePath);
			cachedMessages = new List<string>(reader.ReadToEnd().Split('~'));
			reader.Close();

			//Reopen to clear
			StreamWriter writer = new StreamWriter(cacheFilePath);
			writer.Close();

			UploadCache();
		}

		void WriteCacheToFile()
		{
			StreamWriter writer = new StreamWriter(cacheFilePath, false);
			writer.Write(string.Join("~", cachedMessages));
			writer.Close();
		}

		void WriteLogToFile()
		{
			StreamWriter writer = new StreamWriter(logFilePath, false);
			writer.Write(activeSession.gameId + "|" + logLine.ToString());
			writer.Close();
		}

		protected void Upload(string gameId, string jsonString)
		{
			string url = GRG_URL + "/v1/games/" + gameId + "/data";
			awaitingResults++;
			GLog.Instance.Monitor.DoPOSTRequest(url, jsonString, UploadSuccessful, UploadFailed);
		}

		void UploadSuccessful(string response)
		{
			Debug.Log("Uploading data successful: " + response);
			awaitingResults--;
			if (quitting && awaitingResults == 0)
				Application.Quit();
			else if (HasCache)
				UploadCache();
		}

		void UploadFailed(string error, bool isNetworkError, string url, string content)
		{
			Debug.LogError($"Uploading data failed. \n URL: {url}\n Error: {error}\n Content: {content}");
			awaitingResults--;
			cachedMessages.Add(content);
			if (quitting && awaitingResults == 0)
				Application.Quit();
		}

		void UploadCache()
		{
			foreach (string cachedMessage in cachedMessages)
				Upload(activeSession.gameId, cachedMessage);
			cachedMessages = new List<string>(0);
		}

		bool ApplicationWantsToQuit()
		{
			quitting = true;
			if (awaitingResults > 0)
			{
				Debug.Log("Waiting for submission results. Player quit delayed.");
				return false;
			}

			return true;
		}
	}
}
