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

namespace EonaCat.LogStack.Output.Formatters;

// 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>
/// Base interface for log formatters
/// </summary>
public interface ILogFormatter
{
    string Format(object logEvent, Dictionary<string, object?> properties);
    void Format(StringBuilder sb, object logEvent, Dictionary<string, object?> properties);
}

/// <summary>
/// JSON formatter for structured logging
/// </summary>
public sealed class JsonFormatter : ILogFormatter
{
    private readonly bool _includeProperties;
    private readonly bool _indented;

    public JsonFormatter(bool includeProperties = true, bool indented = false)
    {
        _includeProperties = includeProperties;
        _indented = indented;
    }

    public string Format(object logEvent, Dictionary<string, object?> properties)
    {
        var sb = new StringBuilder();
        Format(sb, logEvent, properties);
        return sb.ToString();
    }

    public void Format(StringBuilder sb, object logEvent, Dictionary<string, object?> properties)
    {
        sb.Append("{");
        sb.Append("\"timestamp\":\"").Append(DateTime.Now.ToString("o")).Append("\",");
        sb.Append("\"level\":\"INFO\",");
        sb.Append("\"logger\":\"Default\",");
        sb.Append("\"message\":\"\"");

        // Properties
        if (_includeProperties && properties.Count > 0)
        {
            sb.Append(",\"properties\":{");
            int count = 0;
            foreach (var kvp in properties)
            {
                if (count > 0)
                {
                    sb.Append(",");
                }

                sb.Append("\"").AppendEscapedJson(kvp.Key).Append("\":");
                FormatPropertyValue(sb, kvp.Value);
                count++;
            }
            sb.Append("}");
        }

        sb.Append("}");
    }

    private void FormatPropertyValue(StringBuilder sb, object? value)
    {
        if (value == null)
        {
            sb.Append("null");
        }
        else if (value is string str)
        {
            sb.Append("\"").AppendEscapedJson(str).Append("\"");
        }
        else if (value is bool b)
        {
            sb.Append(b ? "true" : "false");
        }
        else if (value is int or long or double or decimal)
        {
            sb.Append(value);
        }
        else
        {
            sb.Append("\"").AppendEscapedJson(value.ToString() ?? "").Append("\"");
        }
    }
}

/// <summary>
/// CSV formatter for data export
/// </summary>
public sealed class CsvFormatter : ILogFormatter
{
    private readonly string[] _propertyNames;
    private readonly bool _includeHeader;
    private bool _headerWritten = false;

    public CsvFormatter(string[]? propertyNames = null, bool includeHeader = true)
    {
        _propertyNames = propertyNames ?? Array.Empty<string>();
        _includeHeader = includeHeader;
    }

    public string Format(object logEvent, Dictionary<string, object?> properties)
    {
        var sb = new StringBuilder();
        Format(sb, logEvent, properties);
        return sb.ToString();
    }

    public void Format(StringBuilder sb, object logEvent, Dictionary<string, object?> properties)
    {
        if (_includeHeader && !_headerWritten)
        {
            WriteCsvHeader(sb);
            _headerWritten = true;
        }

        WriteCsvLine(sb, properties);
    }

    private void WriteCsvHeader(StringBuilder sb)
    {
        var headers = new List<string> { "Timestamp", "Level", "Logger", "Message" };
        headers.AddRange(_propertyNames);

        for (int i = 0; i < headers.Count; i++)
        {
            if (i > 0)
            {
                sb.Append(",");
            }

            sb.Append(EscapeCsv(headers[i]));
        }

        sb.AppendLine();
    }

    private void WriteCsvLine(StringBuilder sb, Dictionary<string, object?> properties)
    {
        var values = new List<string>
        {
            DateTime.Now.ToString(CultureInfo.InvariantCulture),
            "INFO",
            "Default",
            ""
        };

        foreach (var propName in _propertyNames)
        {
            if (properties.TryGetValue(propName, out var value))
            {
                values.Add(value?.ToString() ?? "");
            }
            else
            {
                values.Add("");
            }
        }

        for (int i = 0; i < values.Count; i++)
        {
            if (i > 0)
            {
                sb.Append(",");
            }

            sb.Append(EscapeCsv(values[i]));
        }

        sb.AppendLine();
    }

    private string EscapeCsv(string value)
    {
        if (string.IsNullOrEmpty(value))
        {
            return "";
        }

        if (value.Contains(",") || value.Contains("\"") || value.Contains("\n"))
        {
            return "\"" + value.Replace("\"", "\"\"") + "\"";
        }

        return value;
    }
}

/// <summary>
/// Compact formatter for console output
/// </summary>
public sealed class CompactFormatter : ILogFormatter
{
    public string Format(object logEvent, Dictionary<string, object?> properties)
    {
        var sb = new StringBuilder();
        Format(sb, logEvent, properties);
        return sb.ToString();
    }

    public void Format(StringBuilder sb, object logEvent, Dictionary<string, object?> properties)
    {
        sb.AppendFormat("[{0:HH:mm:ss}] [{1,7}] {2}: {3}",
            DateTime.Now,
            "INFO",
            "Default",
            "");
    }
}

/// <summary>
/// Verbose formatter with all details
/// </summary>
public sealed class VerboseFormatter : ILogFormatter
{
    public string Format(object logEvent, Dictionary<string, object?> properties)
    {
        var sb = new StringBuilder();
        Format(sb, logEvent, properties);
        return sb.ToString();
    }

    public void Format(StringBuilder sb, object logEvent, Dictionary<string, object?> properties)
    {
        sb.AppendLine("===== LOG EVENT =====");
        sb.AppendFormat("Timestamp: {0:O}", DateTime.Now).AppendLine();
        sb.AppendFormat("Level: INFO").AppendLine();
        sb.AppendFormat("Logger: Default").AppendLine();
        sb.AppendFormat("Message: ").AppendLine();

        if (properties.Count > 0)
        {
            sb.AppendLine("Properties:");
            foreach (var kvp in properties)
            {
                sb.AppendFormat("  {0}: {1}", kvp.Key, kvp.Value).AppendLine();
            }
        }

        sb.AppendLine("====================");
    }
}

/// <summary>
/// Factory for creating formatters
/// </summary>
public static class FormatterFactory
{
    public static ILogFormatter CreateJson(bool includeProperties = true, bool indented = false)
        => new JsonFormatter(includeProperties, indented);

    public static ILogFormatter CreateCsv(string[]? propertyNames = null, bool includeHeader = true)
        => new CsvFormatter(propertyNames, includeHeader);

    public static ILogFormatter CreateCompact()
        => new CompactFormatter();

    public static ILogFormatter CreateVerbose()
        => new VerboseFormatter();
}

/// <summary>
/// Extension methods for StringBuilder to support JSON escaping
/// </summary>
public static class StringBuilderExtensions
{
    public static StringBuilder AppendEscapedJson(this StringBuilder sb, string value)
    {
        if (string.IsNullOrEmpty(value))
        {
            return sb;
        }

        foreach (char c in value)
        {
            switch (c)
            {
                case '"':
                    sb.Append("\\\"");
                    break;
                case '\\':
                    sb.Append("\\\\");
                    break;
                case '\b':
                    sb.Append("\\b");
                    break;
                case '\f':
                    sb.Append("\\f");
                    break;
                case '\n':
                    sb.Append("\\n");
                    break;
                case '\r':
                    sb.Append("\\r");
                    break;
                case '\t':
                    sb.Append("\\t");
                    break;
                default:
                    if (c < 32)
                    {
                        sb.AppendFormat("\\u{0:X4}", (int)c);
                    }
                    else
                    {
                        sb.Append(c);
                    }

                    break;
            }
        }

        return sb;
    }
}
