import Foundation
import Testing
@testable import CodexBarCore

struct CodexCLIWindowNormalizationTests {
    @Test
    func `normalizer maps lone weekly window into secondary`() {
        let weekly = RateWindow(
            usedPercent: 5,
            windowMinutes: 10080,
            resetsAt: nil,
            resetDescription: nil)

        let normalized = CodexRateWindowNormalizer._normalizeForTesting(primary: weekly, secondary: nil)
        #expect(normalized.primary == nil)
        #expect(normalized.secondary?.usedPercent == 5)
        #expect(normalized.secondary?.windowMinutes == 10080)
    }

    @Test
    func `normalizer keeps lone session window in primary`() {
        let session = RateWindow(
            usedPercent: 31,
            windowMinutes: 300,
            resetsAt: nil,
            resetDescription: nil)

        let normalized = CodexRateWindowNormalizer._normalizeForTesting(primary: session, secondary: nil)
        #expect(normalized.primary?.usedPercent == 31)
        #expect(normalized.primary?.windowMinutes == 300)
        #expect(normalized.secondary == nil)
    }

    @Test
    func `normalizer keeps session and weekly ordering unchanged`() {
        let session = RateWindow(
            usedPercent: 31,
            windowMinutes: 300,
            resetsAt: nil,
            resetDescription: nil)
        let weekly = RateWindow(
            usedPercent: 26,
            windowMinutes: 10080,
            resetsAt: nil,
            resetDescription: nil)

        let normalized = CodexRateWindowNormalizer._normalizeForTesting(primary: session, secondary: weekly)
        #expect(normalized.primary?.usedPercent == 31)
        #expect(normalized.primary?.windowMinutes == 300)
        #expect(normalized.secondary?.usedPercent == 26)
        #expect(normalized.secondary?.windowMinutes == 10080)
    }

    @Test
    func `normalizer swaps reversed weekly and unknown windows`() {
        let weekly = RateWindow(
            usedPercent: 43,
            windowMinutes: 10080,
            resetsAt: nil,
            resetDescription: nil)
        let unknown = RateWindow(
            usedPercent: 17,
            windowMinutes: 540,
            resetsAt: nil,
            resetDescription: nil)

        let normalized = CodexRateWindowNormalizer._normalizeForTesting(primary: weekly, secondary: unknown)
        #expect(normalized.primary?.usedPercent == 17)
        #expect(normalized.primary?.windowMinutes == 540)
        #expect(normalized.secondary?.usedPercent == 43)
        #expect(normalized.secondary?.windowMinutes == 10080)
    }

    @Test
    func `maps weekly only RPC limits into secondary`() throws {
        let snapshot = try UsageFetcher._mapCodexRPCLimitsForTesting(
            primary: (usedPercent: 5, windowMinutes: 10080, resetsAt: nil),
            secondary: nil)

        #expect(snapshot.primary == nil)
        #expect(snapshot.secondary?.usedPercent == 5)
        #expect(snapshot.secondary?.windowMinutes == 10080)
    }

    @Test
    func `maps session only RPC limits into primary`() throws {
        let snapshot = try UsageFetcher._mapCodexRPCLimitsForTesting(
            primary: (usedPercent: 31, windowMinutes: 300, resetsAt: nil),
            secondary: nil)

        #expect(snapshot.primary?.usedPercent == 31)
        #expect(snapshot.primary?.windowMinutes == 300)
        #expect(snapshot.secondary == nil)
    }

    @Test
    func `maps reversed weekly and unknown RPC limits`() throws {
        let snapshot = try UsageFetcher._mapCodexRPCLimitsForTesting(
            primary: (usedPercent: 43, windowMinutes: 10080, resetsAt: nil),
            secondary: (usedPercent: 17, windowMinutes: 540, resetsAt: nil))

        #expect(snapshot.primary?.usedPercent == 17)
        #expect(snapshot.primary?.windowMinutes == 540)
        #expect(snapshot.secondary?.usedPercent == 43)
        #expect(snapshot.secondary?.windowMinutes == 10080)
    }

    @Test
    func `throws when RPC limits contain no windows`() {
        #expect(throws: UsageError.noRateLimitsFound) {
            try UsageFetcher._mapCodexRPCLimitsForTesting(primary: nil, secondary: nil)
        }
    }

    @Test
    func `maps plan only RPC limits into empty identified snapshot`() throws {
        let snapshot = try UsageFetcher._mapCodexRPCLimitsForTesting(
            primary: nil,
            secondary: nil,
            planType: "pro")

        #expect(snapshot.primary == nil)
        #expect(snapshot.secondary == nil)
        #expect(snapshot.loginMethod(for: .codex) == "pro")
        #expect(snapshot.rateLimitsUnavailable(for: .codex))
    }

    @Test
    func `codex no rate limit error means limits unavailable without snapshot`() {
        let availability = UsageLimitsAvailability.resolve(
            provider: .codex,
            snapshot: nil,
            account: AccountInfo(email: "user@example.com", plan: nil),
            lastErrorDescription: UsageError.noRateLimitsFound.errorDescription)

        #expect(availability == .unavailable)
    }

    @Test
    func `codex no rate limit error stays available without account context`() {
        let availability = UsageLimitsAvailability.resolve(
            provider: .codex,
            snapshot: nil,
            account: AccountInfo(email: nil, plan: nil),
            lastErrorDescription: UsageError.noRateLimitsFound.errorDescription)

        #expect(availability == .available)
    }

    @Test
    func `codex windowed snapshot wins over stale no rate limit error`() {
        let identity = ProviderIdentitySnapshot(
            providerID: .codex,
            accountEmail: "user@example.com",
            accountOrganization: nil,
            loginMethod: "pro")
        let snapshot = UsageSnapshot(
            primary: RateWindow(
                usedPercent: 25,
                windowMinutes: 300,
                resetsAt: nil,
                resetDescription: nil),
            secondary: nil,
            updatedAt: Date(),
            identity: identity)
        let availability = UsageLimitsAvailability.resolve(
            provider: .codex,
            snapshot: snapshot,
            lastErrorDescription: UsageError.noRateLimitsFound.errorDescription)

        #expect(availability == .available)
    }

    @Test
    func `maps weekly only status snapshot into secondary`() throws {
        let status = CodexStatusSnapshot(
            credits: nil,
            fiveHourPercentLeft: nil,
            weeklyPercentLeft: 95,
            fiveHourResetDescription: nil,
            weeklyResetDescription: "resets next week",
            fiveHourResetsAt: nil,
            weeklyResetsAt: nil,
            rawText: "Weekly limit: 95% left")

        let snapshot = try UsageFetcher._mapCodexStatusForTesting(status)
        #expect(snapshot.primary == nil)
        #expect(snapshot.secondary?.usedPercent == 5)
        #expect(snapshot.secondary?.windowMinutes == 10080)
    }

    @Test
    func `maps five hour only status snapshot into primary`() throws {
        let status = CodexStatusSnapshot(
            credits: nil,
            fiveHourPercentLeft: 69,
            weeklyPercentLeft: nil,
            fiveHourResetDescription: "resets soon",
            weeklyResetDescription: nil,
            fiveHourResetsAt: nil,
            weeklyResetsAt: nil,
            rawText: "5h limit: 69% left")

        let snapshot = try UsageFetcher._mapCodexStatusForTesting(status)
        #expect(snapshot.primary?.usedPercent == 31)
        #expect(snapshot.primary?.windowMinutes == 300)
        #expect(snapshot.secondary == nil)
    }

    @Test
    func `throws when status snapshot contains no windows`() {
        let status = CodexStatusSnapshot(
            credits: nil,
            fiveHourPercentLeft: nil,
            weeklyPercentLeft: nil,
            fiveHourResetDescription: nil,
            weeklyResetDescription: nil,
            fiveHourResetsAt: nil,
            weeklyResetsAt: nil,
            rawText: "")

        #expect(throws: UsageError.noRateLimitsFound) {
            try UsageFetcher._mapCodexStatusForTesting(status)
        }
    }
}
