using System.Text;
using Microsoft.ApplicationInsights.DataContracts;

namespace PlanTempus.Core.SeqLogging
{
    public class SeqLogger<T>
    {
        private readonly SeqHttpClient _httpClient;
        private readonly SeqConfiguration _configuration;

        public SeqLogger(SeqHttpClient httpClient, SeqConfiguration configuration)
        {
            _httpClient = httpClient;
            _configuration = configuration;
        }

        public async Task LogAsync(TraceTelemetry trace, CancellationToken cancellationToken = default)
        {
            var seqEvent = new Dictionary<string, object>
            {
                { "@t", trace.Timestamp.UtcDateTime.ToString("o") },
                { "@mt", trace.Message },
                { "@l", MapSeverityToLevel(trace.SeverityLevel) },
                { "Environment", _configuration.Environment },
            };

            foreach (var prop in trace.Properties)
                seqEvent.Add($"prop_{prop.Key}", prop.Value);

            foreach (var prop in trace.Context.GlobalProperties)
                seqEvent.Add($"global_{prop.Key}", prop.Value);

            await SendToSeqAsync(seqEvent, cancellationToken);
        }

        public async Task LogAsync(EventTelemetry evt, CancellationToken cancellationToken = default)
        {
            var seqEvent = new Dictionary<string, object>
            {
                { "@t", evt.Timestamp.UtcDateTime.ToString("o") },
                { "@mt", evt.Name },
                { "@l", "Information" },
                { "Environment",  _configuration.Environment }
            };

            foreach (var prop in evt.Properties)
                seqEvent.Add($"prop_{prop.Key}", prop.Value);

            foreach (var prop in evt.Context.GlobalProperties)
                seqEvent.Add($"global_{prop.Key}", prop.Value);

            foreach (var metric in evt.Metrics)
                seqEvent.Add($"metric_{metric.Key}", metric.Value);

            await SendToSeqAsync(seqEvent, cancellationToken);
        }

        public async Task LogAsync(ExceptionTelemetry ex, CancellationToken cancellationToken = default)
        {
            var seqEvent = new Dictionary<string, object>
            {
                { "@t", ex.Timestamp.UtcDateTime.ToString("o") },
                { "@mt", ex.Exception.Message },
                { "@l", "Error" },
                { "@x", FormatExceptionForSeq(ex.Exception) },
                { "Environment",  _configuration.Environment },
                { "ExceptionType", ex.Exception.GetType().Name }
            };

            foreach (var prop in ex.Properties)
                seqEvent.Add($"prop_{prop.Key}", prop.Value);

            foreach (var prop in ex.Context.GlobalProperties)
                seqEvent.Add($"global_{prop.Key}", prop.Value);

            await SendToSeqAsync(seqEvent, cancellationToken);
        }

        public async Task LogAsync(DependencyTelemetry dep, CancellationToken cancellationToken = default)
        {
            var seqEvent = new Dictionary<string, object>
            {
                { "@t", dep.Timestamp.UtcDateTime.ToString("o") },
                { "@mt", $"Dependency: {dep.Name}" },
                { "@l", dep.Success ?? true ? "Information" : "Error" },
                { "Environment",  _configuration.Environment },
                { "DependencyType", dep.Type },
                { "Target", dep.Target },
                { "Duration", dep.Duration.TotalMilliseconds }
            };

            foreach (var prop in dep.Properties)
                seqEvent.Add($"prop_{prop.Key}", prop.Value);

            foreach (var prop in dep.Context.GlobalProperties)
                seqEvent.Add($"global_{prop.Key}", prop.Value);

            await SendToSeqAsync(seqEvent, cancellationToken);
        }

        public async Task LogAsync(RequestTelemetry req, CancellationToken cancellationToken = default)
        {
            await Task.CompletedTask;
            throw new NotImplementedException();
        }

        public async Task LogAsync(
            Microsoft.ApplicationInsights.Extensibility.IOperationHolder<RequestTelemetry> operationHolder,
            CancellationToken cancellationToken = default)
        {
            var req = operationHolder.Telemetry;

            //https://docs.datalust.co/v2025.1/docs/posting-raw-events
            var seqEvent = new Dictionary<string, object>
            {
                { "@t", req.Timestamp.UtcDateTime.ToString("o") },
                { "@mt", req.Name },
                { "@l", req.Success ?? true ? "Information" : "Error" },
                { "@sp", req.Id }, //Span id	Unique identifier of a span	Yes, if the event is a span
                {
                    "@tr", req.Context.Operation.Id
                }, //Trace id	An identifier that groups all spans and logs that are in the same trace	Yes, if the event is a span
                {
                    "@sk", "Server"
                }, //Span kind	Describes the relationship of the span to others in the trace: Client, Server, Internal, Producer, or Consumer
                {
                    "@st", req.Timestamp.UtcDateTime.Subtract(req.Duration).ToString("o")
                }, //Start	The start ISO 8601 timestamp of this span	Yes, if the event is a span
                { "SourceContext", typeof(T).FullName },
                { "Url", req.Url },
                { "RequestId", req.Id },
                { "ItemTypeFlag", req.ItemTypeFlag.ToString() }
            };


            if (!string.IsNullOrEmpty(req.ResponseCode))
            {
                if (int.TryParse(req.ResponseCode, out int statusCode))
                {
                    if (Enum.IsDefined(typeof(System.Net.HttpStatusCode), statusCode))
                        seqEvent["StatusCode"] = $"{statusCode} {(System.Net.HttpStatusCode)statusCode}";
                    else
                        seqEvent["StatusCode"] = $"{statusCode} Unknown";
                }
            }

            if (!string.IsNullOrEmpty(req.Context.Operation.ParentId))
                seqEvent["@ps"] = req.Context.Operation.ParentId;

            if (req.Properties.TryGetValue("httpMethod", out string method))
            {
                seqEvent["RequestMethod"] = method;
                seqEvent["@mt"] = $"{req.Properties["httpMethod"]} {req.Name}";
                req.Properties.Remove("httpMethod");
            }

            foreach (var prop in req.Properties)
                seqEvent.Add($"prop_{prop.Key}", prop.Value);

            foreach (var prop in req.Context.GlobalProperties)
                seqEvent.Add($"{prop.Key}", prop.Value);

            await SendToSeqAsync(seqEvent, cancellationToken);
        }

        private async Task SendToSeqAsync(Dictionary<string, object> seqEvent, CancellationToken cancellationToken)
        {
            var content = new StringContent(
                Newtonsoft.Json.JsonConvert.SerializeObject(seqEvent),
                Encoding.UTF8,
                "application/vnd.serilog.clef");

            var requestMessage = new HttpRequestMessage(HttpMethod.Post, "/ingest/clef")
            {
                Content = content
            };

            var result = await _httpClient.SendAsync(requestMessage, cancellationToken);

            result.EnsureSuccessStatusCode();
        }

        private static string MapSeverityToLevel(SeverityLevel? severity)
        {
            return severity switch
            {
                SeverityLevel.Verbose => "Verbose",
                SeverityLevel.Information => "Information",
                SeverityLevel.Warning => "Warning",
                SeverityLevel.Error => "Error",
                SeverityLevel.Critical => "Fatal",
                _ => "Information"
            };
        }

        private static string FormatExceptionForSeq(Exception ex)
        {
            var sb = new StringBuilder();
            var exceptionCount = 0;

            void FormatSingleException(Exception currentEx, int depth)
            {
                if (depth > 0) sb.AppendLine("\n--- Inner Exception ---");

                sb.AppendLine($"Exception Type: {currentEx.GetType().FullName}");
                sb.AppendLine($"Message: {currentEx.Message}");
                sb.AppendLine($"Source: {currentEx.Source}");
                sb.AppendLine($"HResult: 0x{currentEx.HResult:X8}");
                sb.AppendLine("Stack Trace:");
                sb.AppendLine(currentEx.StackTrace?.Trim());

                if (currentEx.Data.Count > 0)
                {
                    sb.AppendLine("Additional Data:");
                    foreach (var key in currentEx.Data.Keys)
                    {
                        sb.AppendLine($"  {key}: {currentEx.Data[key]}");
                    }
                }
            }

            void RecurseExceptions(Exception currentEx, int depth = 0)
            {
                if (currentEx is AggregateException aggEx)
                {
                    foreach (var inner in aggEx.InnerExceptions)
                    {
                        RecurseExceptions(inner, depth);
                        depth++;
                    }
                }
                else if (currentEx.InnerException != null)
                {
                    RecurseExceptions(currentEx.InnerException, depth + 1);
                }

                FormatSingleException(currentEx, depth);
                exceptionCount++;
            }

            RecurseExceptions(ex);
            sb.Insert(0, $"EXCEPTION CHAIN ({exceptionCount} exceptions):\n");
            return sb.ToString();
        }
    }
}