using System.Text.Json;
using ClaudeDo.Worker.Report.Interfaces;

namespace ClaudeDo.Worker.Report;

public sealed class ClaudeHistoryReader : IClaudeHistoryReader
{
    private readonly string _projectsRoot;

    public ClaudeHistoryReader(string projectsRoot) => _projectsRoot = projectsRoot;

    public Task<IReadOnlyList<RepoActivity>> ReadAsync(
        DateOnly start, DateOnly end, IReadOnlyList<string> excludedPrefixes, CancellationToken ct = default)
    {
        var buckets = new Dictionary<(string Repo, DateOnly Date), DayActivity>();
        var normalizedExcludes = excludedPrefixes
            .Select(NormalizePath).Where(p => p.Length > 0).ToArray();

        if (Directory.Exists(_projectsRoot))
        {
            foreach (var file in Directory.EnumerateFiles(_projectsRoot, "*.jsonl", SearchOption.AllDirectories))
            {
                ct.ThrowIfCancellationRequested();
                ReadFile(file, start, end, normalizedExcludes, buckets);
            }
        }

        var repos = buckets
            .GroupBy(kv => kv.Key.Repo)
            .Select(g =>
            {
                var ra = new RepoActivity { RepoPath = g.Key };
                foreach (var day in g.OrderBy(kv => kv.Key.Date).Select(kv => kv.Value))
                    ra.Days.Add(day);
                return ra;
            })
            .OrderBy(r => r.RepoPath)
            .ToList();

        return Task.FromResult<IReadOnlyList<RepoActivity>>(repos);
    }

    private static void ReadFile(
        string file, DateOnly start, DateOnly end, string[] excludes,
        Dictionary<(string, DateOnly), DayActivity> buckets)
    {
        string? lastAssistantText = null;
        string? lastAssistantRepo = null;
        DateOnly lastAssistantDate = default;

        foreach (var line in File.ReadLines(file))
        {
            if (string.IsNullOrWhiteSpace(line)) continue;
            JsonDocument doc;
            try { doc = JsonDocument.Parse(line); }
            catch (JsonException) { continue; }
            using (doc)
            {
                var root = doc.RootElement;
                if (root.ValueKind != JsonValueKind.Object) continue;
                if (!root.TryGetProperty("type", out var typeEl)) continue;
                var type = typeEl.GetString();
                if (type is not ("user" or "assistant")) continue;
                if (!root.TryGetProperty("cwd", out var cwdEl) || cwdEl.ValueKind != JsonValueKind.String) continue;
                var cwd = cwdEl.GetString()!;
                if (IsExcluded(cwd, excludes)) continue;
                if (!root.TryGetProperty("timestamp", out var tsEl) ||
                    !DateTimeOffset.TryParse(tsEl.GetString(), out var ts)) continue;
                var date = DateOnly.FromDateTime(ts.LocalDateTime);
                if (date < start || date > end) continue;

                var text = ExtractText(root);
                if (string.IsNullOrWhiteSpace(text)) continue;

                if (type == "user")
                {
                    if (text.Contains("<system-reminder>", StringComparison.OrdinalIgnoreCase)) continue;
                    Bucket(buckets, cwd, date).Prompts.Add(text.Trim());
                }
                else
                {
                    // Keep only the closing summary per (repo, day). If this turn moved to a
                    // different repo/day (e.g. the session cd'd), flush the previous one first.
                    if (lastAssistantText is not null &&
                        (cwd != lastAssistantRepo || date != lastAssistantDate))
                    {
                        Bucket(buckets, lastAssistantRepo!, lastAssistantDate).Summaries.Add(lastAssistantText);
                        lastAssistantText = null;
                    }
                    lastAssistantText = text.Trim();
                    lastAssistantRepo = cwd;
                    lastAssistantDate = date;
                }
            }
        }

        if (lastAssistantText is not null && lastAssistantRepo is not null)
            Bucket(buckets, lastAssistantRepo, lastAssistantDate).Summaries.Add(lastAssistantText);
    }

    private static DayActivity Bucket(
        Dictionary<(string, DateOnly), DayActivity> buckets, string repo, DateOnly date)
    {
        var key = (repo, date);
        if (!buckets.TryGetValue(key, out var day))
        {
            day = new DayActivity { Date = date };
            buckets[key] = day;
        }
        return day;
    }

    private static string ExtractText(JsonElement root)
    {
        if (!root.TryGetProperty("message", out var msg) ||
            !msg.TryGetProperty("content", out var content)) return "";
        if (content.ValueKind == JsonValueKind.String) return content.GetString() ?? "";
        if (content.ValueKind != JsonValueKind.Array) return "";

        var parts = new List<string>();
        foreach (var item in content.EnumerateArray())
        {
            if (item.ValueKind != JsonValueKind.Object) continue;
            if (item.TryGetProperty("type", out var t) && t.GetString() == "text" &&
                item.TryGetProperty("text", out var txt) && txt.ValueKind == JsonValueKind.String)
                parts.Add(txt.GetString() ?? "");
        }
        return string.Join("\n", parts);
    }

    private static bool IsExcluded(string cwd, string[] excludes)
    {
        var norm = NormalizePath(cwd);
        return excludes.Any(p => norm.StartsWith(p, StringComparison.Ordinal));
    }

    private static string NormalizePath(string p) =>
        (p ?? "").Replace('/', '\\').TrimEnd('\\').ToLowerInvariant();
}
