import Foundation
import Testing
@testable import ClientTLS

// B3 · ControlPlaneLoginClient request-building + response-mapping, driven by a
// stub transport (no network). Mirrors the PINNED login contract exactly:
//
//   POST /auth/login { "password": "<operator secret>" }
//     → 201 { "enrollToken": "<jwt/capability>", "accountId": "<uuid>", "expiresIn": <seconds> }

private let cpBaseURL = URL(string: "https://cp.terminal.yaojia.wang")!

/// Records the last request and replays a canned response. `@unchecked Sendable`:
/// the mutable capture is guarded by a lock.
private final class LoginStubTransport: EnrollmentTransport, @unchecked Sendable {
    private let lock = NSLock()
    private var _lastRequest: URLRequest?
    private let status: Int
    private let body: Data

    init(status: Int, body: Data) {
        self.status = status
        self.body = body
    }

    var lastRequest: URLRequest? { lock.withLock { _lastRequest } }

    func send(_ request: URLRequest) async throws -> (Data, HTTPURLResponse) {
        lock.withLock { _lastRequest = request }
        let response = HTTPURLResponse(
            url: request.url!, statusCode: status, httpVersion: "HTTP/1.1", headerFields: nil
        )!
        return (body, response)
    }
}

private func loginBody(
    enrollToken: String = "v4.public.enroll-token",
    accountId: String = "11111111-2222-3333-4444-555555555555",
    expiresIn: Int = 600
) -> Data {
    let json: [String: Any] = [
        "enrollToken": enrollToken,
        "accountId": accountId,
        "expiresIn": expiresIn,
    ]
    return try! JSONSerialization.data(withJSONObject: json)
}

@Test("login builds a POST /auth/login carrying only the password (never bearer)")
func loginBuildsRequest() async throws {
    // Arrange
    let stub = LoginStubTransport(status: 201, body: loginBody())
    let client = ControlPlaneLoginClient(baseURL: cpBaseURL, transport: stub)

    // Act
    _ = try await client.login(password: "operator-secret")

    // Assert
    let request = try #require(stub.lastRequest)
    #expect(request.httpMethod == "POST")
    #expect(request.url?.absoluteString == "https://cp.terminal.yaojia.wang/auth/login")
    #expect(request.value(forHTTPHeaderField: "Content-Type") == "application/json")
    // Login is credential-based, NOT bearer-authed — no Authorization header.
    #expect(request.value(forHTTPHeaderField: "Authorization") == nil)

    let sent = try #require(request.httpBody)
    let object = try #require(try JSONSerialization.jsonObject(with: sent) as? [String: Any])
    #expect(object["password"] as? String == "operator-secret")
    #expect(object.keys.count == 1) // password ONLY — no extra fields leak
}

@Test("login maps a 201 response into a typed LoginResult")
func loginMapsResponse() async throws {
    // Arrange
    let stub = LoginStubTransport(status: 201, body: loginBody(
        enrollToken: "v4.public.abc", accountId: "acct-uuid", expiresIn: 900
    ))
    let client = ControlPlaneLoginClient(baseURL: cpBaseURL, transport: stub)

    // Act
    let result = try await client.login(password: "pw")

    // Assert
    #expect(result.enrollToken == "v4.public.abc")
    #expect(result.accountId == "acct-uuid")
    #expect(result.expiresIn == 900)
}

@Test("an empty password fails fast without any network call")
func loginEmptyPasswordRejected() async throws {
    // Arrange
    let stub = LoginStubTransport(status: 201, body: loginBody())
    let client = ControlPlaneLoginClient(baseURL: cpBaseURL, transport: stub)

    // Act / Assert — boundary validation: reject before sending.
    await #expect(throws: ControlPlaneLoginError.emptyPassword) {
        _ = try await client.login(password: "")
    }
    #expect(stub.lastRequest == nil) // proves zero network happened
}

@Test("a 401 login response surfaces http with the server error code")
func loginRejectSurfacesStatus() async throws {
    // The login stub seam denies-by-default → { error: "login rejected" }.
    let body = try! JSONSerialization.data(withJSONObject: ["error": "login rejected"])
    let stub = LoginStubTransport(status: 401, body: body)
    let client = ControlPlaneLoginClient(baseURL: cpBaseURL, transport: stub)

    await #expect(throws: ControlPlaneLoginError.http(status: 401, code: "login rejected")) {
        _ = try await client.login(password: "wrong")
    }
}

@Test("a malformed 201 body throws malformedResponse")
func loginMalformedBody() async throws {
    let stub = LoginStubTransport(status: 201, body: Data("not json".utf8))
    let client = ControlPlaneLoginClient(baseURL: cpBaseURL, transport: stub)

    await #expect(throws: ControlPlaneLoginError.malformedResponse) {
        _ = try await client.login(password: "pw")
    }
}
