#pragma warning disable CA2211

using System.Runtime.CompilerServices;

namespace Voile.Utils
{
    public class Logger
    {
        public static Action<string, LogLevel>? OnLog;
        public static string LogPath { get; set; } = "Logs/";

        /// <summary>
        /// Maximum amount of log files in a log folder. If it reaches the limit, all logs will be written to <c>voile-latest.log</c> instead of creating a new one.
        /// </summary>
        public static int MaxLogFiles { get; set; } = 5;

        /// <summary>
        /// Specifies the logging level. In release builds, the log level is <c>Error</c>. In debug, the log level is <c>Echo</c>.
        /// </summary>
        public static LogLevel LogLevel = LogLevel.Error;
        /// <summary>
        /// Specifies if the logger should write to file.
        /// </summary>
        public static bool WriteToFile = true;

        public Logger(string className)
        {
            _className = className;

            if (WriteToFile && !_logCreated)
            {
                var dirInfo = Directory.CreateDirectory(LogPath);
                var files = dirInfo.GetFiles();

                string logName = $"voile-{DateFormat}-{TimeFormat}.log".Replace(':', '.');

                if (files.Length >= MaxLogFiles)
                {
                    logName = "voile-latest.log";
                }

                var path = Path.Combine(LogPath, logName);

                if (File.Exists(path))
                {
                    File.Delete(path);
                }

                _fileStream = File.Create(path);
                _fileWriter = new StreamWriter(_fileStream);
                _logCreated = true;
            }

#if DEBUG
            LogLevel = LogLevel.Echo;
#endif
        }

        public void Echo(string what)
        {
            string message = string.Format(EchoFormat, TimeFormat, what);

            LogFile(message);
            LogConsole(what, LogLevel.Echo);
            OnLog?.Invoke(what, LogLevel.Echo);
        }

        public void Info(string what, [CallerMemberName] string method = "")
        {
            LogLevel logType = LogLevel.Info;
            string message = string.Format(LogFormat, TimeFormat, logType.ToString(), _className, method, what);

            LogFile(message);
            LogConsole(message, logType);
            OnLog?.Invoke(message, logType);
        }

        public void Warn(string what, [CallerMemberName] string method = "")
        {
            LogLevel logType = LogLevel.Warn;
            string message = string.Format(LogFormat, TimeFormat, logType.ToString(), _className, method, what);

            LogFile(message);
            LogConsole(message, logType);
            OnLog?.Invoke(message, logType);
        }

        public void Error(string what, [CallerMemberName] string method = "")
        {
            LogLevel logType = LogLevel.Error;
            string message = string.Format(LogFormat, TimeFormat, logType.ToString(), _className, method, what);

            LogFile(message);
            LogConsole(message, logType);
            OnLog?.Invoke(message, logType);
        }


        private static string TimeFormat => $"{DateTime.Now:HH:mm:ffff}";
        private static string DateFormat => $"{DateTime.Now:d:M:yyyy:}";
        private static string LogFormat => "({0}) [{1}/{2}/{3}] {4}";
        private static string EchoFormat => "({0}) {1}";

        private static void LogConsole(string what, LogLevel logType)
        {
            if (LogLevel < logType) return;
            Console.ForegroundColor = ConsoleColorForLog(logType);
            Console.WriteLine(what);
            Console.ForegroundColor = ConsoleColor.White;
        }

        private static ConsoleColor ConsoleColorForLog(LogLevel logType)
        {
            ConsoleColor color = ConsoleColor.White;

            switch (logType)
            {
                case LogLevel.Info:
                    color = ConsoleColor.Cyan;
                    break;

                case LogLevel.Warn:
                    color = ConsoleColor.Yellow;
                    break;
                case LogLevel.Error:
                    color = ConsoleColor.Red;
                    break;
            }

            return color;
        }

        private void LogFile(string message)
        {
            if (!WriteToFile || _fileWriter is null) return;
            _fileWriter.WriteLine(message);
            _fileWriter.Flush();
        }

        private readonly string _className;
        private static bool _logCreated;
        private static FileStream? _fileStream;
        private static StreamWriter? _fileWriter;
    }

    public enum LogLevel
    {
        Error,
        Warn,
        Info,
        Echo,
    }
}