using EonaCat.LogStack.Core;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace EonaCat.LogStack.Flows
{
    // This file is part of the EonaCat project(s) which is released under the Apache License.
    // See the LICENSE file or go to https://EonaCat.com/License for full license details.

    /// <summary>
    /// console flow with color support and minimal allocations
    /// Uses a ColorSchema for configurable colors
    /// </summary>
    public sealed class ConsoleFlow : FlowBase
    {
        private readonly bool _useColors;
        private readonly TimestampMode _timestampMode;
        private readonly StringBuilder _buffer = new(1024);
        private readonly object _consoleLock = new();
        private readonly ColorSchema _colors;

        private readonly string _template;
        private List<Action<LogEvent, StringBuilder>> _compiledTemplate;

        public ConsoleFlow(
            LogLevel minimumLevel = LogLevel.Trace,
            bool useColors = true,
            TimestampMode timestampMode = TimestampMode.Local,
            ColorSchema? colorSchema = null,
            string template = "[{ts}] [Host: {host}] [Category: {category}] [Thread: {thread}] [{logtype}] {message}{props}")
            : base("Console", minimumLevel)
        {
            _useColors = useColors;
            _timestampMode = timestampMode;
            _colors = colorSchema ?? new ColorSchema();
            _template = template ?? throw new ArgumentNullException(nameof(template));

            CompileTemplate(_template);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public override Task<WriteResult> BlastAsync(LogEvent logEvent, CancellationToken cancellationToken = default)
        {
            if (!IsEnabled || !IsLogLevelEnabled(logEvent))
            {
                return Task.FromResult(WriteResult.LevelFiltered);
            }

            WriteToConsole(logEvent);
            Interlocked.Increment(ref BlastedCount);
            return Task.FromResult(WriteResult.Success);
        }

        public override Task<WriteResult> BlastBatchAsync(ReadOnlyMemory<LogEvent> logEvents, CancellationToken cancellationToken = default)
        {
            if (!IsEnabled)
            {
                return Task.FromResult(WriteResult.FlowDisabled);
            }

            foreach (var logEvent in logEvents.Span)
            {
                if (logEvent.Level >= MinimumLevel)
                {
                    WriteToConsole(logEvent);
                    Interlocked.Increment(ref BlastedCount);
                }
            }

            return Task.FromResult(WriteResult.Success);
        }

        private void WriteToConsole(LogEvent logEvent)
        {
            lock (_consoleLock)
            {
                _buffer.Clear();

                foreach (var action in _compiledTemplate)
                {
                    action(logEvent, _buffer);
                }

                if (_useColors && TryGetColor(logEvent.Level, out var color))
                {
                    Console.ForegroundColor = color.Foreground;
                }

                Console.WriteLine(_buffer.ToString());

                if (logEvent.Exception != null)
                {
                    if (_useColors)
                    {
                        Console.ForegroundColor = ConsoleColor.DarkRed;
                    }

                    Console.WriteLine(logEvent.Exception.ToString());

                    if (_useColors)
                    {
                        Console.ResetColor();
                    }
                }

                if (_useColors)
                {
                    Console.ResetColor();
                }
            }
        }

        private void CompileTemplate(string template)
        {
            _compiledTemplate = new List<Action<LogEvent, StringBuilder>>();
            int pos = 0;

            while (pos < template.Length)
            {
                int open = template.IndexOf('{', pos);
                if (open < 0)
                {
                    string lit = template.Substring(pos);
                    _compiledTemplate.Add((_, sb) => sb.Append(lit));
                    break;
                }

                if (open > pos)
                {
                    string lit = template.Substring(pos, open - pos);
                    _compiledTemplate.Add((_, sb) => sb.Append(lit));
                }

                int close = template.IndexOf('}', open);
                if (close < 0)
                {
                    string lit = template.Substring(open);
                    _compiledTemplate.Add((_, sb) => sb.Append(lit));
                    break;
                }

                string token = template.Substring(open + 1, close - open - 1);
                _compiledTemplate.Add(ResolveToken(token));
                pos = close + 1;
            }
        }

        private Action<LogEvent, StringBuilder> ResolveToken(string token)
        {
            switch (token.ToLowerInvariant())
            {
                case "ts":
                    return (log, sb) =>
                        sb.Append(LogEvent.GetDateTime(log.Timestamp)
                            .ToString("yyyy-MM-dd HH:mm:ss.fff"));

                case "tz":
                    return (_, sb) =>
                        sb.Append(_timestampMode == TimestampMode.Local
                            ? TimeZoneInfo.Local.StandardName
                            : "UTC");

                case "host":
                    return (_, sb) => sb.Append(Environment.MachineName);

                case "category":
                    return (log, sb) =>
                    {
                        if (!string.IsNullOrEmpty(log.Category))
                        {
                            sb.Append(log.Category);
                        }
                    };

                case "thread":
                    return (_, sb) => sb.Append(Thread.CurrentThread.ManagedThreadId);

                case "pid":
                    return (_, sb) => sb.Append(Process.GetCurrentProcess().Id);

                case "message":
                    return (log, sb) => sb.Append(log.Message);

                case "props":
                    return AppendProperties;

                case "newline":
                    return (_, sb) => sb.AppendLine();

                case "logtype":
                    return (log, sb) =>
                    {
                        var levelText = GetLevelText(log.Level);

                        if (_useColors && TryGetColor(log.Level, out var color))
                        {
                            Console.ForegroundColor = color.Foreground;
                            Console.BackgroundColor = color.Background;

                            Console.Write(sb.ToString());
                            Console.Write(levelText);

                            Console.ResetColor();
                            sb.Clear();
                        }
                        else
                        {
                            sb.Append(levelText);
                        }
                    };

                default:
                    return (_, sb) => sb.Append('{').Append(token).Append('}');
            }
        }

        private void AppendProperties(LogEvent log, StringBuilder sb)
        {
            if (log.Properties.Count == 0)
            {
                return;
            }

            sb.Append(" {");

            bool first = true;
            foreach (var prop in log.Properties)
            {
                if (!first)
                {
                    sb.Append(", ");
                }

                sb.Append(prop.Key);
                sb.Append('=');
                sb.Append(prop.Value?.ToString() ?? "null");

                first = false;
            }

            sb.Append('}');
        }

        private bool TryGetColor(LogLevel level, out ColorScheme color)
        {
            color = level switch
            {
                LogLevel.Trace => _colors.Trace,
                LogLevel.Debug => _colors.Debug,
                LogLevel.Information => _colors.Info,
                LogLevel.Warning => _colors.Warning,
                LogLevel.Error => _colors.Error,
                LogLevel.Critical => _colors.Critical,
                _ => _colors.Info
            };
            return color != null;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private static string GetLevelText(LogLevel level)
        {
            return level switch
            {
                LogLevel.Trace => "TRACE",
                LogLevel.Debug => "DEBUG",
                LogLevel.Information => "INFO",
                LogLevel.Warning => "WARN",
                LogLevel.Error => "ERROR",
                LogLevel.Critical => "CRITICAL",
                _ => "???"
            };
        }

        public override Task FlushAsync(CancellationToken cancellationToken = default)
        {
            // Console auto-flushes
            return Task.CompletedTask;
        }
    }
}
