import CodexBarCore
import Foundation
import Testing

struct ProviderPaceCapabilityTests {
    private static let weeklyWindowMinutes = 7 * 24 * 60
    private static let monthlyWindowSentinelMinutes = 30 * 24 * 60

    @Test
    func `descriptor pace capabilities match the supported provider mapping`() {
        let now = Date(timeIntervalSince1970: 1_750_000_000)
        let fixtures: [RateWindow] = [
            Self.window(minutes: nil, resetsAt: nil),
            Self.window(minutes: nil, resetsAt: now.addingTimeInterval(4 * 24 * 60 * 60)),
            Self.window(minutes: nil, resetsAt: now.addingTimeInterval(8 * 24 * 60 * 60)),
            Self.window(minutes: nil, resetsAt: now.addingTimeInterval(30 * 24 * 60 * 60)),
            Self.window(minutes: 60, resetsAt: now.addingTimeInterval(30 * 60)),
            Self.window(minutes: Self.weeklyWindowMinutes, resetsAt: nil),
            Self.window(minutes: Self.weeklyWindowMinutes, resetsAt: now.addingTimeInterval(4 * 24 * 60 * 60)),
            Self.window(minutes: Self.weeklyWindowMinutes, resetsAt: now.addingTimeInterval(8 * 24 * 60 * 60)),
            Self.window(minutes: Self.monthlyWindowSentinelMinutes, resetsAt: nil),
            Self.window(
                minutes: Self.monthlyWindowSentinelMinutes,
                resetsAt: now.addingTimeInterval(20 * 24 * 60 * 60)),
            Self.window(minutes: 0, resetsAt: now.addingTimeInterval(60)),
            Self.window(minutes: Self.weeklyWindowMinutes, resetsAt: now.addingTimeInterval(-60)),
        ]

        for provider in UsageProvider.allCases {
            let capability = ProviderDescriptorRegistry.descriptor(for: provider).pace
            for window in fixtures {
                let actualResetWindowPace = capability.supportsResetWindowPace(window: window, now: now)
                let expectedResetWindowPace = Self.expectedSupportsResetWindowPace(
                    provider: provider,
                    window: window,
                    now: now)
                #expect(
                    actualResetWindowPace == expectedResetWindowPace,
                    "Reset-window pace changed for \(provider.rawValue), window=\(String(describing: window)).")

                let actualMonthlyInference = capability.usesInferredMonthlyDuration(window: window)
                let legacyMonthlyInference = Self.legacyUsesInferredMonthlyDuration(
                    provider: provider,
                    window: window)
                #expect(
                    actualMonthlyInference == legacyMonthlyInference,
                    "Monthly inference changed for \(provider.rawValue), window=\(String(describing: window)).")
            }
        }
    }

    @Test
    func `amp monthly pace is limited to subscription windows`() {
        let now = Date(timeIntervalSince1970: 1_750_000_000)
        let capability = AmpProviderDescriptor.descriptor.pace
        let freeTier = Self.window(
            minutes: 24 * 60,
            resetsAt: now.addingTimeInterval(12 * 60 * 60))
        let subscription = Self.window(
            minutes: Self.monthlyWindowSentinelMinutes,
            resetsAt: now.addingTimeInterval(20 * 24 * 60 * 60))

        #expect(!capability.supportsResetWindowPace(window: freeTier, now: now))
        #expect(!capability.usesInferredMonthlyDuration(window: freeTier))
        #expect(capability.supportsResetWindowPace(window: subscription, now: now))
        #expect(capability.usesInferredMonthlyDuration(window: subscription))
    }

    @Test
    func `calendar month pace resolves the real cycle duration`() throws {
        var calendar = Calendar(identifier: .gregorian)
        calendar.timeZone = try #require(TimeZone(secondsFromGMT: 0))
        let resetsAt = try #require(calendar.date(from: DateComponents(
            calendar: calendar,
            timeZone: calendar.timeZone,
            year: 2026,
            month: 3,
            day: 1)))
        let window = Self.window(
            minutes: Self.monthlyWindowSentinelMinutes,
            resetsAt: resetsAt)

        let resolved = ProviderPaceCapability.calendarMonthResetWindow.resolvedResetWindowForPace(window)

        #expect(resolved.windowMinutes == 28 * 24 * 60)
        #expect(resolved.resetsAt == resetsAt)
        #expect(resolved.usedPercent == window.usedPercent)
    }

    private static func window(minutes: Int?, resetsAt: Date?) -> RateWindow {
        RateWindow(
            usedPercent: 50,
            windowMinutes: minutes,
            resetsAt: resetsAt,
            resetDescription: nil)
    }

    /// Expected provider-specific reset-window behavior, including newly declared capabilities.
    private static func expectedSupportsResetWindowPace(
        provider: UsageProvider,
        window: RateWindow,
        now: Date) -> Bool
    {
        switch provider {
        case .copilot:
            return window.resetsAt != nil
        case .cursor:
            return window.windowMinutes != nil
        case .grok:
            guard GrokProviderDescriptor.primaryLabel(window: window, now: now) == "Weekly",
                  let resetsAt = window.resetsAt
            else { return false }
            let windowMinutes = window.windowMinutes ?? self.weeklyWindowMinutes
            let timeUntilReset = resetsAt.timeIntervalSince(now)
            return windowMinutes > 0
                && timeUntilReset > 0
                && timeUntilReset <= TimeInterval(windowMinutes) * 60
        case .kimi:
            return window.windowMinutes == self.weeklyWindowMinutes
        case .alibaba, .alibabatokenplan, .amp, .commandcode, .doubao, .mimo, .notion, .opencodego, .stepfun:
            return window.windowMinutes == self.monthlyWindowSentinelMinutes
        default:
            return false
        }
    }

    private static func legacyUsesInferredMonthlyDuration(
        provider: UsageProvider,
        window: RateWindow) -> Bool
    {
        switch provider {
        case .copilot:
            window.windowMinutes == nil
        case .alibaba, .alibabatokenplan, .amp, .commandcode, .doubao, .mimo, .notion, .opencodego, .stepfun:
            window.windowMinutes == self.monthlyWindowSentinelMinutes
        default:
            false
        }
    }
}
