import CodexBarCore
import Foundation
import Testing
@testable import CodexBar

@MainActor
struct CodexAccountsSettingsSectionTests {
    @Test
    func `codex accounts section shows live badge only for live only multi account row`() throws {
        let settings = Self.makeSettingsStore(suite: "CodexAccountsSettingsSectionTests-live-badge")
        let store = Self.makeUsageStore(settings: settings)
        let managedStoreURL = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
        defer { try? FileManager.default.removeItem(at: managedStoreURL) }

        let managedAccount = ManagedCodexAccount(
            id: UUID(),
            email: "managed@example.com",
            managedHomePath: "/tmp/managed",
            createdAt: 1,
            updatedAt: 2,
            lastAuthenticatedAt: 2)
        let managedStore = FileManagedCodexAccountStore(fileURL: managedStoreURL)
        try managedStore.storeAccounts(ManagedCodexAccountSet(
            version: FileManagedCodexAccountStore.currentVersion,
            accounts: [managedAccount]))

        settings._test_managedCodexAccountStoreURL = managedStoreURL
        settings._test_liveSystemCodexAccount = ObservedSystemCodexAccount(
            email: "live@example.com",
            codexHomePath: "/Users/test/.codex",
            observedAt: Date())

        let pane = ProvidersPane(settings: settings, store: store)
        let state = try #require(pane._test_codexAccountsSectionState())
        let liveAccount = try #require(state.visibleAccounts.first { $0.email == "live@example.com" })
        let managedVisibleAccount = try #require(state.visibleAccounts.first { $0.email == "managed@example.com" })

        #expect(state.showsLiveBadge(for: liveAccount))
        #expect(state.showsLiveBadge(for: managedVisibleAccount) == false)
    }

    @Test
    func `single account codex settings uses simple account view instead of picker`() throws {
        let settings = Self.makeSettingsStore(suite: "CodexAccountsSettingsSectionTests-single-account")
        let store = Self.makeUsageStore(settings: settings)
        settings._test_liveSystemCodexAccount = ObservedSystemCodexAccount(
            email: "solo@example.com",
            codexHomePath: "/Users/test/.codex",
            observedAt: Date())

        let pane = ProvidersPane(settings: settings, store: store)
        let state = try #require(pane._test_codexAccountsSectionState())

        #expect(state.visibleAccounts.count == 1)
        #expect(state.showsActivePicker == false)
        #expect(state.singleVisibleAccount?.email == "solo@example.com")
    }

    @Test
    func `single account codex settings state includes workspace display name`() throws {
        let settings = Self.makeSettingsStore(suite: "CodexAccountsSettingsSectionTests-single-workspace")
        let store = Self.makeUsageStore(settings: settings)
        settings._test_liveSystemCodexAccount = ObservedSystemCodexAccount(
            email: "solo@example.com",
            workspaceLabel: "Team Alpha",
            workspaceAccountID: "account-live",
            codexHomePath: "/Users/test/.codex",
            observedAt: Date(),
            identity: .providerAccount(id: "account-live"))

        let pane = ProvidersPane(settings: settings, store: store)
        let state = try #require(pane._test_codexAccountsSectionState())

        #expect(state.singleVisibleAccount?.displayName == "solo@example.com — Team Alpha")
    }

    @Test
    func `codex accounts section disables managed mutations when store is unreadable`() throws {
        let settings = Self.makeSettingsStore(suite: "CodexAccountsSettingsSectionTests-unreadable")
        let store = Self.makeUsageStore(settings: settings)
        settings._test_liveSystemCodexAccount = ObservedSystemCodexAccount(
            email: "live@example.com",
            codexHomePath: "/Users/test/.codex",
            observedAt: Date())
        settings._test_unreadableManagedCodexAccountStore = true
        defer { settings._test_unreadableManagedCodexAccountStore = false }

        let pane = ProvidersPane(settings: settings, store: store)
        let state = try #require(pane._test_codexAccountsSectionState())
        let liveAccount = try #require(state.visibleAccounts.first)

        #expect(state.hasUnreadableManagedAccountStore)
        #expect(state.canAddAccount == false)
        #expect(state.notice?.tone == .warning)
        #expect(state.canReauthenticate(liveAccount))
    }

    @Test
    func `selecting merged visible account from settings keeps live system source`() async throws {
        let settings = Self.makeSettingsStore(suite: "CodexAccountsSettingsSectionTests-select-merged")
        let store = Self.makeUsageStore(settings: settings)
        let managedStoreURL = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
        defer { try? FileManager.default.removeItem(at: managedStoreURL) }

        let managedAccount = ManagedCodexAccount(
            id: UUID(),
            email: "same@example.com",
            managedHomePath: "/tmp/managed",
            createdAt: 1,
            updatedAt: 2,
            lastAuthenticatedAt: 2)
        let managedStore = FileManagedCodexAccountStore(fileURL: managedStoreURL)
        try managedStore.storeAccounts(ManagedCodexAccountSet(
            version: FileManagedCodexAccountStore.currentVersion,
            accounts: [managedAccount]))

        settings._test_managedCodexAccountStoreURL = managedStoreURL
        settings._test_liveSystemCodexAccount = ObservedSystemCodexAccount(
            email: "SAME@example.com",
            codexHomePath: "/Users/test/.codex",
            observedAt: Date())
        settings.codexActiveSource = .managedAccount(id: managedAccount.id)

        let pane = ProvidersPane(settings: settings, store: store)
        await pane._test_selectCodexVisibleAccount(id: "same@example.com")

        #expect(settings.codexActiveSource == .liveSystem)
        let state = try #require(pane._test_codexAccountsSectionState())
        #expect(state.activeVisibleAccountID == "same@example.com")
    }

    @Test
    func `settings account selection can target the managed row when same email rows split by identity`() async throws {
        let settings = Self.makeSettingsStore(suite: "CodexAccountsSettingsSectionTests-select-split")
        let store = Self.makeUsageStore(settings: settings)
        let managedStoreURL = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
        let managedHome = FileManager.default.temporaryDirectory.appendingPathComponent(
            UUID().uuidString,
            isDirectory: true)
        defer {
            settings._test_managedCodexAccountStoreURL = nil
            settings._test_liveSystemCodexAccount = nil
            try? FileManager.default.removeItem(at: managedStoreURL)
            try? FileManager.default.removeItem(at: managedHome)
        }

        try Self.writeCodexAuthFile(
            homeURL: managedHome,
            email: "same@example.com",
            plan: "pro",
            accountID: "account-managed")
        let managedAccount = ManagedCodexAccount(
            id: UUID(),
            email: "same@example.com",
            managedHomePath: managedHome.path,
            createdAt: 1,
            updatedAt: 2,
            lastAuthenticatedAt: 2)
        let managedStore = FileManagedCodexAccountStore(fileURL: managedStoreURL)
        try managedStore.storeAccounts(ManagedCodexAccountSet(
            version: FileManagedCodexAccountStore.currentVersion,
            accounts: [managedAccount]))

        settings._test_managedCodexAccountStoreURL = managedStoreURL
        settings._test_liveSystemCodexAccount = ObservedSystemCodexAccount(
            email: "SAME@example.com",
            codexHomePath: "/Users/test/.codex",
            observedAt: Date(),
            identity: .emailOnly(normalizedEmail: "same@example.com"))
        settings.codexActiveSource = .liveSystem

        let pane = ProvidersPane(settings: settings, store: store)
        let initialState = try #require(pane._test_codexAccountsSectionState())
        let managedVisibleAccount = try #require(initialState.visibleAccounts
            .first { $0.storedAccountID == managedAccount.id })

        await pane._test_selectCodexVisibleAccount(id: managedVisibleAccount.id)

        #expect(settings.codexActiveSource == .managedAccount(id: managedAccount.id))
        let updatedState = try #require(pane._test_codexAccountsSectionState())
        #expect(updatedState.activeVisibleAccountID == managedVisibleAccount.id)
    }

    @Test
    func `codex accounts section disables add and reauth while managed authentication is in flight`() async throws {
        let settings = Self.makeSettingsStore(suite: "CodexAccountsSettingsSectionTests-in-flight")
        let store = Self.makeUsageStore(settings: settings)
        let managedStoreURL = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
        defer { try? FileManager.default.removeItem(at: managedStoreURL) }

        let managedAccountID = try #require(UUID(uuidString: "AAAAAAAA-BBBB-CCCC-DDDD-111111111111"))
        let managedAccount = ManagedCodexAccount(
            id: managedAccountID,
            email: "managed@example.com",
            managedHomePath: "/tmp/managed",
            createdAt: 1,
            updatedAt: 2,
            lastAuthenticatedAt: 2)
        let managedStore = FileManagedCodexAccountStore(fileURL: managedStoreURL)
        try managedStore.storeAccounts(ManagedCodexAccountSet(
            version: FileManagedCodexAccountStore.currentVersion,
            accounts: [managedAccount]))
        settings._test_managedCodexAccountStoreURL = managedStoreURL

        let root = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString, isDirectory: true)
        defer { try? FileManager.default.removeItem(at: root) }
        let runner = BlockingManagedCodexLoginRunnerForSettingsSectionTests()
        let service = ManagedCodexAccountService(
            store: managedStore,
            homeFactory: TestManagedCodexHomeFactoryForSettingsSectionTests(root: root),
            loginRunner: runner,
            identityReader: StubManagedCodexIdentityReaderForSettingsSectionTests(emails: ["managed@example.com"]))
        let coordinator = ManagedCodexAccountCoordinator(service: service)
        let authTask = Task { try await coordinator.authenticateManagedAccount() }
        await runner.waitUntilStarted()

        let pane = ProvidersPane(
            settings: settings,
            store: store,
            managedCodexAccountCoordinator: coordinator)
        let state = try #require(pane._test_codexAccountsSectionState())
        let visibleAccount = try #require(state.visibleAccounts.first { $0.email == "managed@example.com" })

        #expect(state.canAddAccount == false)
        #expect(state.addAccountTitle == "Adding Account…")
        #expect(state.canReauthenticate(visibleAccount) == false)

        await runner.resume()
        _ = try await authTask.value
    }

    @Test
    func `adding managed codex account auto selects the merged live row`() async throws {
        let settings = Self.makeSettingsStore(suite: "CodexAccountsSettingsSectionTests-add-merged")
        let store = Self.makeUsageStore(settings: settings)
        settings._test_liveSystemCodexAccount = ObservedSystemCodexAccount(
            email: "same@example.com",
            codexHomePath: "/Users/test/.codex",
            observedAt: Date())

        let coordinator = Self.makeManagedCoordinator(settings: settings, email: "same@example.com")
        let pane = ProvidersPane(
            settings: settings,
            store: store,
            managedCodexAccountCoordinator: coordinator)

        await pane._test_addManagedCodexAccount()

        #expect(settings.codexActiveSource == .liveSystem)
        let state = try #require(pane._test_codexAccountsSectionState())
        #expect(state.activeVisibleAccountID == "same@example.com")
    }

    @Test
    func `adding managed codex account selects the new managed account when email differs`() async throws {
        let settings = Self.makeSettingsStore(suite: "CodexAccountsSettingsSectionTests-add-managed")
        let store = Self.makeUsageStore(settings: settings)
        settings._test_liveSystemCodexAccount = ObservedSystemCodexAccount(
            email: "live@example.com",
            codexHomePath: "/Users/test/.codex",
            observedAt: Date())

        let coordinator = Self.makeManagedCoordinator(settings: settings, email: "managed@example.com")
        let pane = ProvidersPane(
            settings: settings,
            store: store,
            managedCodexAccountCoordinator: coordinator)

        await pane._test_addManagedCodexAccount()

        guard case .managedAccount = settings.codexActiveSource else {
            Issue.record("Expected the new managed account to become active")
            return
        }
        let state = try #require(pane._test_codexAccountsSectionState())
        #expect(state.activeVisibleAccountID == "managed@example.com")
    }

    @Test
    func `managed codex login failure message includes codex login output`() {
        let error = ManagedCodexAccountServiceError.loginFailed(CodexLoginRunner.Result(
            outcome: .failed(status: 2),
            output: "Browser selected the existing ChatGPT account"))

        #expect(error.userFacingMessage.contains("codex --version"))
        #expect(error.userFacingMessage.contains("codex login output:"))
        #expect(error.userFacingMessage.contains("Browser selected the existing ChatGPT account"))
    }

    private static func makeManagedCoordinator(
        settings: SettingsStore,
        email: String)
        -> ManagedCodexAccountCoordinator
    {
        let storeURL = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
        let store = FileManagedCodexAccountStore(fileURL: storeURL)
        let root = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString, isDirectory: true)
        settings._test_managedCodexAccountStoreURL = storeURL
        let service = ManagedCodexAccountService(
            store: store,
            homeFactory: TestManagedCodexHomeFactoryForSettingsSectionTests(root: root),
            loginRunner: StubManagedCodexLoginRunnerForSettingsSectionTests.success,
            identityReader: StubManagedCodexIdentityReaderForSettingsSectionTests(emails: [email]))
        return ManagedCodexAccountCoordinator(service: service)
    }

    private static func makeSettingsStore(suite: String) -> SettingsStore {
        let defaults = UserDefaults(suiteName: suite)!
        defaults.removePersistentDomain(forName: suite)
        let configStore = testConfigStore(suiteName: suite)

        return SettingsStore(
            userDefaults: defaults,
            configStore: configStore,
            zaiTokenStore: NoopZaiTokenStore(),
            syntheticTokenStore: NoopSyntheticTokenStore(),
            codexCookieStore: InMemoryCookieHeaderStore(),
            claudeCookieStore: InMemoryCookieHeaderStore(),
            cursorCookieStore: InMemoryCookieHeaderStore(),
            opencodeCookieStore: InMemoryCookieHeaderStore(),
            factoryCookieStore: InMemoryCookieHeaderStore(),
            minimaxCookieStore: InMemoryMiniMaxCookieStore(),
            minimaxAPITokenStore: InMemoryMiniMaxAPITokenStore(),
            kimiTokenStore: InMemoryKimiTokenStore(),
            augmentCookieStore: InMemoryCookieHeaderStore(),
            ampCookieStore: InMemoryCookieHeaderStore(),
            copilotTokenStore: InMemoryCopilotTokenStore(),
            tokenAccountStore: InMemoryTokenAccountStore())
    }

    private static func makeUsageStore(settings: SettingsStore) -> UsageStore {
        let store = UsageStore(
            fetcher: UsageFetcher(environment: [:]),
            browserDetection: BrowserDetection(cacheTTL: 0),
            settings: settings,
            startupBehavior: .testing)
        // Account-selection tests must never trigger a real provider refresh.
        store._test_providerRefreshOverride = { _ in }
        store._test_codexCreditsLoaderOverride = { throw UsageError.noRateLimitsFound }
        return store
    }
}

extension CodexAccountsSettingsSectionTests {
    private static func writeCodexAuthFile(
        homeURL: URL,
        email: String,
        plan: String,
        accountID: String? = nil) throws
    {
        try FileManager.default.createDirectory(at: homeURL, withIntermediateDirectories: true)
        var tokens: [String: Any] = [
            "accessToken": "access-token",
            "refreshToken": "refresh-token",
            "idToken": Self.fakeJWT(email: email, plan: plan, accountID: accountID),
        ]
        if let accountID {
            tokens["account_id"] = accountID
        }
        let auth = ["tokens": tokens]
        let data = try JSONSerialization.data(withJSONObject: auth)
        try data.write(to: homeURL.appendingPathComponent("auth.json"))
    }

    private static func fakeJWT(email: String, plan: String, accountID: String? = nil) -> String {
        let header = (try? JSONSerialization.data(withJSONObject: ["alg": "none"])) ?? Data()
        var payloadObject: [String: Any] = [
            "email": email,
            "chatgpt_plan_type": plan,
        ]
        if let accountID {
            payloadObject["https://api.openai.com/auth"] = [
                "chatgpt_account_id": accountID,
            ]
        }
        let payload = (try? JSONSerialization.data(withJSONObject: payloadObject)) ?? Data()

        func base64URL(_ data: Data) -> String {
            data.base64EncodedString()
                .replacingOccurrences(of: "=", with: "")
                .replacingOccurrences(of: "+", with: "-")
                .replacingOccurrences(of: "/", with: "_")
        }

        return "\(base64URL(header)).\(base64URL(payload))."
    }
}

private struct TestManagedCodexHomeFactoryForSettingsSectionTests: ManagedCodexHomeProducing {
    let root: URL
    private let nextID = UUID().uuidString

    func makeHomeURL() -> URL {
        self.root.appendingPathComponent(self.nextID, isDirectory: true)
    }

    func validateManagedHomeForDeletion(_ url: URL) throws {
        try ManagedCodexHomeFactory(root: self.root).validateManagedHomeForDeletion(url)
    }
}

private struct StubManagedCodexLoginRunnerForSettingsSectionTests: ManagedCodexLoginRunning {
    let result: CodexLoginRunner.Result

    func run(homePath _: String, timeout _: TimeInterval) async -> CodexLoginRunner.Result {
        self.result
    }

    static let success = StubManagedCodexLoginRunnerForSettingsSectionTests(
        result: CodexLoginRunner.Result(outcome: .success, output: "ok"))
}

private actor BlockingManagedCodexLoginRunnerForSettingsSectionTests: ManagedCodexLoginRunning {
    private var waiters: [CheckedContinuation<CodexLoginRunner.Result, Never>] = []
    private var startedWaiters: [CheckedContinuation<Void, Never>] = []
    private var didStart = false

    func run(homePath _: String, timeout _: TimeInterval) async -> CodexLoginRunner.Result {
        self.didStart = true
        self.startedWaiters.forEach { $0.resume() }
        self.startedWaiters.removeAll()
        return await withCheckedContinuation { continuation in
            self.waiters.append(continuation)
        }
    }

    func waitUntilStarted() async {
        if self.didStart { return }
        await withCheckedContinuation { continuation in
            self.startedWaiters.append(continuation)
        }
    }

    func resume() {
        let result = CodexLoginRunner.Result(outcome: .success, output: "ok")
        self.waiters.forEach { $0.resume(returning: result) }
        self.waiters.removeAll()
    }
}

private final class StubManagedCodexIdentityReaderForSettingsSectionTests: ManagedCodexIdentityReading,
@unchecked Sendable {
    private var emails: [String]

    init(emails: [String]) {
        self.emails = emails
    }

    func loadAccountIdentity(homePath _: String) throws -> CodexAuthBackedAccount {
        let email = self.emails.isEmpty ? nil : self.emails.removeFirst()
        return CodexAuthBackedAccount(
            identity: CodexIdentityResolver.resolve(accountId: nil, email: email),
            email: email,
            plan: "Pro")
    }
}
