using System.IO;
using System.Runtime.CompilerServices;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace AxCopilot.Services.Agent;

public enum AgentEventLogType
{
    SessionStart, SessionEnd,
    UserMessage, AssistantMessage,
    ToolRequest, ToolResult,
    HookFired, HookResult,
    SkillActivated, SkillCompleted,
    CompactionTriggered, CompactionCompleted,
    SubagentSpawned, SubagentCompleted,
    ReflexionSaved,
    Error
}

public record AgentEventRecord
{
    [JsonPropertyName("seq")]
    public long SeqNo { get; init; }

    [JsonPropertyName("id")]
    public string EventId { get; init; } = Guid.NewGuid().ToString("N")[..12];

    [JsonPropertyName("parent_id")]
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
    public string? ParentEventId { get; init; }

    [JsonPropertyName("session_id")]
    public string SessionId { get; init; } = "";

    [JsonPropertyName("type")]
    public AgentEventLogType Type { get; init; }

    [JsonPropertyName("ts")]
    public DateTime Timestamp { get; init; } = DateTime.UtcNow;

    [JsonPropertyName("payload")]
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
    public string? Payload { get; init; }
}

/// <summary>
/// JSONL 기반 에이전트 이벤트 로그.
/// 저장: %APPDATA%\AxCopilot\sessions\{sessionId}\events.jsonl
/// </summary>
public class AgentEventLog : IDisposable
{
    private static readonly string BaseDir = Path.Combine(
        Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
        "AxCopilot", "sessions");

    private static readonly JsonSerializerOptions _json = new()
    {
        Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
    };

    private readonly string _filePath;
    private readonly string _sessionId;
    private long _seq;
    private readonly SemaphoreSlim _lock = new(1, 1);

    public AgentEventLog(string sessionId)
    {
        _sessionId = sessionId;
        var dir = Path.Combine(BaseDir, sessionId);
        Directory.CreateDirectory(dir);
        _filePath = Path.Combine(dir, "events.jsonl");
    }

    public async Task AppendAsync(AgentEventLogType type, string? payload = null, string? parentId = null)
    {
        var record = new AgentEventRecord
        {
            SeqNo = System.Threading.Interlocked.Increment(ref _seq),
            SessionId = _sessionId,
            Type = type,
            ParentEventId = parentId,
            Payload = payload
        };

        await _lock.WaitAsync();
        try
        {
            var line = JsonSerializer.Serialize(record, _json);
            await File.AppendAllTextAsync(_filePath, line + "\n");
        }
        finally { _lock.Release(); }
    }

    public async IAsyncEnumerable<AgentEventRecord> ReadAllAsync([EnumeratorCancellation] CancellationToken ct = default)
    {
        if (!File.Exists(_filePath)) yield break;
        var lines = await File.ReadAllLinesAsync(_filePath, ct);
        foreach (var line in lines)
        {
            if (string.IsNullOrWhiteSpace(line)) continue;
            AgentEventRecord? record = null;
            try { record = JsonSerializer.Deserialize<AgentEventRecord>(line, _json); }
            catch (Exception) { }
            if (record != null) yield return record;
        }
    }

    public async IAsyncEnumerable<AgentEventRecord> ReadFromAsync(long fromSeq, [EnumeratorCancellation] CancellationToken ct = default)
    {
        await foreach (var r in ReadAllAsync(ct))
            if (r.SeqNo >= fromSeq) yield return r;
    }

    public void Dispose() => _lock.Dispose();

    /// <summary>보관 기간 초과 세션 정리.</summary>
    public static void CleanOldSessions(int retentionDays)
    {
        if (!Directory.Exists(BaseDir)) return;
        var cutoff = DateTime.UtcNow.AddDays(-retentionDays);
        foreach (var dir in Directory.GetDirectories(BaseDir))
        {
            try
            {
                var info = new DirectoryInfo(dir);
                if (info.LastWriteTimeUtc < cutoff)
                    Directory.Delete(dir, true);
            }
            catch (Exception) { }
        }
    }
}
