import Foundation
import NucleicProtocol
import Testing

@testable import NucleicCore

/// Locks the Intelligence router's research-derived invariants: every purpose × level cell
/// resolves to a real, supported (model, effort) for every connected-provider combination;
/// cost never decreases as the slider rises; and the per-model rules the July-2026 research
/// established (Opus 5 never above `high`, Fable only at Deep/Max, Haiku only on the light
/// review/writing rows, `max` effort reserved for Fable's planning/debugging cells, `ultra`
/// only for a Pro account at Max on Sol) hold for the whole matrix — so a future matrix edit
/// can't silently reintroduce the misrouting failure modes the slider exists to prevent.
@Suite struct IntelligenceRoutingTests {
    private let allLevels = IntelligenceLevel.allCases
    private let allPurposes = PromptPurpose.allCases
    private let fallback = (model: "claude-opus-5", effort: "high")
    private let connectedCombos: [Set<BackendID>] = [
        [], [.claudeCode], [.codex], [.claudeCode, .codex],
    ]

    // MARK: Matrix shape

    @Test func everyCellCoversBothLanes() {
        for purpose in allPurposes {
            for level in allLevels {
                let cell = IntelligenceRouter.matrix[purpose]?[level]
                #expect(cell != nil, "missing cell \(purpose)/\(level)")
                let backends = Set((cell ?? []).compactMap { BackendID.forModel($0.sku) })
                #expect(
                    backends.isSuperset(of: [.claudeCode, .codex]),
                    "\(purpose)/\(level) must offer a Claude and a GPT lane")
            }
        }
    }

    @Test func everyCandidateIsACatalogModelWithASupportedEffort() {
        for (purpose, row) in IntelligenceRouter.matrix {
            for (level, cell) in row {
                for candidate in cell {
                    let supported = EffortLadder.efforts(for: candidate.sku, codexPro: true)
                    #expect(
                        supported.contains(candidate.effort),
                        "\(purpose)/\(level): \(candidate.sku) doesn't support \(candidate.effort)")
                }
            }
        }
    }

    // MARK: Resolution invariants

    @Test func everyRouteResolvesToASupportedPair() {
        for purpose in allPurposes {
            for level in allLevels {
                for connected in connectedCombos {
                    for codexPro in [false, true] {
                        let r = IntelligenceRouter.route(
                            purpose: purpose, level: level, connected: connected,
                            codexPro: codexPro, fallback: fallback)
                        #expect(!r.model.isEmpty)
                        #expect(
                            EffortLadder.efforts(for: r.model, codexPro: codexPro)
                                .contains(r.effort),
                            "\(purpose)/\(level)/\(connected): \(r.model) · \(r.effort)")
                        if connected.isEmpty {
                            #expect(r.model == fallback.model, "nothing connected → default")
                        } else {
                            let backend = BackendID.forModel(r.model)
                            #expect(backend != nil && connected.contains(backend!))
                        }
                    }
                }
            }
        }
    }

    /// The slider's promise: moving right never gets *cheaper*. Checked per lane, since a
    /// single-provider user only ever sees one lane.
    @Test func costTierIsMonotonicInLevel() {
        for purpose in allPurposes {
            for lane in [Set<BackendID>([.claudeCode]), Set<BackendID>([.codex])] {
                var previous = -1
                for level in allLevels {
                    let r = IntelligenceRouter.route(
                        purpose: purpose, level: level, connected: lane, fallback: fallback)
                    let tier = IntelligenceRouter.costTier(of: r.model)
                    #expect(
                        tier >= previous,
                        "\(purpose) \(lane): tier dropped \(previous)→\(tier) at \(level)")
                    previous = tier
                }
            }
        }
    }

    // MARK: Research-derived per-model rules

    @Test func opusNeverExceedsHighEffort() {
        for (purpose, row) in IntelligenceRouter.matrix {
            for (level, cell) in row {
                for candidate in cell where candidate.sku.hasPrefix("claude-opus") {
                    #expect(
                        ["low", "medium", "high"].contains(candidate.effort),
                        "\(purpose)/\(level): Opus past its measured peak (\(candidate.effort))")
                }
            }
        }
    }

    @Test func fableOnlyAtDeepAndMax() {
        for (purpose, row) in IntelligenceRouter.matrix {
            for (level, cell) in row {
                for candidate in cell where candidate.sku.hasPrefix("claude-fable") {
                    #expect(
                        level >= .deep,
                        "\(purpose)/\(level): Fable on a low tier overpays for routine work")
                }
            }
        }
    }

    @Test func haikuOnlyOnLightReviewAndWritingCells() {
        for (purpose, row) in IntelligenceRouter.matrix {
            for (level, cell) in row {
                for candidate in cell where candidate.sku.hasPrefix("claude-haiku") {
                    #expect(
                        [PromptPurpose.review, .writing].contains(purpose) && level <= .light,
                        "\(purpose)/\(level): Haiku can't carry implementation/planning work")
                }
            }
        }
    }

    @Test func maxEffortReservedForFablePlanningAndDebugging() {
        for (purpose, row) in IntelligenceRouter.matrix {
            for (level, cell) in row {
                for candidate in cell where candidate.effort == "max" {
                    #expect(candidate.sku.hasPrefix("claude-fable"))
                    #expect([PromptPurpose.planning, .debugging].contains(purpose))
                    #expect(level == .max, "\(purpose)/\(level)")
                }
            }
        }
    }

    @Test func ultraOnlyForProAccountsAtMaxOnSol() {
        // Pro + Max on an ultra-eligible cell upgrades to Sol's `ultra`.
        let pro = IntelligenceRouter.route(
            purpose: .backendImpl, level: .max, connected: [.codex],
            codexPro: true, fallback: fallback)
        #expect(pro.model == "gpt-5.6-sol" && pro.effort == "ultra")
        // No Pro → stays xhigh; below Max → stays xhigh even for Pro.
        let free = IntelligenceRouter.route(
            purpose: .backendImpl, level: .max, connected: [.codex],
            codexPro: false, fallback: fallback)
        #expect(free.effort == "xhigh")
        let deep = IntelligenceRouter.route(
            purpose: .backendImpl, level: .deep, connected: [.codex],
            codexPro: true, fallback: fallback)
        #expect(deep.effort == "xhigh")
        // And never anywhere in the raw matrix.
        for (_, row) in IntelligenceRouter.matrix {
            for (_, cell) in row {
                #expect(cell.allSatisfy { $0.effort != EffortLadder.proEffort })
            }
        }
    }

    // MARK: Constraints

    @Test func gpt56ExposesMaxIndependentOfProPlan() {
        for sku in ["gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna"] {
            #expect(EffortLadder.efforts(for: sku, codexPro: false).last == "max")
        }
        #expect(!EffortLadder.efforts(for: "gpt-5.5", codexPro: true).contains("max"))
        #expect(
            EffortLadder.efforts(for: "gpt-5.6-sol", codexPro: true)
                == ["low", "medium", "high", "xhigh", "max", "ultra"])
    }

    @Test func pinRestrictsToThePinnedProvider() {
        for purpose in allPurposes {
            for level in allLevels {
                let claude = IntelligenceRouter.route(
                    purpose: purpose, level: level, connected: [.claudeCode, .codex],
                    pinned: .claudeCode, fallback: fallback)
                #expect(BackendID.forModel(claude.model) == .claudeCode)
                let codex = IntelligenceRouter.route(
                    purpose: purpose, level: level, connected: [.claudeCode, .codex],
                    pinned: .codex, fallback: fallback)
                #expect(BackendID.forModel(codex.model) == .codex)
            }
        }
    }

    @Test func backendLockKeepsRoutingInsideTheSession() {
        // A locked backend counts as reachable even if the availability probe disagrees —
        // the session is already running on it.
        let r = IntelligenceRouter.route(
            purpose: .backendImpl, level: .balanced, connected: [.codex],
            backendLock: .claudeCode, fallback: fallback)
        #expect(BackendID.forModel(r.model) == .claudeCode)
        // `.codexExec` locks onto the Codex model lane.
        let exec = IntelligenceRouter.route(
            purpose: .frontendImpl, level: .balanced, connected: [],
            backendLock: .codexExec, fallback: fallback)
        #expect(BackendID.forModel(exec.model) == .codex)
    }

    @Test func degradedLaneIsDemotedNeverDropped() {
        // Balanced backend work prefers Sol; a Codex incident flips it to the Claude lane.
        let healthy = IntelligenceRouter.route(
            purpose: .backendImpl, level: .balanced, connected: [.claudeCode, .codex],
            fallback: fallback)
        #expect(healthy.model == "gpt-5.6-sol")
        let demoted = IntelligenceRouter.route(
            purpose: .backendImpl, level: .balanced, connected: [.claudeCode, .codex],
            degraded: [.codex], fallback: fallback)
        #expect(BackendID.forModel(demoted.model) == .claudeCode)
        // Everything degraded → the original ranking stands rather than resolving nothing.
        let allDown = IntelligenceRouter.route(
            purpose: .backendImpl, level: .balanced, connected: [.claudeCode, .codex],
            degraded: [.claudeCode, .codex], fallback: fallback)
        #expect(allDown.model == healthy.model)
    }

    @Test func reachedQuotaWindowsBecomeTemporaryRoutingLimits() {
        let now = Date(timeIntervalSince1970: 1_000_000)
        let future = now.addingTimeInterval(600)
        let claude = SubscriptionUsage(
            fiveHour: UsageWindow(utilization: 100, resetsAt: future),
            sevenDay: nil,
            sevenDayOpus: UsageWindow(utilization: 100, resetsAt: future),
            sevenDaySonnet: nil)
        let codex = CodexUsage(weekly: CodexUsageWindow(
            utilization: 100, windowMinutes: 10_080, resetsAt: future))
        let limits = IntelligenceRouter.Limits(
            claudeUsage: claude, codexUsage: codex,
            claudeRateLimit: nil, now: now)

        #expect(limits.providers == [.claudeCode, .codex])
        #expect(limits.models.contains("claude-opus-5"))
        // The same stale snapshots stop excluding routes as soon as their reset passes.
        let reset = IntelligenceRouter.Limits(
            claudeUsage: claude, codexUsage: codex,
            claudeRateLimit: nil, now: future)
        #expect(reset.providers.isEmpty)
        #expect(reset.models.isEmpty)

        // With no OAuth snapshot, the Claude stream rejection is the coarse fallback.
        let coarse = IntelligenceRouter.Limits(
            claudeUsage: nil, codexUsage: nil,
            claudeRateLimit: RateLimit(status: "rejected", resetsAt: future), now: now)
        #expect(coarse.providers == [.claudeCode])
        let coarseReset = IntelligenceRouter.Limits(
            claudeUsage: nil, codexUsage: nil,
            claudeRateLimit: RateLimit(status: "rejected", resetsAt: future), now: future)
        #expect(coarseReset.providers.isEmpty)
    }

    @Test func limitedPreferredProviderFallsThroughToOtherRankedLane() {
        let resolution = IntelligenceRouter.route(
            purpose: .backendImpl, level: .balanced,
            connected: [.claudeCode, .codex], limits: .init(providers: [.codex]),
            fallback: fallback)
        #expect(BackendID.forModel(resolution.model) == .claudeCode)
        #expect(resolution.isAvailable)
        #expect(resolution.reason.contains("usage limit"))
    }

    @Test func availableCapacityBreaksClosePerformanceCall() {
        // Sol and Opus are close choices for balanced backend work. With Codex's window
        // nearly consumed and ample Claude quota, preserve the scarce Codex capacity.
        let limits = IntelligenceRouter.Limits(
            providerUtilization: [.claudeCode: 10, .codex: 90])
        let resolution = IntelligenceRouter.route(
            purpose: .backendImpl, level: .balanced,
            connected: [.claudeCode, .codex], limits: limits,
            fallback: fallback)

        #expect(resolution.model == "claude-opus-5")
        #expect(resolution.reason.contains("more available quota"))
    }

    @Test func liveUsageSnapshotsFeedAvailableCapacityScore() {
        let now = Date(timeIntervalSince1970: 1_000_000)
        let future = now.addingTimeInterval(600)
        let claude = SubscriptionUsage(
            fiveHour: UsageWindow(utilization: 10, resetsAt: future),
            sevenDay: UsageWindow(utilization: 5, resetsAt: future),
            sevenDayOpus: UsageWindow(utilization: 15, resetsAt: future),
            sevenDaySonnet: nil)
        let codex = CodexUsage(weekly: CodexUsageWindow(
            utilization: 90, windowMinutes: 10_080, resetsAt: future))
        let limits = IntelligenceRouter.Limits(
            claudeUsage: claude, codexUsage: codex,
            claudeRateLimit: nil, now: now)

        let resolution = IntelligenceRouter.route(
            purpose: .backendImpl, level: .balanced,
            connected: [.claudeCode, .codex], limits: limits,
            fallback: fallback)
        #expect(resolution.model == "claude-opus-5")
    }

    @Test func performanceKeepsCloseCallUntilCapacityGapIsMaterial() {
        // A modest quota difference should not overturn the task's preferred model.
        let limits = IntelligenceRouter.Limits(
            providerUtilization: [.claudeCode: 40, .codex: 60])
        let resolution = IntelligenceRouter.route(
            purpose: .backendImpl, level: .balanced,
            connected: [.claudeCode, .codex], limits: limits,
            fallback: fallback)

        #expect(resolution.model == "gpt-5.6-sol")
        #expect(!resolution.reason.contains("more available quota"))
    }

    @Test func specialistPerformanceWinsDespiteLowProviderCapacity() {
        // Deep planning is a deliberately decisive Fable cell. Even a large quota imbalance
        // must not turn the capacity signal into a quality downgrade.
        let limits = IntelligenceRouter.Limits(
            providerUtilization: [.claudeCode: 95, .codex: 0])
        let resolution = IntelligenceRouter.route(
            purpose: .planning, level: .deep,
            connected: [.claudeCode, .codex], limits: limits,
            fallback: fallback)

        #expect(resolution.model == "claude-fable-5")
        #expect(!resolution.reason.contains("more available quota"))
    }

    @Test func familyCapacityContributesToModelWorthwhileScore() {
        let limits = IntelligenceRouter.Limits(
            providerUtilization: [.claudeCode: 10, .codex: 20],
            modelUtilization: ["claude-opus-5": 95])
        #expect(
            abs((limits.availableCapacity(forModel: "claude-opus-5") ?? -1) - 0.05)
                < 0.000_001)
        #expect(
            abs((limits.availableCapacity(forModel: "claude-sonnet-5") ?? -1) - 0.9)
                < 0.000_001)
        #expect(
            abs((limits.availableCapacity(forModel: "gpt-5.6-sol") ?? -1) - 0.8)
                < 0.000_001)
    }

    @Test func quotaTemporarilyOverridesProviderPin() {
        let resolution = IntelligenceRouter.route(
            purpose: .planning, level: .balanced,
            connected: [.claudeCode, .codex], pinned: .claudeCode,
            limits: .init(providers: [.claudeCode]), fallback: fallback)
        #expect(BackendID.forModel(resolution.model) == .codex)
        #expect(resolution.reason.contains("pinned provider"))
    }

    @Test func modelFamilyLimitUsesNearestSamePurposeMatch() {
        let resolution = IntelligenceRouter.route(
            purpose: .planning, level: .balanced,
            connected: [.claudeCode], pinned: .claudeCode,
            limits: .init(models: ["claude-opus-5"]), fallback: fallback)
        #expect(resolution.model == "claude-sonnet-5")
        #expect(resolution.isAvailable)
        #expect(resolution.reason.contains("nearest available match"))
    }

    @Test func fullyLimitedLockedBackendIsUnavailable() {
        let resolution = IntelligenceRouter.route(
            purpose: .debugging, level: .deep,
            connected: [.claudeCode, .codex], backendLock: .claudeCode,
            limits: .init(providers: [.claudeCode]), fallback: fallback)
        #expect(!resolution.isAvailable)
        #expect(resolution.reason.contains("every compatible model"))
    }

    // MARK: Mixed-intent blend

    @Test func mixedIntentRaisesPrimaryToSecondaryCostFloor() {
        let primary = IntelligenceRouter.route(
            purpose: .writing, level: .balanced, connected: [.claudeCode],
            fallback: fallback)
        let secondary = IntelligenceRouter.route(
            purpose: .debugging, level: .balanced, connected: [.claudeCode],
            fallback: fallback)
        let mixed = IntelligenceRouter.routeMixed(
            primary: .writing, secondary: .debugging, level: .balanced,
            connected: [.claudeCode], fallback: fallback)

        #expect(IntelligenceRouter.costTier(of: primary.model) == 1)
        #expect(IntelligenceRouter.costTier(of: secondary.model) == 2)
        #expect(IntelligenceRouter.costTier(of: mixed.model) == 2)
        #expect(mixed.purpose == .writing)
        #expect(mixed.level == .balanced)
        #expect(mixed.reason.contains("\(PromptPurpose.writing.displayName) + \(PromptPurpose.debugging.displayName)"))
        #expect(mixed.reason.contains("secondary intent's cost tier"))
    }

    @Test func mixedIntentBlendStaysBoundedForEveryPair() {
        for primaryPurpose in allPurposes {
            for secondaryPurpose in allPurposes where secondaryPurpose != primaryPurpose {
                for level in allLevels {
                    for connected in connectedCombos.dropFirst() {
                        let primary = IntelligenceRouter.route(
                            purpose: primaryPurpose, level: level, connected: connected,
                            fallback: fallback)
                        let secondary = IntelligenceRouter.route(
                            purpose: secondaryPurpose, level: level, connected: connected,
                            fallback: fallback)
                        let mixed = IntelligenceRouter.routeMixed(
                            primary: primaryPurpose, secondary: secondaryPurpose,
                            level: level, connected: connected, fallback: fallback)
                        let primaryTier = IntelligenceRouter.costTier(of: primary.model)
                        let secondaryTier = IntelligenceRouter.costTier(of: secondary.model)
                        let mixedTier = IntelligenceRouter.costTier(of: mixed.model)
                        #expect(
                            mixedTier >= max(primaryTier, secondaryTier),
                            "\(primaryPurpose)+\(secondaryPurpose) at \(level) under-routes")
                        #expect(
                            mixedTier <= max(primaryTier, secondaryTier) + 1,
                            "\(primaryPurpose)+\(secondaryPurpose) at \(level) over-routes")
                        #expect(mixed.purpose == primaryPurpose)
                        #expect(mixed.level == level)
                    }
                }
            }
        }
    }

    // MARK: Misroute damage bound

    /// The binding accuracy invariant: even when the heuristic classifier mislabels a
    /// fixture, routing its *predicted* purpose lands within one cost tier of routing the
    /// *true* purpose, at every level and in every lane — so a misclassification can never
    /// cause the 12× overpay (or a hard task on a toy model) the feature exists to prevent.
    @Test func fixtureMisroutesStayWithinOneCostTier() throws {
        for fixture in try PurposeFixtures.load() {
            let predicted = HeuristicPurposeClassifier.classify(fixture.prompt).purpose
            guard let truth = PromptPurpose(rawValue: fixture.purpose) else {
                Issue.record("unknown label \(fixture.purpose)")
                continue
            }
            for level in allLevels {
                for connected in connectedCombos.dropFirst() {  // skip the empty set
                    let routedPredicted = IntelligenceRouter.route(
                        purpose: predicted, level: level, connected: connected,
                        fallback: fallback)
                    let routedTruth = IntelligenceRouter.route(
                        purpose: truth, level: level, connected: connected, fallback: fallback)
                    let drift = abs(
                        IntelligenceRouter.costTier(of: routedPredicted.model)
                            - IntelligenceRouter.costTier(of: routedTruth.model))
                    #expect(
                        drift <= 1,
                        """
                        "\(fixture.prompt)": predicted \(predicted) vs true \(truth) drifts \
                        \(drift) tiers at \(level) with \(connected)
                        """)
                }
            }
        }
    }
}

/// The labeled prompt set shared by the routing and classifier suites.
enum PurposeFixtures {
    struct Fixture: Decodable {
        let prompt: String
        let purpose: String
    }

    static func load() throws -> [Fixture] {
        guard
            let url = Bundle.module.url(
                forResource: "purpose-prompts", withExtension: "json", subdirectory: "Fixtures")
        else {
            fatalError("missing fixture purpose-prompts.json")
        }
        return try JSONDecoder().decode([Fixture].self, from: Data(contentsOf: url))
    }
}
