import CodexBarCore
import Foundation
import Testing
@testable import CodexBar

@Suite(.serialized)
@MainActor
struct CodexAccountPromotionServiceTests {
    @Test
    func `happy path promotion swaps target auth into live home`() async throws {
        let container = try CodexAccountPromotionTestContainer(
            suiteName: "CodexAccountPromotionServiceTests-happy-path")
        defer { container.tearDown() }

        let target = try container.createManagedAccount(
            persistedEmail: "beta@example.com",
            authAccountID: "acct-beta")
        try container.persistAccounts([target])
        container.settings.codexActiveSource = .managedAccount(id: target.id)
        try container.removeLiveAuthFile()

        let targetAuthData = try container.managedAuthData(for: target)
        let result = try await container.makeService().promoteManagedAccount(id: target.id)
        let accounts = try container.loadAccounts().accounts
        await container.usageStore.widgetSnapshotPersistTask?.value
        let widgetSnapshot = WidgetSnapshotStore.load(from: container.widgetSnapshotURL)

        #expect(result.targetManagedAccountID == target.id)
        #expect(result.outcome == .promoted)
        #expect(result.displacedLiveDisposition == .none)
        #expect(result.didMutateLiveAuth)
        #expect(result.resultingActiveSource == .liveSystem)
        #expect(try container.liveAuthData() == targetAuthData)
        #expect(accounts.count == 1)
        #expect(accounts.first?.id == target.id)
        #expect(container.settings.codexActiveSource == .liveSystem)
        #expect(container.usageStore.snapshots[.codex]?.accountEmail(for: .codex) == "beta@example.com")
        #expect(widgetSnapshot?.entries.contains(where: { $0.provider == .codex }) == true)
    }

    @Test
    func `displaced live oauth is imported before target auth is promoted`() async throws {
        let container = try CodexAccountPromotionTestContainer(
            suiteName: "CodexAccountPromotionServiceTests-displaced-import",
            workspaceIdentities: [
                "acct-alpha": CodexOpenAIWorkspaceIdentity(
                    workspaceAccountID: "acct-alpha",
                    workspaceLabel: "Personal"),
            ])
        defer { container.tearDown() }

        let target = try container.createManagedAccount(
            persistedEmail: "beta@example.com",
            authAccountID: "acct-beta")
        try container.persistAccounts([target])
        container.settings.codexActiveSource = .managedAccount(id: target.id)
        let displacedLiveAuthData = try container.writeLiveOAuthAuthFile(
            email: "alpha@example.com",
            accountID: "acct-alpha")

        let result = try await container.makeService().promoteManagedAccount(id: target.id)
        let accounts = try container.loadAccounts().accounts
        let importedID: UUID
        switch result.displacedLiveDisposition {
        case let .imported(managedAccountID):
            importedID = managedAccountID
        case .none, .alreadyManaged:
            Issue.record("Expected displaced live account import")
            throw PromotionTestError.unexpectedDisposition
        }
        let imported = try #require(accounts.first(where: { $0.id == importedID }))

        #expect(accounts.count == 2)
        #expect(imported.email == "alpha@example.com")
        #expect(imported.providerAccountID == "acct-alpha")
        #expect(imported.workspaceLabel == "Personal")
        #expect(imported.workspaceAccountID == "acct-alpha")
        #expect(imported.authFingerprint == CodexAuthFingerprint.fingerprint(data: displacedLiveAuthData))
        #expect(try container.managedAuthData(for: imported) == displacedLiveAuthData)
        #expect(try container.liveAuthData() == container.managedAuthData(for: target))
    }

    @Test
    func `displaced live already managed uses reconciliation identity dedupe`() async throws {
        let container = try CodexAccountPromotionTestContainer(
            suiteName: "CodexAccountPromotionServiceTests-already-managed")
        defer { container.tearDown() }

        let target = try container.createManagedAccount(
            persistedEmail: "beta@example.com",
            authAccountID: "acct-beta")
        let existingManagedLive = try container.createManagedAccount(
            persistedEmail: "legacy@example.com",
            authEmail: "alpha@example.com",
            authAccountID: "acct-alpha",
            persistedProviderAccountID: nil)
        try container.persistAccounts([target, existingManagedLive])
        let liveAuthData = try container.writeLiveOAuthAuthFile(
            email: "alpha@example.com",
            accountID: "acct-alpha",
            apiKey: "sk-fresh-live")

        let result = try await container.makeService().promoteManagedAccount(id: target.id)
        let accounts = try container.loadAccounts().accounts
        let refreshedManagedLive = try #require(accounts.first(where: { $0.id == existingManagedLive.id }))

        #expect(result.displacedLiveDisposition == .alreadyManaged(managedAccountID: existingManagedLive.id))
        #expect(accounts.count == 2)
        #expect(accounts.contains(where: { $0.id == existingManagedLive.id }))
        #expect(refreshedManagedLive.authFingerprint == CodexAuthFingerprint.fingerprint(data: liveAuthData))
        #expect(try container.managedAuthData(for: refreshedManagedLive) == liveAuthData)
        #expect(try container.liveAuthData() == container.managedAuthData(for: target))
    }

    @Test
    func `provider only live auth refreshes existing managed account using persisted email`() async throws {
        let container = try CodexAccountPromotionTestContainer(
            suiteName: "CodexAccountPromotionServiceTests-already-managed-no-email")
        defer { container.tearDown() }

        let target = try container.createManagedAccount(
            persistedEmail: "beta@example.com",
            authAccountID: "acct-beta")
        let existingManagedLive = try container.createManagedAccount(
            persistedEmail: "alpha@example.com",
            authAccountID: "acct-alpha")
        try container.persistAccounts([target, existingManagedLive])
        let liveAuthData = try container.writeLiveOAuthAuthFileWithoutEmail(accountID: "acct-alpha")

        let result = try await container.makeService().promoteManagedAccount(id: target.id)
        let accounts = try container.loadAccounts().accounts
        let refreshedManagedLive = try #require(accounts.first(where: { $0.id == existingManagedLive.id }))

        #expect(result.displacedLiveDisposition == .alreadyManaged(managedAccountID: existingManagedLive.id))
        #expect(refreshedManagedLive.email == "alpha@example.com")
        #expect(refreshedManagedLive.providerAccountID == "acct-alpha")
        #expect(refreshedManagedLive.authFingerprint == CodexAuthFingerprint.fingerprint(data: liveAuthData))
        #expect(try container.managedAuthData(for: refreshedManagedLive) == liveAuthData)
    }

    @Test
    func `provider only live auth matching target converges and keeps managed active source`() async throws {
        let container = try CodexAccountPromotionTestContainer(
            suiteName: "CodexAccountPromotionServiceTests-provider-only-convergence")
        defer { container.tearDown() }

        let target = try container.createManagedAccount(
            persistedEmail: "alpha@example.com",
            authAccountID: "acct-alpha")
        try container.persistAccounts([target])
        let liveAuthData = try container.writeLiveOAuthAuthFileWithoutEmail(accountID: "acct-alpha")
        container.settings.codexActiveSource = .managedAccount(id: target.id)

        let result = try await container.makeService().promoteManagedAccount(id: target.id)

        #expect(result.outcome == .convergedNoOp)
        #expect(result.displacedLiveDisposition == .none)
        #expect(result.didMutateLiveAuth == false)
        #expect(result.resultingActiveSource == .managedAccount(id: target.id))
        #expect(try container.liveAuthData() == liveAuthData)
        #expect(try container.loadAccounts().accounts.count == 1)
        #expect(container.settings.codexActiveSource == .managedAccount(id: target.id))
    }

    @Test
    func `provider only live auth matching target converges when target managed auth is missing`() async throws {
        let container = try CodexAccountPromotionTestContainer(
            suiteName: "CodexAccountPromotionServiceTests-provider-only-convergence-missing-target-auth")
        defer { container.tearDown() }

        let target = try container.createManagedAccount(
            persistedEmail: "alpha@example.com",
            authAccountID: "acct-alpha")
        try container.persistAccounts([target])
        container.settings.codexActiveSource = .managedAccount(id: target.id)
        try FileManager.default.removeItem(
            at: URL(fileURLWithPath: target.managedHomePath, isDirectory: true)
                .appendingPathComponent("auth.json", isDirectory: false))
        let liveAuthData = try container.writeLiveOAuthAuthFileWithoutEmail(accountID: "acct-alpha")

        let result = try await container.makeService().promoteManagedAccount(id: target.id)

        #expect(result.outcome == .convergedNoOp)
        #expect(result.displacedLiveDisposition == .none)
        #expect(result.didMutateLiveAuth == false)
        #expect(result.resultingActiveSource == .managedAccount(id: target.id))
        #expect(try container.liveAuthData() == liveAuthData)
        #expect(container.settings.codexActiveSource == .managedAccount(id: target.id))
    }

    @Test
    func `snapshot convergence no op does not require target managed auth file`() async throws {
        let container = try CodexAccountPromotionTestContainer(
            suiteName: "CodexAccountPromotionServiceTests-snapshot-convergence-with-missing-target-auth")
        defer { container.tearDown() }

        let target = try container.createManagedAccount(
            persistedEmail: "alpha@example.com",
            authAccountID: "acct-alpha")
        try container.persistAccounts([target])
        try FileManager.default.removeItem(
            at: URL(fileURLWithPath: target.managedHomePath, isDirectory: true)
                .appendingPathComponent("auth.json", isDirectory: false))
        container.settings._test_liveSystemCodexAccount = ObservedSystemCodexAccount(
            email: "alpha@example.com",
            codexHomePath: container.liveHomeURL.path,
            observedAt: Date(),
            identity: .emailOnly(normalizedEmail: "alpha@example.com"))
        let liveAuthData = try container.writeLiveOAuthAuthFile(
            email: "alpha@example.com",
            accountID: "acct-alpha")

        let result = try await container.makeService().promoteManagedAccount(id: target.id)

        #expect(result.outcome == .convergedNoOp)
        #expect(result.displacedLiveDisposition == .none)
        #expect(result.didMutateLiveAuth == false)
        #expect(try container.liveAuthData() == liveAuthData)
    }

    @Test
    func `convergence no-op does not rewrite live auth or import displaced live`() async throws {
        let container = try CodexAccountPromotionTestContainer(
            suiteName: "CodexAccountPromotionServiceTests-convergence")
        defer { container.tearDown() }

        let target = try container.createManagedAccount(
            persistedEmail: "alpha@example.com",
            authAccountID: "acct-alpha")
        try container.persistAccounts([target])
        let liveAuthData = try container.writeLiveOAuthAuthFile(
            email: "alpha@example.com",
            accountID: "acct-alpha")

        let result = try await container.makeService().promoteManagedAccount(id: target.id)

        #expect(result.outcome == .convergedNoOp)
        #expect(result.displacedLiveDisposition == .none)
        #expect(result.didMutateLiveAuth == false)
        #expect(try container.liveAuthData() == liveAuthData)
        #expect(try container.loadAccounts().accounts.count == 1)
        #expect(container.settings.codexActiveSource == .liveSystem)
    }

    @Test
    func `same email different workspace imports displaced live as a distinct managed account`() async throws {
        let container = try CodexAccountPromotionTestContainer(
            suiteName: "CodexAccountPromotionServiceTests-same-email-different-workspace",
            workspaceIdentities: [
                "acct-personal": CodexOpenAIWorkspaceIdentity(
                    workspaceAccountID: "acct-personal",
                    workspaceLabel: "Personal"),
            ])
        defer { container.tearDown() }

        let target = try container.createManagedAccount(
            persistedEmail: "alice@example.com",
            authAccountID: "acct-team",
            workspaceLabel: "Team",
            workspaceAccountID: "acct-team")
        try container.persistAccounts([target])
        _ = try container.writeLiveOAuthAuthFile(email: "alice@example.com", accountID: "acct-personal")

        let result = try await container.makeService().promoteManagedAccount(id: target.id)
        let importedID: UUID
        switch result.displacedLiveDisposition {
        case let .imported(managedAccountID):
            importedID = managedAccountID
        case .none, .alreadyManaged:
            Issue.record("Expected same-email different-workspace import")
            throw PromotionTestError.unexpectedDisposition
        }
        let accounts = try container.loadAccounts().accounts
        let imported = try #require(accounts.first(where: { $0.id == importedID }))

        #expect(accounts.count == 2)
        #expect(imported.id != target.id)
        #expect(imported.email == "alice@example.com")
        #expect(imported.providerAccountID == "acct-personal")
        #expect(imported.workspaceLabel == "Personal")
        #expect(imported.workspaceAccountID == "acct-personal")
    }

    @Test
    func `mixed api key and oauth live auth still preserves displaced live identity`() async throws {
        let container = try CodexAccountPromotionTestContainer(
            suiteName: "CodexAccountPromotionServiceTests-mixed-auth-preservation",
            workspaceIdentities: [
                "acct-alpha": CodexOpenAIWorkspaceIdentity(
                    workspaceAccountID: "acct-alpha",
                    workspaceLabel: "Personal"),
            ])
        defer { container.tearDown() }

        let target = try container.createManagedAccount(
            persistedEmail: "beta@example.com",
            authAccountID: "acct-beta")
        try container.persistAccounts([target])
        _ = try container.writeLiveOAuthAuthFile(
            email: "alpha@example.com",
            accountID: "acct-alpha",
            apiKey: "sk-mixed-live")

        let result = try await container.makeService().promoteManagedAccount(id: target.id)
        let importedID: UUID
        switch result.displacedLiveDisposition {
        case let .imported(managedAccountID):
            importedID = managedAccountID
        case .none, .alreadyManaged:
            Issue.record("Expected displaced live import for mixed auth material")
            throw PromotionTestError.unexpectedDisposition
        }
        let imported = try #require(try container.loadAccounts().accounts.first(where: { $0.id == importedID }))

        #expect(imported.email == "alpha@example.com")
        #expect(imported.providerAccountID == "acct-alpha")
        #expect(imported.workspaceLabel == "Personal")
    }

    @Test
    func `store commit failure leaves live auth untouched because promotion preserves before mutating`() async throws {
        let container = try CodexAccountPromotionTestContainer(
            suiteName: "CodexAccountPromotionServiceTests-store-failure")
        defer { container.tearDown() }

        let target = try container.createManagedAccount(
            persistedEmail: "beta@example.com",
            authAccountID: "acct-beta")
        try container.persistAccounts([target])
        let liveAuthData = try container.writeLiveOAuthAuthFile(
            email: "alpha@example.com",
            accountID: "acct-alpha")
        let swapper = RecordingCodexLiveAuthSwapper()
        let store = RecordingManagedCodexAccountStore(base: container.fileStore) { _ in
            throw PromotionTestError.storeWriteFailed
        }

        await #expect(throws: CodexAccountPromotionError.managedStoreCommitFailed) {
            try await container.makeService(
                store: store,
                liveAuthSwapper: swapper).promoteManagedAccount(id: target.id)
        }

        #expect(swapper.swapCallCount == 0)
        #expect(try container.liveAuthData() == liveAuthData)
        #expect(try container.loadAccounts().accounts.count == 1)
        #expect(try container.loadAccounts().accounts.first?.id == target.id)
        #expect(try container.managedHomeURLs().count == 1)
    }

    @Test
    func `refresh store failure leaves existing managed metadata stale after auth copy`() async throws {
        let container = try CodexAccountPromotionTestContainer(
            suiteName: "CodexAccountPromotionServiceTests-refresh-store-failure")
        defer { container.tearDown() }

        let target = try container.createManagedAccount(
            persistedEmail: "beta@example.com",
            authAccountID: "acct-beta")
        let existingManagedLive = try container.createManagedAccount(
            persistedEmail: "alpha@example.com",
            authAccountID: "acct-alpha")
        try container.persistAccounts([target, existingManagedLive])
        let originalManagedAuthData = try container.managedAuthData(for: existingManagedLive)
        let liveAuthData = try container.writeLiveOAuthAuthFile(
            email: "alpha@example.com",
            accountID: "acct-alpha",
            apiKey: "sk-refreshed-live")
        let swapper = RecordingCodexLiveAuthSwapper()
        let store = RecordingManagedCodexAccountStore(base: container.fileStore) { accounts in
            if accounts.account(id: existingManagedLive.id)?
                .lastAuthenticatedAt != existingManagedLive.lastAuthenticatedAt
            {
                throw PromotionTestError.storeWriteFailed
            }
        }

        await #expect(throws: CodexAccountPromotionError.managedStoreCommitFailed) {
            try await container.makeService(
                store: store,
                liveAuthSwapper: swapper).promoteManagedAccount(id: target.id)
        }

        let accounts = try container.loadAccounts().accounts
        let persistedManagedLive = try #require(accounts.first(where: { $0.id == existingManagedLive.id }))
        #expect(swapper.swapCallCount == 0)
        #expect(try container.managedAuthData(for: persistedManagedLive) != originalManagedAuthData)
        #expect(try container.managedAuthData(for: persistedManagedLive) == liveAuthData)
        #expect(persistedManagedLive.lastAuthenticatedAt == existingManagedLive.lastAuthenticatedAt)
    }

    @Test
    func `already managed refresh resolves workspace metadata from live auth home`() async throws {
        let container = try CodexAccountPromotionTestContainer(
            suiteName: "CodexAccountPromotionServiceTests-already-managed-workspace-refresh")
        defer { container.tearDown() }

        let target = try container.createManagedAccount(
            persistedEmail: "beta@example.com",
            authAccountID: "acct-beta")
        let existingManagedLive = try container.createManagedAccount(
            persistedEmail: "alpha@example.com",
            authAccountID: "acct-alpha",
            workspaceLabel: "Stale",
            workspaceAccountID: "acct-alpha")
        try container.persistAccounts([target, existingManagedLive])
        _ = try container.writeLiveOAuthAuthFile(email: "alpha@example.com", accountID: "acct-alpha")

        let service = CodexAccountPromotionService(
            store: container.fileStore,
            homeFactory: container.homeFactory,
            identityReader: container.identityReader,
            workspaceResolver: HomePathWorkspaceResolver(
                byHomePath: [
                    container.liveHomeURL.path: CodexOpenAIWorkspaceIdentity(
                        workspaceAccountID: "acct-alpha",
                        workspaceLabel: "Fresh"),
                    existingManagedLive.managedHomePath: CodexOpenAIWorkspaceIdentity(
                        workspaceAccountID: "acct-alpha",
                        workspaceLabel: "Stale"),
                ]),
            snapshotLoader: SettingsStoreCodexAccountReconciliationSnapshotLoader(settingsStore: container.settings),
            authMaterialReader: DefaultCodexAuthMaterialReader(),
            liveAuthSwapper: DefaultCodexLiveAuthSwapper(),
            activeSourceWriter: SettingsStoreCodexActiveSourceWriter(settingsStore: container.settings),
            accountScopedRefresher: UsageStoreCodexAccountScopedRefresher(usageStore: container.usageStore),
            baseEnvironment: container.baseEnvironment,
            fileManager: .default)

        let result = try await service.promoteManagedAccount(id: target.id)
        let accounts = try container.loadAccounts().accounts
        let refreshedManagedLive = try #require(accounts.first(where: { $0.id == existingManagedLive.id }))

        #expect(result.displacedLiveDisposition == .alreadyManaged(managedAccountID: existingManagedLive.id))
        #expect(refreshedManagedLive.workspaceLabel == "Fresh")
        #expect(refreshedManagedLive.workspaceAccountID == "acct-alpha")
    }

    @Test
    func `live swap failure keeps preserved displaced live import in the managed store`() async throws {
        let container = try CodexAccountPromotionTestContainer(
            suiteName: "CodexAccountPromotionServiceTests-swap-failure")
        defer { container.tearDown() }

        let target = try container.createManagedAccount(
            persistedEmail: "beta@example.com",
            authAccountID: "acct-beta")
        try container.persistAccounts([target])
        container.settings.codexActiveSource = .managedAccount(id: target.id)
        let liveAuthData = try container.writeLiveOAuthAuthFile(
            email: "alpha@example.com",
            accountID: "acct-alpha")
        let swapper = RecordingCodexLiveAuthSwapper { _, _ in
            throw PromotionTestError.swapFailed
        }

        await #expect(throws: CodexAccountPromotionError.liveAuthSwapFailed) {
            try await container.makeService(liveAuthSwapper: swapper).promoteManagedAccount(id: target.id)
        }

        let accounts = try container.loadAccounts().accounts
        let imported = try #require(accounts.first(where: { $0.id != target.id }))
        #expect(accounts.count == 2)
        #expect(imported.authFingerprint == CodexAuthFingerprint.fingerprint(data: liveAuthData))
        #expect(try container.managedAuthData(for: imported) == liveAuthData)
        #expect(try container.liveAuthData() == liveAuthData)
        #expect(container.settings.codexActiveSource == .managedAccount(id: target.id))
    }

    @Test
    func `target managed auth without email is rejected before swap`() async throws {
        let container = try CodexAccountPromotionTestContainer(
            suiteName: "CodexAccountPromotionServiceTests-target-auth-missing-email")
        defer { container.tearDown() }

        let target = try container.createManagedAccount(
            persistedEmail: "alpha@example.com",
            authAccountID: "acct-alpha")
        try container.persistAccounts([target])
        container.settings.codexActiveSource = .managedAccount(id: target.id)
        _ = try container.writeManagedOAuthAuthFileWithoutEmail(for: target, accountID: "acct-alpha")
        let swapper = RecordingCodexLiveAuthSwapper()

        await #expect(throws: CodexAccountPromotionError.targetManagedAccountAuthUnreadable) {
            try await container.makeService(liveAuthSwapper: swapper).promoteManagedAccount(id: target.id)
        }

        #expect(swapper.swapCallCount == 0)
        #expect(try container.liveAuthData() == nil)
        #expect(try container.loadAccounts().accounts.count == 1)
        #expect(container.settings.codexActiveSource == .managedAccount(id: target.id))
    }

    @Test
    func `provider account collision after stale managed home repairs existing record to preserved auth`()
        async throws
    {
        let container = try CodexAccountPromotionTestContainer(
            suiteName: "CodexAccountPromotionServiceTests-stale-home-collision")
        defer { container.tearDown() }

        let target = try container.createManagedAccount(
            persistedEmail: "beta@example.com",
            authAccountID: "acct-beta")
        let existingID = UUID()
        let staleHomeURL = container.managedHomesURL.appendingPathComponent(existingID.uuidString, isDirectory: true)
        try FileManager.default.createDirectory(at: staleHomeURL, withIntermediateDirectories: true)
        let staleManaged = ManagedCodexAccount(
            id: existingID,
            email: "alpha@example.com",
            providerAccountID: "acct-alpha",
            workspaceLabel: "Personal",
            workspaceAccountID: "acct-alpha",
            managedHomePath: staleHomeURL.path,
            createdAt: 1,
            updatedAt: 1,
            lastAuthenticatedAt: 1)
        try container.persistAccounts([target, staleManaged])
        _ = try container.writeLiveOAuthAuthFile(email: "alpha@example.com", accountID: "acct-alpha")
        let liveAuthData = try #require(try container.liveAuthData())

        let result = try await container.makeService().promoteManagedAccount(id: target.id)
        let accounts = try container.loadAccounts().accounts
        let repairedManaged = try #require(accounts.first(where: { $0.id == staleManaged.id }))

        #expect(result.displacedLiveDisposition == .alreadyManaged(managedAccountID: staleManaged.id))
        #expect(accounts.count == 2)
        #expect(repairedManaged.managedHomePath == staleHomeURL.path)
        #expect(repairedManaged.authFingerprint == CodexAuthFingerprint.fingerprint(data: liveAuthData))
        #expect(try container.managedAuthData(for: repairedManaged) == liveAuthData)
        #expect(try container.managedHomeURLs().count == 2)
    }

    @Test
    func `legacy email only managed account upgrades to provider identity during promotion`() async throws {
        let container = try CodexAccountPromotionTestContainer(
            suiteName: "CodexAccountPromotionServiceTests-legacy-email-upgrade",
            workspaceIdentities: [
                "acct-alpha": CodexOpenAIWorkspaceIdentity(
                    workspaceAccountID: "acct-alpha",
                    workspaceLabel: "Personal"),
            ])
        defer { container.tearDown() }

        let target = try container.createManagedAccount(
            persistedEmail: "beta@example.com",
            authAccountID: "acct-beta")
        let legacyID = UUID()
        let legacyHomeURL = container.managedHomesURL.appendingPathComponent(legacyID.uuidString, isDirectory: true)
        try FileManager.default.createDirectory(at: legacyHomeURL, withIntermediateDirectories: true)
        let legacyManaged = ManagedCodexAccount(
            id: legacyID,
            email: "alpha@example.com",
            providerAccountID: nil,
            workspaceLabel: nil,
            workspaceAccountID: nil,
            managedHomePath: legacyHomeURL.path,
            createdAt: 1,
            updatedAt: 1,
            lastAuthenticatedAt: nil)
        try container.persistAccounts([target, legacyManaged])
        let liveAuthData = try container.writeLiveOAuthAuthFile(email: "alpha@example.com", accountID: "acct-alpha")

        let result = try await container.makeService().promoteManagedAccount(id: target.id)
        let accounts = try container.loadAccounts().accounts
        let upgradedManaged = try #require(accounts.first(where: { $0.id == legacyManaged.id }))

        #expect(result.displacedLiveDisposition == .alreadyManaged(managedAccountID: legacyManaged.id))
        #expect(accounts.count == 2)
        #expect(upgradedManaged.providerAccountID == "acct-alpha")
        #expect(upgradedManaged.workspaceLabel == "Personal")
        #expect(upgradedManaged.workspaceAccountID == "acct-alpha")
        #expect(try container.managedAuthData(for: upgradedManaged) == liveAuthData)
    }

    @Test
    func `unsafe managed home refresh is rejected before auth rewrite`() async throws {
        let container = try CodexAccountPromotionTestContainer(
            suiteName: "CodexAccountPromotionServiceTests-unsafe-refresh-home")
        defer { container.tearDown() }

        let target = try container.createManagedAccount(
            persistedEmail: "beta@example.com",
            authAccountID: "acct-beta")
        let unsafeManagedLive = ManagedCodexAccount(
            id: UUID(),
            email: "alpha@example.com",
            providerAccountID: "acct-alpha",
            workspaceLabel: nil,
            workspaceAccountID: "acct-alpha",
            managedHomePath: container.rootURL.appendingPathComponent("unsafe-home", isDirectory: true).path,
            createdAt: 1,
            updatedAt: 1,
            lastAuthenticatedAt: 1)
        try container.persistAccounts([target, unsafeManagedLive])
        let liveAuthData = try container.writeLiveOAuthAuthFile(
            email: "alpha@example.com",
            accountID: "acct-alpha")

        await #expect(throws: CodexAccountPromotionError.displacedLiveImportFailed) {
            try await container.makeService().promoteManagedAccount(id: target.id)
        }

        let accounts = try container.loadAccounts().accounts
        #expect(try container.liveAuthData() == liveAuthData)
        #expect(FileManager.default.fileExists(atPath: unsafeManagedLive.managedHomePath) == false)
        #expect(accounts.count == 2)
        #expect(accounts.contains(where: { $0.id == target.id }))
        #expect(accounts.contains(where: { $0.id == unsafeManagedLive.id }))
    }

    @Test
    func `promotion rejects conflicting readable managed home before overwriting auth`() async throws {
        let container = try CodexAccountPromotionTestContainer(
            suiteName: "CodexAccountPromotionServiceTests-conflicting-readable-home")
        defer { container.tearDown() }

        let target = try container.createManagedAccount(
            persistedEmail: "beta@example.com",
            authAccountID: "acct-beta")
        let conflictingManaged = try container.createManagedAccount(
            persistedEmail: "alpha@example.com",
            authEmail: "gamma@example.com",
            authAccountID: "acct-gamma",
            persistedProviderAccountID: "acct-alpha",
            useAuthAccountIDAsPersistedProviderAccountID: false)
        try container.persistAccounts([target, conflictingManaged])
        let liveAuthData = try container.writeLiveOAuthAuthFile(email: "alpha@example.com", accountID: "acct-alpha")
        let conflictingAuthData = try container.managedAuthData(for: conflictingManaged)

        await #expect(throws: CodexAccountPromotionError.displacedLiveManagedAccountConflict) {
            try await container.makeService().promoteManagedAccount(id: target.id)
        }

        let accounts = try container.loadAccounts().accounts
        let persistedConflict = try #require(accounts.first(where: { $0.id == conflictingManaged.id }))
        #expect(try container.liveAuthData() == liveAuthData)
        #expect(try container.managedAuthData(for: persistedConflict) == conflictingAuthData)
        #expect(accounts.count == 2)
    }

    @Test
    func `api key only live auth is rejected fail closed`() async throws {
        let container = try CodexAccountPromotionTestContainer(
            suiteName: "CodexAccountPromotionServiceTests-api-key-only")
        defer { container.tearDown() }

        let target = try container.createManagedAccount(
            persistedEmail: "beta@example.com",
            authAccountID: "acct-beta")
        try container.persistAccounts([target])
        let liveAuthData = try container.writeLiveAPIKeyAuthFile()
        let snapshotLoader =
            StaticCodexAccountReconciliationSnapshotLoader(snapshot: CodexAccountReconciliationSnapshot(
                storedAccounts: [target],
                activeStoredAccount: nil,
                liveSystemAccount: nil,
                matchingStoredAccountForLiveSystemAccount: nil,
                activeSource: .liveSystem,
                hasUnreadableAddedAccountStore: false))

        await #expect(throws: CodexAccountPromotionError.liveAccountAPIKeyOnlyUnsupported) {
            try await container.makeService(snapshotLoader: snapshotLoader).promoteManagedAccount(id: target.id)
        }

        #expect(try container.liveAuthData() == liveAuthData)
        #expect(try container.loadAccounts().accounts.count == 1)
        #expect(try container.loadAccounts().accounts.first?.id == target.id)
    }

    @Test
    func `post promotion refresh re-resolves codex state from the new live auth`() async throws {
        let container = try CodexAccountPromotionTestContainer(
            suiteName: "CodexAccountPromotionServiceTests-state-reresolution")
        defer { container.tearDown() }

        let target = try container.createManagedAccount(
            persistedEmail: "beta@example.com",
            authAccountID: "acct-beta")
        try container.persistAccounts([target])
        let snapshotLoader =
            StaticCodexAccountReconciliationSnapshotLoader(snapshot: CodexAccountReconciliationSnapshot(
                storedAccounts: [target],
                activeStoredAccount: nil,
                liveSystemAccount: nil,
                matchingStoredAccountForLiveSystemAccount: nil,
                activeSource: .liveSystem,
                hasUnreadableAddedAccountStore: false))
        _ = try container.writeLiveOAuthAuthFile(email: "alpha@example.com", accountID: "acct-alpha")
        container.seedScopedRefreshState(
            email: "alpha@example.com",
            identity: .providerAccount(id: "acct-alpha"))
        let refresher = ClosureCodexAccountScopedRefresher { _ in
            let snapshot = UsageSnapshot(
                primary: RateWindow(usedPercent: 12, windowMinutes: 300, resetsAt: nil, resetDescription: nil),
                secondary: nil,
                updatedAt: Date(),
                identity: ProviderIdentitySnapshot(
                    providerID: .codex,
                    accountEmail: "beta@example.com",
                    accountOrganization: nil,
                    loginMethod: "Pro"))
            let credits = CreditsSnapshot(remaining: 17, events: [], updatedAt: Date())
            container.usageStore._setSnapshotForTesting(snapshot, provider: .codex)
            container.usageStore.credits = credits
            container.usageStore.lastCreditsSnapshot = credits
            container.usageStore.lastCreditsSnapshotAccountKey = "beta@example.com"
            container.usageStore.lastCreditsSource = .api
            container.usageStore.lastCodexAccountScopedRefreshGuard = CodexAccountScopedRefreshGuard(
                source: .liveSystem,
                identity: .providerAccount(id: "acct-beta"),
                accountKey: "beta@example.com")
        }

        let result = try await container.makeService(
            snapshotLoader: snapshotLoader,
            accountScopedRefresher: refresher)
            .promoteManagedAccount(id: target.id)

        #expect(result.outcome == .promoted)
        #expect(container.usageStore.snapshots[.codex]?.accountEmail(for: .codex) == "beta@example.com")
        #expect(container.usageStore.lastCreditsSnapshotAccountKey == "beta@example.com")
        #expect(container.usageStore.lastCodexAccountScopedRefreshGuard?.identity == .providerAccount(id: "acct-beta"))
        #expect(container.usageStore.lastCodexAccountScopedRefreshGuard?.accountKey == "beta@example.com")
    }
}

private struct HomePathWorkspaceResolver: ManagedCodexWorkspaceResolving {
    let byHomePath: [String: CodexOpenAIWorkspaceIdentity]

    func resolveWorkspaceIdentity(
        homePath: String,
        providerAccountID _: String) async -> CodexOpenAIWorkspaceIdentity?
    {
        self.byHomePath[homePath]
    }
}
