#region Header

// Copyright (c) 2021-2022 AccelByte Inc. All Rights Reserved.
// This is licensed software from AccelByte Inc, for limitations
// and restrictions contact your company contract manager.

#endregion

using System;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using UnityEngine.Windows;

namespace BlackBox
{
    public class LogWriter
    {
        public const string DATE_FORMAT = "yyyy-MM-dd_HHmmss";
        public const string MANAGED_STACKTRACE_FILE = "managed.stacktrace";
        
        public string Output = string.Empty;
        public string Stack = string.Empty;

        private string logText_;
        private bool shouldCauseErrorSession_ = false;
        private bool hasWrittenOutLog_ = false;

        private string managedStacktracePath_;
        private Dictionary<string, bool> stacktraceMap_;

        public void Start()
        {
#if UNITY_ANDROID
            var userPath = Application.persistentDataPath;
#elif UNITY_IOS            
            var userPath = Application.persistentDataPath;
#else
            var userPath           = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
#endif
            var unityFolder        = Path.Combine(userPath, ".blackbox", "unity");
            managedStacktracePath_ = Path.Combine(unityFolder, MANAGED_STACKTRACE_FILE);
            stacktraceMap_         = new Dictionary<string, bool>();

            if(!System.IO.Directory.Exists(unityFolder))
            {
                System.IO.Directory.CreateDirectory(unityFolder);
            }
            
            Application.logMessageReceived += HandleLog;

            WriteBlackBoxConfigValuesToLog();
        }

        public void End()
        {
            stacktraceMap_.Clear();
            stacktraceMap_ = null;
            
            Application.logMessageReceived -= HandleLog;

            SetGameHasCrashed();
        }

        private void HandleLog(string logString, string stackTrace, LogType type)
        {
            Output = logString;
            Stack = stackTrace;

            var seriousIssue = type == LogType.Exception || type == LogType.Error || type == LogType.Assert;

            if (seriousIssue)
            {
                shouldCauseErrorSession_ =  true;
                logText_ += "\n" + Output;
                logText_ += "\n========== MANAGED STACK TRACE ==================\n";
                logText_ += Stack;
                logText_ += "\n========== END OF STACKTRACE ===========\n";
#if UNITY_ANDROID && !UNITY_EDITOR
				if(type != LogType.Error)
				{
					Debug.Log("Processing stack trace from logger");
					AndroidHandler.process_crash_stack_trace(stackTrace, type);
				}
#endif
            }
            else
            {
                logText_ += "\n" + Output;
            }
            
            // Test write stacktrace
            WriteManagedStacktrace(type, stackTrace);
        }

        private static void WriteBlackBoxConfigValuesToLog()
        {
#if UNITY_EDITOR
            var iniPath = PluginManager.GetConfigPath(); 
            var reader = new StreamReader(iniPath);
            var blackBoxIniContents = reader.ReadToEnd();

            reader.Close();
            
            Logger.Log($"\n<---BLACKBOX CONFIG START---> \n${blackBoxIniContents} \n<---BLACKBOX CONFIG END--->\n");
#endif
        }

        public void SetGameHasCrashed()
        {
            if (!hasWrittenOutLog_ && shouldCauseErrorSession_)
            {
                var crashDirectory = CreateAndGetCrashDirectory();
                Logger.Log($"[BlackBoxManaged] CrashDirectory:{crashDirectory}");
                
                PluginAPI.blackbox_set_game_has_crashed();

                hasWrittenOutLog_ = true;
            }
        }

        public void WriteManagedStacktrace(LogType type, string stacktrace)
        {
            var seriousIssue = type == LogType.Exception || type == LogType.Error || type == LogType.Assert;
            if (!seriousIssue) return;
            
            // TODO: Filter duplicate exceptions
            if (stacktraceMap_.ContainsKey(stacktrace)) return;

            var writer = new StreamWriter(managedStacktracePath_, true);
            writer.WriteLine(stacktrace);
            writer.Close();
            
            // Save the stacktrace to the map to skip repetition
            stacktraceMap_.Add(stacktrace, true);
            
            Logger.Log(stacktrace);
        }

        private string CreateAndGetCrashDirectory()
        {
#if UNITY_ANDROID || UNITY_IOS
            var date = System.DateTime.Now.ToString(DATE_FORMAT);
            var crashDirectory = $"{Application.persistentDataPath}/Crash_{date}";
#else
            // Get the folder path where crash reports are stored
            string crashReportPath = UnityEngine.Windows.CrashReporting.crashReportFolder;            
            // Log the path to the console
            Debug.Log("Crash reports are stored in: " + crashReportPath);
            var directories = System.IO.Directory.GetDirectories(crashReportPath);

            if (directories.Length > 0)
            {
                Debug.Log("CreateAndGetCrashDirectory in: " + directories[directories.Length - 1]);
                return directories[directories.Length - 1];
            }

            var date = System.DateTime.Now.ToString(DATE_FORMAT);
            var crashDirectory = $"{CrashReporting.crashReportFolder}/Crash_{date}";
#endif

            if(!System.IO.Directory.Exists(crashDirectory))
            {
                System.IO.Directory.CreateDirectory(crashDirectory);
            }

            Debug.Log("CreateAndGetCrashDirectory in: " + crashDirectory);

            return crashDirectory;
        }

#if UNITY_EDITOR
        [ContextMenu("Print Directories")]
        private void PrintDirectories()
        {
            var crashDirectory = CreateAndGetCrashDirectory();
            Logger.Log($"{crashDirectory}");
        }
#endif
    }
}
