using System;
using System.Collections.Generic;
using System.Diagnostics;

namespace EonaCat.LogStack.Tracing
{
    /// <summary>
    /// Enhanced span representation for distributed tracing.
    /// Tracks operation timing, status, and linked operations.
    /// </summary>
    public sealed class TracingSpan : IDisposable
    {
        private readonly Stopwatch _stopwatch = Stopwatch.StartNew();
        private readonly List<Exception> _exceptions = new();
        private bool _disposed;

        /// <summary>
        /// Gets the span name
        /// </summary>
        public string Name { get; }

        /// <summary>
        /// Gets the span kind (internal, server, client, producer, consumer)
        /// </summary>
        public string Kind { get; set; } = "internal";

        /// <summary>
        /// Gets the trace context
        /// </summary>
        public TraceContext Context { get; }

        /// <summary>
        /// Gets or sets the span status
        /// </summary>
        public SpanStatus Status { get; set; } = SpanStatus.Unset;

        /// <summary>
        /// Gets or sets the status description
        /// </summary>
        public string? StatusDescription { get; set; }

        /// <summary>
        /// Gets the start time
        /// </summary>
        public DateTime StartTime { get; }

        /// <summary>
        /// Gets the end time (if ended)
        /// </summary>
        public DateTime? EndTime { get; private set; }

        /// <summary>
        /// Gets the duration in milliseconds
        /// </summary>
        public double DurationMs { get; private set; }

        /// <summary>
        /// Gets attributes attached to span
        /// </summary>
        public Dictionary<string, object> Attributes { get; } = new();

        /// <summary>
        /// Gets events recorded on span
        /// </summary>
        public List<SpanEvent> Events { get; } = new();

        /// <summary>
        /// Gets linked spans
        /// </summary>
        public List<SpanLink> Links { get; } = new();

        /// <summary>
        /// Gets recorded exceptions
        /// </summary>
        public IReadOnlyList<Exception> Exceptions => _exceptions.AsReadOnly();

        /// <summary>
        /// Creates a new tracing span
        /// </summary>
        public TracingSpan(string name, TraceContext? context = null)
        {
            Name = name ?? throw new ArgumentNullException(nameof(name));
            Context = context ?? TraceContextManager.Current;
            StartTime = DateTime.UtcNow;
        }

        /// <summary>
        /// Records an event on the span
        /// </summary>
        public void AddEvent(string eventName, Dictionary<string, object>? attributes = null)
        {
            Events.Add(new SpanEvent
            {
                Name = eventName,
                Timestamp = DateTime.UtcNow,
                Attributes = attributes ?? new Dictionary<string, object>()
            });
        }

        /// <summary>
        /// Records an exception on the span
        /// </summary>
        public void RecordException(Exception exception, Dictionary<string, object>? attributes = null)
        {
            if (exception == null) return;

            _exceptions.Add(exception);
            Status = SpanStatus.Error;
            StatusDescription = exception.Message;

            var eventAttrs = attributes ?? new Dictionary<string, object>();
            eventAttrs["exception.type"] = exception.GetType().FullName;
            eventAttrs["exception.message"] = exception.Message;
            eventAttrs["exception.stacktrace"] = exception.StackTrace ?? "";

            AddEvent("exception", eventAttrs);
        }

        /// <summary>
        /// Sets a span attribute
        /// </summary>
        public void SetAttribute(string key, object value)
        {
            Attributes[key] = value;
        }

        /// <summary>
        /// Sets multiple attributes
        /// </summary>
        public void SetAttributes(Dictionary<string, object> attributes)
        {
            if (attributes == null) return;
            foreach (var kvp in attributes)
            {
                Attributes[kvp.Key] = kvp.Value;
            }
        }

        /// <summary>
        /// Adds a linked span
        /// </summary>
        public void AddLink(string linkedTraceId, string linkedSpanId, Dictionary<string, object>? attributes = null)
        {
            Links.Add(new SpanLink
            {
                TraceId = linkedTraceId,
                SpanId = linkedSpanId,
                Attributes = attributes ?? new Dictionary<string, object>()
            });
        }

        /// <summary>
        /// Marks span as ended with success
        /// </summary>
        public void End(SpanStatus status = SpanStatus.Ok, string? description = null)
        {
            if (_disposed) return;

            _stopwatch.Stop();
            EndTime = DateTime.UtcNow;
            DurationMs = _stopwatch.Elapsed.TotalMilliseconds;
            Status = status;
            StatusDescription = description;
        }

        /// <summary>
        /// Gets span as W3C trace context header value
        /// </summary>
        public string ToW3CTraceContext()
        {
            return $"{Context.TraceId}-{Context.SpanId}-{(byte)Context.TraceFlags:x2}";
        }

        public void Dispose()
        {
            if (_disposed) return;
            _disposed = true;
            End();
        }
    }

    /// <summary>
    /// Span status enumeration
    /// </summary>
    public enum SpanStatus
    {
        Unset = 0,
        Ok = 1,
        Error = 2
    }

    /// <summary>
    /// Represents an event recorded on a span
    /// </summary>
    public sealed class SpanEvent
    {
        public string Name { get; set; }
        public DateTime Timestamp { get; set; }
        public Dictionary<string, object> Attributes { get; set; } = new();
    }

    /// <summary>
    /// Represents a link to another span
    /// </summary>
    public sealed class SpanLink
    {
        public string TraceId { get; set; }
        public string SpanId { get; set; }
        public Dictionary<string, object> Attributes { get; set; } = new();
    }
}
