import Foundation
import Testing
import NucleicProtocol

@testable import NucleicCore

/// Validates the real authority wiring: a live `AppStore` standing in as `SyncHostBridge`,
/// projected to a `SyncClient` over an in-memory pipe. Complements `SyncHostTests` (which
/// uses a fake bridge) by exercising the actual session→wire mapping and snapshot path.
@MainActor
@Suite("AppStore sync bridge", .serialized)
struct AppStoreSyncBridgeTests {
    private func makeStore(repo: GitTestRepo) -> AppStore {
        let database = try! GRDBMetadataStore(path: nil)
        let worktrees = GitWorktreeManager(now: { Date(timeIntervalSince1970: 1_700_000_000) })
        let transcriptsDir = URL(fileURLWithPath: (repo.container as NSString)
            .appendingPathComponent("transcripts"))
        return AppStore(
            database: database, worktrees: worktrees, transcriptsDir: transcriptsDir,
            now: { Date(timeIntervalSince1970: 1_700_000_000) }
        ) { session in
            let wtPath = session.worktreePath ?? ""
            return ScriptedBackend { e, _ in
                e.emit(.sessionStarted(SessionStarted(
                    backendSessionID: "be", model: "m", cwd: wtPath, toolNames: ["Write"])))
                e.emit(.assistantText(TextChunk(messageID: "m1", text: "on it", isPartial: false)))
                e.emit(.turnCompleted(TurnCompleted(stopReason: "end_turn", usage: nil)))
                e.emit(.runFinished(RunFinished(outcome: .completed)))
            }
        }
    }

    @Test func phoneSeesRealSessionsAndSnapshot() async throws {
        let repo = try await GitTestRepo()
        defer { repo.cleanup() }
        let store = makeStore(repo: repo)
        store.syncHostName = "Andrew's Mac"

        let project = await store.addProject(name: "demo", rootPath: repo.root, defaultBranch: "main")
        let projectID = try #require(project?.id)
        let sessionID = try await store.createSession(
            in: store.project(projectID)!, title: "make a file", prompt: "go")
        store.openSessionID = sessionID
        await store.awaitOpenSessionSettled()

        // Stand the sync server up with the live store as its authority.
        let hostIdentity = DeviceIdentity()
        let host = SyncHost(identity: hostIdentity, bridge: store, store: InMemoryPairedDeviceStore())
        let listener = ManualListener()
        try await host.start(listener: listener)
        let (clientCh, serverCh) = MemoryChannel.pair()
        listener.push(serverCh)
        let payload = await host.beginPairing()

        let client = SyncClient(
            channel: clientCh, identity: DeviceIdentity(), hostStaticKey: payload.hostStaticKey,
            mode: .pair(secret: payload.pairingSecret), deviceID: "iphone-1", deviceLabel: "iPhone")
        let recorder = EventRecorder()
        await recorder.consume(client.start())

        let ready = await recorder.waitFor { if case .ready = $0 { return true } else { return false } }
        guard case .ready(let welcome) = ready else { Issue.record("no welcome"); return }
        #expect(welcome.host.hostName == "Andrew's Mac")

        await client.send(.listSessions)
        let list = await recorder.waitFor { if case .sessionList = $0 { return true } else { return false } }
        guard case .sessionList(let summaries) = list else { Issue.record("no list"); return }
        #expect(summaries.count == 1)
        let summary = try #require(summaries.first)
        #expect(summary.sessionID == sessionID)
        #expect(summary.projectName == "demo")
        #expect(summary.title == "make a file")
        #expect(summary.status == .awaitingInput)

        await client.send(.subscribe(Subscribe(sessionID: sessionID, sinceSeq: nil, verbosity: .full)))
        let snap = await recorder.waitFor { if case .snapshot = $0 { return true } else { return false } }
        guard case .snapshot(let snapshot) = snap else { Issue.record("no snapshot"); return }
        #expect(snapshot.summary.sessionID == sessionID)
        // The transcript tail came across: the agent's assistant text is in there.
        #expect(snapshot.recentEvents.contains {
            if case .assistantText(let c) = $0.kind { return c.text == "on it" } else { return false }
        })

        await host.stop()
    }
}
