import APIClient
import ClientTLS
import Foundation
import HostRegistry
import SessionCore
import WireProtocol

/// T-iOS-15 · Production dependency graph (composition root). One immutable
/// value assembled at launch; every screen/VM receives its dependencies from
/// here — no ad-hoc URLSession/keychain access anywhere else in the App layer.
///
/// Assembly security audit (task 安全注, verified at this single point):
/// - All `G` (state-changing) HTTP goes through `APIClient` (kill via
///   SessionListViewModel's client, probe's kill round-trip inside
///   `runPairingProbe`); `URLSessionHTTPTransport` adds no headers of its own.
/// - Origin is derived solely by `HostEndpoint` (WS: URLSessionTermTransport;
///   HTTP: APIClient's route builder) — nothing here hand-assembles one.
/// - Secrets: hosts in `KeychainHostStore`
///   (AfterFirstUnlockThisDeviceOnly, hosted keychain test asserts it);
///   UserDefaults carries only the non-secret per-host lastSessionId.
/// - No debug ATS overrides exist (project.yml declares NO
///   NSAllowsArbitraryLoads in any configuration — only the five §5.2 CIDR
///   exceptions), and no isSecureTextEntry-style screenshot hacks are used;
///   `UIScreen.isCaptured` detection is SKIPPED per plan (accepted residual
///   risk, local trust model).
struct AppEnvironment: Sendable {
    let hostStore: any HostStore
    let lastSessionStore: any LastSessionStore
    let http: any HTTPTransport
    let termTransport: any TermTransport
    /// Injected into `PairingViewModel` — production is `runPairingProbe`
    /// over the real transports (two-step: RO GET, then WS attach + guarded
    /// kill; only runs after the user's explicit confirm, T-iOS-12).
    let probe: PairingViewModel.Probe
    /// T-iOS-23 · unread last-seen watermarks (non-secret; UserDefaults).
    /// `var` + default so the memberwise init stays source-compatible for
    /// pre-P1 call sites while tests can inject an in-memory fake.
    var unreadStore: any UnreadWatermarkStore = UserDefaultsUnreadWatermarkStore()

    static func production() -> AppEnvironment {
        // C-iOS-2 (MEDIUM no-relaunch fix) · Resolve the installed device client
        // identity LAZILY from the keychain on each connect/challenge, injected
        // into BOTH transports + the probe as a provider. This way a certificate
        // imported from the "设备证书" screen takes effect on the NEXT connection
        // without relaunching — a snapshot captured here would stay stale. A
        // missing cert is the normal pre-install state (→ nil); genuine faults
        // are logged, not fatal (`loadedIdentityOrNil`). mTLS challenges only
        // fire for tunnel hosts, so a `nil` result is inert for local hosts.
        let identityStore = KeychainClientIdentityStore()
        let identityProvider: @Sendable () -> ClientIdentity? = {
            identityStore.loadedIdentityOrNil()
        }
        let http = URLSessionHTTPTransport(identityProvider: identityProvider)
        let termTransport = URLSessionTermTransport(identityProvider: identityProvider)
        return AppEnvironment(
            hostStore: KeychainHostStore(),
            lastSessionStore: UserDefaultsLastSessionStore(),
            http: http,
            termTransport: termTransport,
            probe: { endpoint in
                await runPairingProbe(endpoint: endpoint, http: http, ws: termTransport)
            }
        )
    }

    /// Away-digest source for a session engine: wraps `APIClient.events` per
    /// host (the engine never holds an HTTP client — plan §3.2).
    func makeEventsSource(endpoint: HostEndpoint)
        -> @Sendable (UUID) async throws -> [TimelineEvent] {
        let client = APIClient(endpoint: endpoint, http: http)
        return { id in try await client.events(id: id) }
    }
}
