import Foundation
import Testing

@testable import NucleicProtocol

/// `CodexUsage` — parsing the `codex` `account/rateLimits/updated` snapshot, the stale-window
/// correction, window labels, and Codable round-tripping (it rides both `AgentEvent` and
/// `DashboardSnapshot`).
@Suite struct CodexUsageTests {
    private let now = Date(timeIntervalSince1970: 1_700_000_000)

    @Test func parsesCamelCaseAppServerShape() throws {
        let params: JSONValue = ["rateLimits": [
            "primary": ["usedPercent": 42.5, "windowMinutes": 300, "resetsInSeconds": 3600],
            "secondary": ["usedPercent": 12, "windowMinutes": 10080]]]
        let usage = try #require(CodexUsage.fromRateLimitsNotification(params, now: now))
        #expect(usage.primary?.utilization == 42.5)
        #expect(usage.primary?.windowMinutes == 300)
        #expect(usage.primary?.resetsAt == now.addingTimeInterval(3600))
        #expect(usage.secondary?.utilization == 12)
        #expect(usage.secondary?.resetsAt == nil)
        #expect(usage.peakUtilization == 42.5)
        #expect(usage.hasData)
    }

    @Test func parsesSnakeCaseCoreShape() throws {
        // The exec/core wire uses snake_case and may nest under `rate_limits`.
        let params: JSONValue = ["rate_limits": [
            "primary": ["used_percent": 5, "window_minutes": 300, "resets_in_seconds": 60]]]
        let usage = try #require(CodexUsage.fromRateLimitsNotification(params, now: now))
        #expect(usage.primary?.utilization == 5)
        #expect(usage.primary?.resetsAt == now.addingTimeInterval(60))
        #expect(usage.secondary == nil)
    }

    @Test func clampsUtilizationAndDropsEmptySnapshots() {
        let over: JSONValue = ["rateLimits": ["primary": ["usedPercent": 140]]]
        #expect(CodexUsage.fromRateLimitsNotification(over, now: now)?.primary?.utilization == 100)
        // No usable window ⇒ nil, so a malformed push never displaces a good snapshot.
        #expect(CodexUsage.fromRateLimitsNotification(["rateLimits": [:]], now: now) == nil)
        #expect(CodexUsage.fromRateLimitsNotification(["rateLimits": ["primary": ["windowMinutes": 300]]], now: now) == nil)
    }

    @Test func lapsedWindowReadsAsEmpty() {
        let window = CodexUsageWindow(utilization: 80, windowMinutes: 300, resetsAt: now.addingTimeInterval(60))
        #expect(window.utilization(at: now) == 80)                       // before reset: as-is
        #expect(window.utilization(at: now.addingTimeInterval(120)) == 0)  // past reset: freed
        // A window with no reset is always reported as-is.
        let noReset = CodexUsageWindow(utilization: 30, windowMinutes: nil, resetsAt: nil)
        #expect(noReset.utilization(at: now.addingTimeInterval(1_000_000)) == 30)
    }

    @Test func windowLabelsFromLength() {
        #expect(CodexUsageWindow(utilization: 0, windowMinutes: 300, resetsAt: nil).label == "5-hour")
        #expect(CodexUsageWindow(utilization: 0, windowMinutes: 10080, resetsAt: nil).label == "Weekly")
        #expect(CodexUsageWindow(utilization: 0, windowMinutes: nil, resetsAt: nil).label == nil)
    }

    @Test func codableRoundTrips() throws {
        let usage = CodexUsage(
            primary: CodexUsageWindow(utilization: 42.5, windowMinutes: 300, resetsAt: now),
            secondary: CodexUsageWindow(utilization: 12, windowMinutes: 10080, resetsAt: nil))
        let data = try JSONEncoder().encode(usage)
        #expect(try JSONDecoder().decode(CodexUsage.self, from: data) == usage)
    }
}
