import Foundation
import Testing
@testable import ClientTLS

// C-iOS-1 · The responder truth table: 3 challenge types × identity present /
// absent. Exercised through the pure method-keyed core AND through a real
// `URLAuthenticationChallenge` (proving the method is extracted correctly),
// with no live socket.

private let responder = MutualTLSChallengeResponder()

private func makeIdentity() throws -> ClientIdentity {
    try PKCS12Importer.importIdentity(
        data: ClientTLSFixtures.deviceP12Data, passphrase: ClientTLSFixtures.passphrase
    )
}

// MARK: - ClientCertificate

@Test("ClientCertificate + identity → .useCredential with a credential")
func clientCertWithIdentityUsesCredential() throws {
    // Arrange
    let identity = try makeIdentity()

    // Act
    let resolution = responder.resolve(
        authenticationMethod: NSURLAuthenticationMethodClientCertificate, identity: identity
    )

    // Assert
    #expect(resolution.disposition == .useCredential)
    #expect(resolution.credential != nil)
    #expect(resolution.credential?.identity != nil)
}

@Test("ClientCertificate + no identity → .cancelAuthenticationChallenge, no credential")
func clientCertWithoutIdentityCancels() {
    // Act
    let resolution = responder.resolve(
        authenticationMethod: NSURLAuthenticationMethodClientCertificate, identity: nil
    )

    // Assert
    #expect(resolution.disposition == .cancelAuthenticationChallenge)
    #expect(resolution.credential == nil)
}

// MARK: - ServerTrust

@Test("ServerTrust → .performDefaultHandling regardless of identity")
func serverTrustDefaultHandling() throws {
    let identity = try makeIdentity()
    for id in [identity, nil] as [ClientIdentity?] {
        let resolution = responder.resolve(
            authenticationMethod: NSURLAuthenticationMethodServerTrust, identity: id
        )
        #expect(resolution.disposition == .performDefaultHandling)
        #expect(resolution.credential == nil)
    }
}

// MARK: - Other method (e.g. Basic)

@Test("an unrelated method → .performDefaultHandling regardless of identity")
func otherMethodDefaultHandling() throws {
    let identity = try makeIdentity()
    for id in [identity, nil] as [ClientIdentity?] {
        let resolution = responder.resolve(
            authenticationMethod: NSURLAuthenticationMethodHTTPBasic, identity: id
        )
        #expect(resolution.disposition == .performDefaultHandling)
        #expect(resolution.credential == nil)
    }
}

// MARK: - Full URLAuthenticationChallenge extraction

@Test("resolve(challenge:) extracts the method from the protection space")
func resolveFromRealChallenge() throws {
    // Arrange — a real challenge for the ClientCertificate method.
    let identity = try makeIdentity()
    let challenge = makeChallenge(method: NSURLAuthenticationMethodClientCertificate)

    // Act
    let withIdentity = responder.resolve(challenge, identity: identity)
    let withoutIdentity = responder.resolve(challenge, identity: nil)

    // Assert
    #expect(withIdentity.disposition == .useCredential)
    #expect(withIdentity.credential != nil)
    #expect(withoutIdentity.disposition == .cancelAuthenticationChallenge)
}

// MARK: - Helpers

/// Minimal sender so `URLAuthenticationChallenge` can be constructed in-process
/// (its designated init requires a non-optional sender). The responder never
/// calls back into the sender — it only reads `protectionSpace`.
private final class NoopChallengeSender: NSObject, URLAuthenticationChallengeSender {
    func use(_ credential: URLCredential, for challenge: URLAuthenticationChallenge) {}
    func continueWithoutCredential(for challenge: URLAuthenticationChallenge) {}
    func cancel(_ challenge: URLAuthenticationChallenge) {}
}

private func makeChallenge(method: String) -> URLAuthenticationChallenge {
    let space = URLProtectionSpace(
        host: "t1.terminal.yaojia.wang", port: 443, protocol: "https",
        realm: nil, authenticationMethod: method
    )
    return URLAuthenticationChallenge(
        protectionSpace: space, proposedCredential: nil, previousFailureCount: 0,
        failureResponse: nil, error: nil, sender: NoopChallengeSender()
    )
}
