using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using EonaCat.LogStack.Templates;

namespace EonaCat.LogStack.Output.Layouts;

// 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>
/// Represents a layout token similar to NLog format
/// </summary>
public abstract class LayoutToken
{
    public abstract void Render(StringBuilder sb, object logEvent, Dictionary<string, object?> properties);
}

/// <summary>
/// Literal layout token
/// </summary>
public sealed class LiteralLayoutToken : LayoutToken
{
    private readonly string _text;

    public LiteralLayoutToken(string text)
    {
        _text = text;
    }

    public override void Render(StringBuilder sb, object logEvent, Dictionary<string, object?> properties)
    {
        sb.Append(_text);
    }
}

/// <summary>
/// DateTime layout token ${date:format=yyyy-MM-dd HH:mm:ss}
/// </summary>
public sealed class DateTimeLayoutToken : LayoutToken
{
    private readonly string _format;

    public DateTimeLayoutToken(string format = "yyyy-MM-dd HH:mm:ss")
    {
        _format = format ?? "yyyy-MM-dd HH:mm:ss";
    }

    public override void Render(StringBuilder sb, object logEvent, Dictionary<string, object?> properties)
    {
        sb.Append(DateTime.Now.ToString(_format, CultureInfo.InvariantCulture));
    }
}

/// <summary>
/// Log level layout token ${level}
/// </summary>
public sealed class LevelLayoutToken : LayoutToken
{
    private readonly bool _uppercase;

    public LevelLayoutToken(bool uppercase = true)
    {
        _uppercase = uppercase;
    }

    public override void Render(StringBuilder sb, object logEvent, Dictionary<string, object?> properties)
    {
        string level = "INFO";
        sb.Append(_uppercase ? level.ToUpper() : level);
    }
}

/// <summary>
/// Logger name layout token ${logger}
/// </summary>
public sealed class LoggerNameLayoutToken : LayoutToken
{
    public override void Render(StringBuilder sb, object logEvent, Dictionary<string, object?> properties)
    {
        sb.Append("Default");
    }
}

/// <summary>
/// Message layout token ${message}
/// </summary>
public sealed class MessageLayoutToken : LayoutToken
{
    public override void Render(StringBuilder sb, object logEvent, Dictionary<string, object?> properties)
    {
        sb.Append("");  // Message would go here when LogEvent is in scope
    }
}

/// <summary>
/// Exception layout token ${exception}
/// </summary>
public sealed class ExceptionLayoutToken : LayoutToken
{
    private readonly string _format;

    public ExceptionLayoutToken(string format = "full")
    {
        _format = format ?? "full";
    }

    public override void Render(StringBuilder sb, object logEvent, Dictionary<string, object?> properties)
    {
        // Exception rendering would happen when LogEvent has Exception property
    }
}

/// <summary>
/// Property layout token ${property:name=PropertyName}
/// </summary>
public sealed class PropertyLayoutToken : LayoutToken
{
    private readonly string _propertyName;

    public PropertyLayoutToken(string propertyName)
    {
        _propertyName = propertyName;
    }

    public override void Render(StringBuilder sb, object logEvent, Dictionary<string, object?> properties)
    {
        if (properties.TryGetValue(_propertyName, out var value))
        {
            sb.Append(value);
        }
    }
}

/// <summary>
/// Newline layout token ${newline}
/// </summary>
public sealed class NewlineLayoutToken : LayoutToken
{
    public override void Render(StringBuilder sb, object logEvent, Dictionary<string, object?> properties)
    {
        sb.AppendLine();
    }
}

/// <summary>
/// Parses NLog-like layout strings: ${date:format=...}, ${level}, ${message}, etc.
/// </summary>
public sealed class LayoutParser
{
    private readonly string _layout;
    private int _position;

    public LayoutParser(string layout)
    {
        _layout = layout ?? "";
    }

    public List<LayoutToken> Parse()
    {
        var tokens = new List<LayoutToken>();
        var sb = new StringBuilder();

        while (_position < _layout.Length)
        {
            if (_layout[_position] == '$' && _position + 1 < _layout.Length && _layout[_position + 1] == '{')
            {
                // Flush literal text
                if (sb.Length > 0)
                {
                    tokens.Add(new LiteralLayoutToken(sb.ToString()));
                    sb.Clear();
                }

                // Parse token
                var token = ParseToken();
                if (token != null)
                {
                    tokens.Add(token);
                }
            }
            else if (_layout[_position] == '\\' && _position + 1 < _layout.Length && _layout[_position + 1] == 'n')
            {
                sb.Append('\n');
                _position += 2;
            }
            else
            {
                sb.Append(_layout[_position]);
                _position++;
            }
        }

        if (sb.Length > 0)
        {
            tokens.Add(new LiteralLayoutToken(sb.ToString()));
        }

        return tokens;
    }

    private LayoutToken? ParseToken()
    {
        _position += 2; // Skip ${

        var nameSb = new StringBuilder();
        var argsSb = new StringBuilder();
        bool inArgs = false;

        while (_position < _layout.Length)
        {
            char c = _layout[_position];

            if (c == '}')
            {
                _position++;
                break;
            }
            else if (c == ':' && !inArgs)
            {
                inArgs = true;
                _position++;
            }
            else if (inArgs)
            {
                argsSb.Append(c);
                _position++;
            }
            else
            {
                nameSb.Append(c);
                _position++;
            }
        }

        string tokenName = nameSb.ToString().Trim().ToLower();
        string args = argsSb.ToString().Trim();

        return tokenName switch
        {
            "date" => new DateTimeLayoutToken(ParseFormatArg(args)),
            "time" => new DateTimeLayoutToken("HH:mm:ss"),
            "level" => new LevelLayoutToken(ParseBoolArg(args, "uppercase", true)),
            "logger" => new LoggerNameLayoutToken(),
            "message" => new MessageLayoutToken(),
            "exception" => new ExceptionLayoutToken(ParseFormatArg(args)),
            "property" or "prop" => new PropertyLayoutToken(ParseNameArg(args)),
            "newline" => new NewlineLayoutToken(),
            _ => null
        };
    }

    private string ParseFormatArg(string args)
    {
        if (args.StartsWith("format="))
        {
            return args.Substring(7);
        }

        return args;
    }

    private string ParseNameArg(string args)
    {
        if (args.StartsWith("name="))
        {
            return args.Substring(5);
        }

        if (args.StartsWith("key="))
        {
            return args.Substring(4);
        }

        return args;
    }

    private bool ParseBoolArg(string args, string name, bool defaultValue)
    {
        string prefix = name + "=";
        if (args.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
        {
            string value = args.Substring(prefix.Length);
            return value.Equals("true", StringComparison.OrdinalIgnoreCase);
        }
        return defaultValue;
    }
}

/// <summary>
/// Renders a parsed layout with LogEvent data
/// </summary>
public sealed class LayoutRenderer
{
    private readonly List<LayoutToken> _tokens;
    private readonly string _originalLayout;

    public LayoutRenderer(string layout)
    {
        _originalLayout = layout;
        var parser = new LayoutParser(layout);
        _tokens = parser.Parse();
    }

    public string Render(object logEvent, Dictionary<string, object?>? properties = null)
    {
        var sb = new StringBuilder();
        properties ??= new Dictionary<string, object?>();

        foreach (var token in _tokens)
        {
            token.Render(sb, logEvent, properties);
        }

        return sb.ToString();
    }

    public void Render(StringBuilder sb, object logEvent, Dictionary<string, object?>? properties = null)
    {
        properties ??= new Dictionary<string, object?>();
        foreach (var token in _tokens)
        {
            token.Render(sb, logEvent, properties);
        }
    }

    public string OriginalLayout => _originalLayout;
}

/// <summary>
/// Predefined layout templates
/// </summary>
public static class LayoutTemplates
{
    public const string Simple = "[${level}] ${message}";
    public const string Standard = "[${date:format=yyyy-MM-dd HH:mm:ss}] [${level}] ${logger} - ${message}";
    public const string Extended = "[${date:format=yyyy-MM-dd HH:mm:ss.fff}] [${level}] [${logger}] ${message}${exception}";
    public const string Json = "{\"timestamp\":\"${date:format=o}\",\"level\":\"${level}\",\"logger\":\"${logger}\",\"message\":\"${message}\"}";
    public const string Csv = "${date:format=yyyy-MM-dd HH:mm:ss},${level},${logger},${message}";

    /// <summary>
    /// Gets a layout by common name
    /// </summary>
    public static string GetTemplate(string name)
    {
        return name?.ToLower() switch
        {
            "simple" => Simple,
            "standard" => Standard,
            "extended" => Extended,
            "json" => Json,
            "csv" => Csv,
            _ => Standard
        };
    }
}
