import Foundation

/// B3 · The phone-track enrollment FLOW — the composition that turns the pinned
/// bootstrap contract into an installed, hardware-bound device identity:
///
///   1. `POST /auth/login { password }`  → short-lived `device:enroll` bearer.
///   2. Build a `DeviceEnrollmentClient` authenticated with THAT bearer.
///   3. `install` = generate a NON-EXPORTABLE Secure-Enclave key, self-sign a
///      PKCS#10 CSR, `POST /device/enroll`, and store the returned leaf against
///      the SE key — so it presents automatically on the existing mTLS path.
///
/// It reuses the enrollment library wholesale (`ControlPlaneLoginClient`,
/// `DeviceEnrollmentClient`, `KeychainClientIdentityStore.enroll`) and adds NO
/// crypto/keychain of its own — it is pure orchestration, so it is unit-testable
/// with a stub transport + a fake installer (no network, no keychain).
///
/// The `install` step is injected (rather than a hard dependency on the keychain
/// store) so the composition is testable off-device; production supplies the
/// `KeychainClientIdentityStore` convenience initializer below.
public struct DeviceEnrollmentFlow: Sendable {
    /// The install step: given the bearer-authenticated client + the requested
    /// subdomain/name, generate the SE key + CSR, enroll, and persist the leaf.
    /// Returns the installed cert summary (nil only if the leaf can't be read).
    public typealias Install = @Sendable (
        _ client: DeviceEnrollmentClient, _ subdomain: String, _ deviceName: String
    ) async throws -> ClientCertificateSummary?

    private let baseURL: URL
    private let transport: any EnrollmentTransport
    private let install: Install

    public init(baseURL: URL, transport: any EnrollmentTransport, install: @escaping Install) {
        self.baseURL = baseURL
        self.transport = transport
        self.install = install
    }

    /// Production convenience: wire `install` to the keychain store's
    /// Secure-Enclave enrollment (the `.p12`-free path). The SE key is generated
    /// inside `store.enroll` and never leaves hardware.
    public init(
        baseURL: URL, transport: any EnrollmentTransport, store: KeychainClientIdentityStore
    ) {
        self.init(baseURL: baseURL, transport: transport) { client, subdomain, deviceName in
            try await store.enroll(using: client, subdomain: subdomain, deviceName: deviceName)
        }
    }

    /// Run the full bootstrap. Errors propagate unchanged so the UI can map them:
    /// `ControlPlaneLoginError` (login step), `DeviceEnrollmentError` (enroll
    /// step, e.g. 403 subdomain-not-owned), or a keychain/CSR error from install.
    /// A login failure short-circuits — the enroll step never runs.
    public func run(
        password: String, subdomain: String, deviceName: String
    ) async throws -> ClientCertificateSummary? {
        let login = try await ControlPlaneLoginClient(baseURL: baseURL, transport: transport)
            .login(password: password)
        let client = DeviceEnrollmentClient(
            baseURL: baseURL, bearerToken: login.enrollToken, transport: transport
        )
        return try await install(client, subdomain, deviceName)
    }
}
