import XCTest
import AIFabric

/// Gate 4 (AI flow): proves the S2 AIRouter produces a VISIBLE routingNote on a rule-based fallback,
/// and that an explicit-provider-unavailable pick throws (no silent fallback). Sources/AI is consumed
/// read-only; these tests never modify it.
final class RouterFallbackTests: XCTestCase {

    func testRuleFallbackIsVisible() async throws {
        let router = AIRouter(providers: [
            .onDevice: MockAIProvider(id: .onDevice, available: false),
            .localMLX: MockAIProvider(id: .localMLX, available: true),
        ])
        let resp = try await router.route(AICompletionRequest(task: .quickSummarize, prompt: "x"))
        XCTAssertEqual(resp.providerUsed, .localMLX)
        XCTAssertEqual(resp.routingNote, "fallback from onDevice → localMLX")
    }

    func testExplicitUnavailableThrowsNoFallback() async throws {
        let router = AIRouter(providers: [
            .onDevice: MockAIProvider(id: .onDevice, available: false),
            .localMLX: MockAIProvider(id: .localMLX, available: true),
        ])
        do {
            _ = try await router.route(
                AICompletionRequest(task: .quickSummarize, prompt: "x", explicitProvider: .onDevice)
            )
            XCTFail("expected explicitProviderUnavailable")
        } catch let error as AIRoutingError {
            guard case .explicitProviderUnavailable = error else {
                return XCTFail("wrong error: \(error)")
            }
        }
    }

    func testCorpusAskRoutesRemoteWithCitation() async throws {
        let router = AIRouter(providers: [.remoteDS: MockAIProvider(id: .remoteDS)])
        let resp = try await router.route(AICompletionRequest(task: .corpusAsk, prompt: "q"))
        XCTAssertEqual(resp.providerUsed, .remoteDS)
        XCTAssertEqual(resp.citations.count, 1)
    }
}
