using System.Text.Json;

namespace ClaudeDo.Worker.Usage.TokenTracker;

/// <summary>
/// Parses <c>tokentracker sessions --format json</c>. The export is not a promised API, so every
/// field is read defensively: a missing number becomes 0, an unreadable row is skipped, and
/// unparseable output returns null rather than throwing into the caller's face.
/// </summary>
public static class TokenTrackerExportParser
{
    /// <summary>The <c>version</c> value this parser was written against (CLI v0.88.4).</summary>
    public const int SupportedFormatVersion = 11;

    public static TokenTrackerExport? Parse(string? json, DateTime fetchedAtUtc)
    {
        if (string.IsNullOrWhiteSpace(json)) return null;

        JsonDocument doc;
        try { doc = JsonDocument.Parse(json); }
        catch (JsonException) { return null; }

        using (doc)
        {
            var root = doc.RootElement;
            if (root.ValueKind != JsonValueKind.Object) return null;
            if (!root.TryGetProperty("sessions", out var sessions) ||
                sessions.ValueKind != JsonValueKind.Array) return null;

            var parsed = new List<TokenTrackerSession>();
            var version = 0;

            foreach (var row in sessions.EnumerateArray())
            {
                if (row.ValueKind != JsonValueKind.Object) continue;

                version = Math.Max(version, (int)GetLong(row, "version"));

                var hash = GetString(row, "session_hash");
                if (string.IsNullOrWhiteSpace(hash)) continue;

                var source = GetString(row, "source") ?? "";
                if (!string.Equals(source, "claude", StringComparison.OrdinalIgnoreCase)) continue;

                if (!DateTimeOffset.TryParse(GetString(row, "started_at"), out var startedAt)) continue;

                var tokens = row.TryGetProperty("tokens", out var t) && t.ValueKind == JsonValueKind.Object
                    ? t
                    : default;

                parsed.Add(new TokenTrackerSession(
                    SessionHash: hash!,
                    Source: source,
                    Model: GetString(row, "model") ?? "",
                    StartedAt: startedAt,
                    InputTokens: GetLong(tokens, "input_tokens"),
                    OutputTokens: GetLong(tokens, "output_tokens"),
                    CacheReadTokens: GetLong(tokens, "cached_input_tokens"),
                    CacheCreationTokens: GetLong(tokens, "cache_creation_input_tokens"),
                    CostUsd: GetDouble(row, "cost_usd"),
                    Turns: (int)GetLong(row, "turns"),
                    RetryTurns: (int)GetLong(row, "retry_turns"),
                    Productive: GetBool(row, "productive"),
                    OneShot: GetBool(row, "one_shot")));
            }

            return new TokenTrackerExport(version, parsed, fetchedAtUtc);
        }
    }

    private static string? GetString(JsonElement obj, string prop) =>
        obj.ValueKind == JsonValueKind.Object &&
        obj.TryGetProperty(prop, out var el) &&
        el.ValueKind == JsonValueKind.String
            ? el.GetString()
            : null;

    private static long GetLong(JsonElement obj, string prop) =>
        obj.ValueKind == JsonValueKind.Object &&
        obj.TryGetProperty(prop, out var el) &&
        el.ValueKind == JsonValueKind.Number &&
        el.TryGetInt64(out var v)
            ? v
            : 0;

    private static double GetDouble(JsonElement obj, string prop) =>
        obj.ValueKind == JsonValueKind.Object &&
        obj.TryGetProperty(prop, out var el) &&
        el.ValueKind == JsonValueKind.Number &&
        el.TryGetDouble(out var v)
            ? v
            : 0;

    private static bool GetBool(JsonElement obj, string prop) =>
        obj.ValueKind == JsonValueKind.Object &&
        obj.TryGetProperty(prop, out var el) &&
        el.ValueKind == JsonValueKind.True;
}
