using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Text;
using H.Formatters;
using H.Pipes;
using InABox.Core;

namespace InABox.Logging
{
    public abstract class LoggerBase
    {
        public LogType[] LogTypes = { LogType.Information, LogType.Query, LogType.Update, LogType.Error, LogType.Important };

        protected abstract void DoSend(string message);

        public virtual void Send(LogType logType, string user, string message, Guid transaction)
        {
            if (!LogTypes.Any(x => x == logType))
                return;
            var type = logType switch
            {
                LogType.Information => "INFO",
                LogType.Query => "READ",
                LogType.Update => "UPDATE",
                LogType.Error => "ERROR",
                LogType.Important => "IMPTNT",
                _ => "ERROR"
            };
            var msg = string.Format("{0:HH:mm:ss.fff} {1} {2} {3} {4}",
                DateTime.Now,
                transaction,
                type?.PadRight(6),
                (user ?? "").PadRight(12),
                message
            );

            DoSend(msg);
        }

        public virtual void Stop() { }
    }

    public class EventLogger : LoggerBase
    {
        public Action<string>? OnLog;

        public EventLogger(Action<string>? onLog = null)
        {
            OnLog = onLog;
        }

        protected override void DoSend(string message)
        {
            OnLog?.Invoke(message);
        }
    }

    public class ConsoleLogger : LoggerBase
    {
        public ConsoleLogger()
        {
            LogTypes = new LogType[] { LogType.Information, LogType.Error };
        }

        protected override void DoSend(string message)
        {
            Console.WriteLine(message);
        }
    }

    public class LogFileLogger : LoggerBase
    {
        private static readonly object logfileLock = new();
        private string TmpFolder;

        private string _folder;
        public string Folder
        {
            get => _folder;
            [MemberNotNull(nameof(_folder), nameof(TmpFolder))]
            set
            {
                _folder = value;
                TmpFolder = Path.Combine(Folder, "Temp");
                if (Directory.Exists(TmpFolder))
                {
                    Directory.CreateDirectory(TmpFolder);
                    var files = Directory.GetFiles(TmpFolder);
                    foreach (var file in files)
                        File.Delete(file);
                }
            }
        }

        public LogFileLogger(string folder)
        {
            Folder = folder;
        }

        public override void Send(LogType logtype, string user, string message, Guid transaction)
        {
            if (logtype == LogType.Update)
                UpdateJournal(message);
            else
                base.Send(logtype, user, message, transaction);
        }

        protected override void DoSend(string message)
        {
            if (!string.IsNullOrEmpty(Folder))
            {
                UpdateLogFile(message);
            }
        }
        private void UpdateLogFile(string msg)
        {
            if (!Directory.Exists(Folder)) Directory.CreateDirectory(Folder);
            var filename = Path.Combine(Folder, string.Format("{0:yyyy-MM-dd}.log", DateTime.Today));
            try
            {
                lock (logfileLock)
                {
                    using (var sw = new StreamWriter(filename, true, Encoding.UTF8, 65536))
                    {
                        sw.WriteLine(msg);
                        sw.Close();
                    }
                }
            }
            catch (Exception e)
            {
                Console.WriteLine("*** Failed to Update Log: " + e.Message);
                Console.WriteLine("*** Message: " + msg);
            }
        }

        private void UpdateJournal(string sql)
        {
            var filename = Path.Combine(Folder, string.Format("{0:yyyy-MM-dd}.sql", DateTime.Today));
            try
            {
                lock (logfileLock)
                {
                    using (var sw = new StreamWriter(filename, true, Encoding.UTF8, 65536))
                    {
                        sw.WriteLine(sql);
                        sw.Close();
                    }
                }
            }
            catch (Exception e)
            {
                Console.WriteLine("*** Failed to Update SQL Journal: " + e.Message);
                Console.WriteLine("*** Message: " + sql);
            }
        }

    }

    public class NamedPipeLogger : LoggerBase
    {
        private PipeServer<string> _pipe;
        private string _name = "";

        public NamedPipeLogger(string name = "")
        {
            _name = string.IsNullOrWhiteSpace(name) ? Process.GetCurrentProcess().ProcessName : name;

            _pipe = new PipeServer<string>(_name, formatter:new BinaryFormatter());
            _pipe.ClientConnected += _pipe_ClientConnected;
            _pipe.StartAsync();
        }

        private void _pipe_ClientConnected(object? sender, H.Pipes.Args.ConnectionEventArgs<string> e)
        {
            _pipe.WriteAsync("Connected to " + _name);
        }
        public override void Stop()
        {
            _pipe.StopAsync().Wait();
        }

        protected override void DoSend(string message)
        {
            if (_pipe != null)
                _pipe.WriteAsync(message);
        }
    }

    public static class MainLogger
    {
        private static List<LoggerBase> Loggers = new();

        public static void AddLogger(LoggerBase logger)
        {
            Loggers.Add(logger);
        }
        public static void RemoveLogger(LoggerBase logger)
        {
            Loggers.Remove(logger);
        }

        public static void Send(LogType logType, string user, string message, Guid transaction)
        {
            foreach(var logger in Loggers)
            {
                try
                {
                    logger.Send(logType, user, message, transaction);
                }
                catch (Exception e)
                {
                    Logger.Send(LogType.Error, "LOGERROR",
                        $"Exception in Logger.Send ({e.Message}) Message=[{message}]");
                }
                
            }
        }

        public static void Stop()
        {
            foreach (var logger in Loggers)
            {
                logger.Stop();
            }
        }
    }
}