import ClientTLS
import Foundation
import WireProtocol

/// B3 · Production wiring that connects the ClientTLS enrollment library to the
/// app's real transports. Kept in the assembly layer (not the leaf package) so
/// `ClientTLS` stays dependency-free and unit-testable; here we only adapt types
/// and read persisted config — no crypto, no keychain of our own.

/// Adapts the app's `HTTPTransport` (WireProtocol) to `ClientTLS`'s
/// `EnrollmentTransport`. The two protocols have an identical `send` shape; this
/// one-line bridge lets the SAME `URLSessionHTTPTransport` — which already
/// presents the device identity on a client-cert challenge (mTLS) — serve login,
/// enroll AND the silent renew, so renew authenticates with the current cert.
struct EnrollmentTransportAdapter: EnrollmentTransport {
    private let http: any HTTPTransport

    init(http: any HTTPTransport) {
        self.http = http
    }

    func send(_ request: URLRequest) async throws -> (Data, HTTPURLResponse) {
        try await http.send(request)
    }
}

/// B3 · Non-secret enrollment config persisted for the silent rotation scheduler
/// (the control-plane URL to renew against) and to prefill the re-enroll form.
/// Namespaced statics over `UserDefaults.standard` — nothing secret is stored
/// here (the bearer is never persisted; the private key stays in the Secure
/// Enclave; the leaf/chain live in the keychain via `KeychainClientIdentityStore`).
enum EnrollmentSettings {
    private static let controlPlaneURLKey = "webterm.enroll.controlPlaneURL"
    private static let subdomainKey = "webterm.enroll.subdomain"

    static var controlPlaneURL: String? {
        get { UserDefaults.standard.string(forKey: controlPlaneURLKey) }
        set { UserDefaults.standard.set(newValue, forKey: controlPlaneURLKey) }
    }

    static var subdomain: String? {
        get { UserDefaults.standard.string(forKey: subdomainKey) }
        set { UserDefaults.standard.set(newValue, forKey: subdomainKey) }
    }
}

enum DeviceEnrollmentWiringError: Error, Equatable, Sendable {
    /// The control-plane URL is missing/invalid — cannot build a renew client.
    case missingControlPlaneURL
    /// The typed control-plane URL is not a valid absolute URL.
    case invalidControlPlaneURL
}

/// Build the enrollment flow (login → SE keygen+CSR → enroll → store) against the
/// given control-plane URL, reusing the app's mTLS-capable transport.
func makeDeviceEnrollmentFlow(
    controlPlaneURL: URL,
    http: any HTTPTransport,
    store: KeychainClientIdentityStore = KeychainClientIdentityStore()
) -> DeviceEnrollmentFlow {
    DeviceEnrollmentFlow(
        baseURL: controlPlaneURL,
        transport: EnrollmentTransportAdapter(http: http),
        store: store
    )
}

/// Build the silent rotation scheduler. `renewalState` reads the installed leaf's
/// timing; `performRenew` re-CSRs from the SAME Secure-Enclave key and POSTs
/// `/device/:id/renew` over mTLS (nil bearer — the current cert authenticates).
/// The control-plane URL is read fresh from `EnrollmentSettings` at renew time.
func makeCertificateRotationScheduler(
    http: any HTTPTransport,
    store: KeychainClientIdentityStore = KeychainClientIdentityStore()
) -> CertificateRotationScheduler {
    let transport = EnrollmentTransportAdapter(http: http)
    return CertificateRotationScheduler(
        renewalState: { try store.renewalState() },
        performRenew: {
            guard let urlString = EnrollmentSettings.controlPlaneURL,
                  let url = URL(string: urlString) else {
                throw DeviceEnrollmentWiringError.missingControlPlaneURL
            }
            // nil bearer: /device/:id/renew authenticates by the presented
            // client cert (mTLS), which the adapter's transport supplies.
            let client = DeviceEnrollmentClient(baseURL: url, bearerToken: nil, transport: transport)
            return try await store.renew(using: client)
        }
    )
}
