import AppKit
import CodexBarCore
import Foundation
import Testing
@testable import CodexBar

@Suite(.serialized)
@MainActor
struct StatusMenuCodexSwitcherPresentationTests {
    private func disableMenuCardsForTesting() {
        StatusItemController.menuCardRenderingEnabled = false
        StatusItemController.setMenuRefreshEnabledForTesting(false)
    }

    private func makeSettings() -> SettingsStore {
        let suite = "StatusMenuCodexSwitcherPresentationTests-\(UUID().uuidString)"
        let defaults = UserDefaults(suiteName: suite)!
        defaults.removePersistentDomain(forName: suite)
        return SettingsStore(
            userDefaults: defaults,
            configStore: testConfigStore(suiteName: suite),
            zaiTokenStore: NoopZaiTokenStore(),
            syntheticTokenStore: NoopSyntheticTokenStore())
    }

    private func makeManagedAccountStoreURL(accounts: [ManagedCodexAccount]) throws -> URL {
        let storeURL = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
        let store = FileManagedCodexAccountStore(fileURL: storeURL)
        try store.storeAccounts(ManagedCodexAccountSet(
            version: FileManagedCodexAccountStore.currentVersion,
            accounts: accounts))
        return storeURL
    }

    private func enableOnlyCodex(_ settings: SettingsStore) {
        let registry = ProviderRegistry.shared
        for provider in UsageProvider.allCases {
            guard let metadata = registry.metadata[provider] else { continue }
            settings.setProviderEnabled(provider: provider, metadata: metadata, enabled: provider == .codex)
        }
    }

    private func representedIDs(in menu: NSMenu) -> [String] {
        menu.items.compactMap { $0.representedObject as? String }
    }

    private func snapshot(email: String, percent: Double = 12) -> UsageSnapshot {
        UsageSnapshot(
            primary: RateWindow(
                usedPercent: percent,
                windowMinutes: 300,
                resetsAt: Date().addingTimeInterval(300),
                resetDescription: nil),
            secondary: RateWindow(
                usedPercent: percent,
                windowMinutes: 10080,
                resetsAt: Date().addingTimeInterval(86400),
                resetDescription: nil),
            updatedAt: Date(),
            identity: ProviderIdentitySnapshot(
                providerID: .codex,
                accountEmail: email,
                accountOrganization: nil,
                loginMethod: "Plus"))
    }

    private func removeAccountIdentity(fromSnapshotStoreAt fileURL: URL) throws {
        var payload = try #require(JSONSerialization.jsonObject(with: Data(contentsOf: fileURL)) as? [String: Any])
        var records = try #require(payload["records"] as? [[String: Any]])
        records[0].removeValue(forKey: "accountIdentity")
        payload["records"] = records
        try JSONSerialization.data(withJSONObject: payload).write(to: fileURL)
    }

    @Test
    func `codex account ordering keeps workspace groups contiguous`() {
        let teamActive = CodexVisibleAccount(
            id: "team-a-active",
            email: "active@example.com",
            workspaceLabel: "Team A",
            workspaceAccountID: "team-a",
            storedAccountID: UUID(),
            selectionSource: .managedAccount(id: UUID()),
            isActive: true,
            isLive: false,
            canReauthenticate: true,
            canRemove: true)
        let teamHighQuota = CodexVisibleAccount(
            id: "team-b-high-quota",
            email: "high@example.com",
            workspaceLabel: "Team B",
            workspaceAccountID: "team-b",
            storedAccountID: UUID(),
            selectionSource: .managedAccount(id: UUID()),
            isActive: false,
            isLive: false,
            canReauthenticate: true,
            canRemove: true)
        let teamSibling = CodexVisibleAccount(
            id: "team-a-sibling",
            email: "sibling@example.com",
            workspaceLabel: "Team A",
            workspaceAccountID: "team-a",
            storedAccountID: UUID(),
            selectionSource: .managedAccount(id: UUID()),
            isActive: false,
            isLive: false,
            canReauthenticate: true,
            canRemove: true)
        let accounts = [teamActive, teamHighQuota, teamSibling]
        let snapshots = [
            CodexAccountUsageSnapshot(
                account: teamActive,
                snapshot: self.snapshot(email: teamActive.email, percent: 95),
                error: nil,
                sourceLabel: "test"),
            CodexAccountUsageSnapshot(
                account: teamHighQuota,
                snapshot: self.snapshot(email: teamHighQuota.email, percent: 10),
                error: nil,
                sourceLabel: "test"),
            CodexAccountUsageSnapshot(
                account: teamSibling,
                snapshot: self.snapshot(email: teamSibling.email, percent: 20),
                error: nil,
                sourceLabel: "test"),
        ]

        let ordered = CodexAccountPresentationOrdering.orderedAccounts(
            accounts,
            snapshots: snapshots,
            activeVisibleAccountID: teamActive.id)

        #expect(ordered.map(\.id) == ["team-a-active", "team-a-sibling", "team-b-high-quota"])
        #expect(ordered.codexWorkspaceSections().map(\.title) == ["Team A", "Team B"])
        #expect(ordered.codexWorkspaceSections().first?.accounts.map(\.id) == ["team-a-active", "team-a-sibling"])
    }

    @Test
    func `codex stacked menu orders by quota and groups workspaces`() throws {
        self.disableMenuCardsForTesting()
        let settings = self.makeSettings()
        settings.statusChecksEnabled = false
        settings.refreshFrequency = .manual
        settings.mergeIcons = false
        settings.multiAccountMenuLayout = .stacked
        self.enableOnlyCodex(settings)

        let lowID = try #require(UUID(uuidString: "AAAAAAAA-BBBB-CCCC-DDDD-111111111111"))
        let highID = try #require(UUID(uuidString: "AAAAAAAA-BBBB-CCCC-DDDD-222222222222"))
        let low = ManagedCodexAccount(
            id: lowID,
            email: "low@example.com",
            workspaceLabel: "Team Low",
            workspaceAccountID: "team-low",
            managedHomePath: "/tmp/low-home",
            createdAt: 1,
            updatedAt: 2,
            lastAuthenticatedAt: 2)
        let high = ManagedCodexAccount(
            id: highID,
            email: "high@example.com",
            workspaceLabel: "Team High",
            workspaceAccountID: "team-high",
            managedHomePath: "/tmp/high-home",
            createdAt: 1,
            updatedAt: 2,
            lastAuthenticatedAt: 2)
        let storeURL = try self.makeManagedAccountStoreURL(accounts: [low, high])
        defer {
            settings._test_managedCodexAccountStoreURL = nil
            settings._test_liveSystemCodexAccount = nil
            try? FileManager.default.removeItem(at: storeURL)
        }

        settings._test_managedCodexAccountStoreURL = storeURL
        settings._test_liveSystemCodexAccount = ObservedSystemCodexAccount(
            email: "active@example.com",
            workspaceLabel: "Personal",
            codexHomePath: "/Users/test/.codex",
            observedAt: Date())
        settings.codexActiveSource = .liveSystem

        let fetcher = UsageFetcher()
        let store = UsageStore(fetcher: fetcher, browserDetection: BrowserDetection(cacheTTL: 0), settings: settings)
        store.codexAccountSnapshots = settings.codexVisibleAccountProjection.visibleAccounts.map { account in
            let usedPercent = switch account.email {
            case "high@example.com":
                10.0
            case "low@example.com":
                80.0
            default:
                95.0
            }
            return CodexAccountUsageSnapshot(
                account: account,
                snapshot: self.snapshot(email: account.email, percent: usedPercent),
                error: nil,
                sourceLabel: "test")
        }
        let controller = StatusItemController(
            store: store,
            settings: settings,
            account: fetcher.loadAccountInfo(),
            updater: DisabledUpdaterController(),
            preferencesSelection: PreferencesSelection(),
            statusBar: .system)
        defer { controller.releaseStatusItemsForTesting() }

        let display = try #require(controller.codexAccountMenuDisplay(for: .codex))
        #expect(display.accounts.map(\.email) == ["active@example.com", "high@example.com", "low@example.com"])
        #expect(display.workspaceSections.map(\.title) == ["Personal", "Team High", "Team Low"])

        let menu = controller.makeMenu(for: .codex)
        controller.menuWillOpen(menu)
        #expect(self.representedIDs(in: menu).count(where: { $0.hasPrefix("codexWorkspace-") }) == 3)
    }

    @Test
    func `codex stacked menu surfaces account health labels`() throws {
        self.disableMenuCardsForTesting()
        let settings = self.makeSettings()
        settings.statusChecksEnabled = false
        settings.refreshFrequency = .manual
        settings.mergeIcons = false
        settings.multiAccountMenuLayout = .stacked
        self.enableOnlyCodex(settings)

        let managedAccountID = try #require(UUID(uuidString: "AAAAAAAA-BBBB-CCCC-DDDD-111111111111"))
        let managedAccount = ManagedCodexAccount(
            id: managedAccountID,
            email: "managed@example.com",
            managedHomePath: "/tmp/managed-home",
            createdAt: 1,
            updatedAt: 2,
            lastAuthenticatedAt: 2)
        let storeURL = try self.makeManagedAccountStoreURL(accounts: [managedAccount])
        defer {
            settings._test_managedCodexAccountStoreURL = nil
            settings._test_liveSystemCodexAccount = nil
            try? FileManager.default.removeItem(at: storeURL)
        }

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

        let visibleAccount = try #require(settings.codexVisibleAccountProjection.visibleAccounts
            .first { $0.email == "managed@example.com" })

        #expect(CodexAccountHealth.status(for: visibleAccount, error: "401 Unauthorized")
            .label == "Needs re-auth")
    }

    @Test
    func `codex account snapshot store hydrates current visible accounts`() {
        let fileURL = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
        defer { try? FileManager.default.removeItem(at: fileURL) }

        let account = CodexVisibleAccount(
            id: "active@example.com",
            email: "active@example.com",
            workspaceAccountID: "acct-active",
            storedAccountID: nil,
            selectionSource: .liveSystem,
            isActive: true,
            isLive: true,
            canReauthenticate: true,
            canRemove: false)
        let store = FileCodexAccountUsageSnapshotStore(fileURL: fileURL)
        store.store([
            CodexAccountUsageSnapshot(
                account: account,
                snapshot: self.snapshot(email: account.email, percent: 17),
                error: nil,
                sourceLabel: "test"),
        ])

        let hydrated = store.load(for: [account])

        #expect(hydrated.map(\.id) == [account.id])
        #expect(hydrated.first?.snapshot?.primary?.usedPercent == 17)
        #expect(hydrated.first?.account.email == account.email)
    }

    @Test
    func `codex account snapshot store rejects mismatched workspace records`() {
        let fileURL = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
        defer { try? FileManager.default.removeItem(at: fileURL) }

        let oldAccountID = UUID()
        let newAccountID = UUID()
        let oldAccount = CodexVisibleAccount(
            id: "workspace@example.com",
            email: "workspace@example.com",
            workspaceLabel: "Old Team",
            workspaceAccountID: "acct-old",
            storedAccountID: oldAccountID,
            selectionSource: .managedAccount(id: oldAccountID),
            isActive: false,
            isLive: false,
            canReauthenticate: true,
            canRemove: true)
        let newAccount = CodexVisibleAccount(
            id: "workspace@example.com",
            email: "workspace@example.com",
            workspaceLabel: "New Team",
            workspaceAccountID: "acct-new",
            storedAccountID: newAccountID,
            selectionSource: .managedAccount(id: newAccountID),
            isActive: true,
            isLive: false,
            canReauthenticate: true,
            canRemove: true)
        let store = FileCodexAccountUsageSnapshotStore(fileURL: fileURL)
        store.store([
            CodexAccountUsageSnapshot(
                account: oldAccount,
                snapshot: self.snapshot(email: oldAccount.email, percent: 71),
                error: nil,
                sourceLabel: "test"),
        ])

        let hydrated = store.load(for: [newAccount])

        #expect(hydrated.isEmpty)
    }

    @Test
    func `codex account snapshot store keeps same composite owner after auth fingerprint changes`() {
        let fileURL = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
        defer { try? FileManager.default.removeItem(at: fileURL) }

        let accountID = UUID()
        let oldAccount = CodexVisibleAccount(
            id: "reauth@example.com",
            email: "reauth@example.com",
            workspaceAccountID: "acct-reauth",
            authFingerprint: "old-auth-fingerprint",
            storedAccountID: accountID,
            selectionSource: .managedAccount(id: accountID),
            isActive: false,
            isLive: false,
            canReauthenticate: true,
            canRemove: true)
        let newAccount = CodexVisibleAccount(
            id: "reauth@example.com",
            email: "reauth@example.com",
            workspaceAccountID: "acct-reauth",
            authFingerprint: "new-auth-fingerprint",
            storedAccountID: accountID,
            selectionSource: .managedAccount(id: accountID),
            isActive: true,
            isLive: false,
            canReauthenticate: true,
            canRemove: true)
        let store = FileCodexAccountUsageSnapshotStore(fileURL: fileURL)
        store.store([
            CodexAccountUsageSnapshot(
                account: oldAccount,
                snapshot: self.snapshot(email: oldAccount.email, percent: 71),
                error: nil,
                sourceLabel: "test"),
        ])

        let hydrated = store.load(for: [newAccount])

        #expect(hydrated.map(\.id) == [newAccount.id])
        #expect(hydrated.first?.snapshot?.primary?.usedPercent == 71)
    }

    @Test
    func `codex account snapshot store rejects legacy workspace records without identity`() throws {
        let fileURL = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
        defer { try? FileManager.default.removeItem(at: fileURL) }
        let payload = """
        {
          "records" : [
            {
              "error" : "cached",
              "id" : "legacy@example.com",
              "snapshot" : null,
              "sourceLabel" : "legacy"
            }
          ],
          "version" : 1
        }
        """
        try Data(payload.utf8).write(to: fileURL)

        let accountID = UUID()
        let workspaceAccount = CodexVisibleAccount(
            id: "legacy@example.com",
            email: "legacy@example.com",
            workspaceLabel: "New Team",
            workspaceAccountID: "acct-new",
            storedAccountID: accountID,
            selectionSource: .managedAccount(id: accountID),
            isActive: true,
            isLive: false,
            canReauthenticate: true,
            canRemove: true)
        let store = FileCodexAccountUsageSnapshotStore(fileURL: fileURL)

        let hydrated = store.load(for: [workspaceAccount])

        #expect(hydrated.isEmpty)
    }

    @Test
    func `codex account snapshot store rejects normalized legacy email ids without composite owner`() throws {
        let fileURL = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
        defer { try? FileManager.default.removeItem(at: fileURL) }
        let payload = """
        {
          "records" : [
            {
              "error" : "cached",
              "id" : "Legacy@Example.com",
              "snapshot" : null,
              "sourceLabel" : "legacy"
            }
          ],
          "version" : 1
        }
        """
        try Data(payload.utf8).write(to: fileURL)

        let account = CodexVisibleAccount(
            id: "Legacy@Example.com",
            email: "legacy@example.com",
            storedAccountID: nil,
            selectionSource: .liveSystem,
            isActive: true,
            isLive: true,
            canReauthenticate: true,
            canRemove: false)
        let store = FileCodexAccountUsageSnapshotStore(fileURL: fileURL)

        #expect(store.load(for: [account]).isEmpty)
    }

    @Test
    func `codex account snapshot store rejects legacy stable ids crossing workspace members`() throws {
        let fileURL = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
        defer { try? FileManager.default.removeItem(at: fileURL) }

        let managedAccountID = UUID()
        let visibleID = "managed:\(managedAccountID.uuidString.lowercased())"
        let priorAccount = CodexVisibleAccount(
            id: visibleID,
            email: "first-member@example.com",
            workspaceAccountID: "shared-workspace",
            storedAccountID: managedAccountID,
            selectionSource: .managedAccount(id: managedAccountID),
            isActive: false,
            isLive: false,
            canReauthenticate: true,
            canRemove: true)
        let otherMember = CodexVisibleAccount(
            id: priorAccount.id,
            email: "second-member@example.com",
            workspaceAccountID: priorAccount.workspaceAccountID,
            storedAccountID: managedAccountID,
            selectionSource: .managedAccount(id: managedAccountID),
            isActive: true,
            isLive: false,
            canReauthenticate: true,
            canRemove: true)
        let store = FileCodexAccountUsageSnapshotStore(fileURL: fileURL)
        store.store([
            CodexAccountUsageSnapshot(
                account: priorAccount,
                snapshot: self.snapshot(email: priorAccount.email, percent: 71),
                error: nil,
                sourceLabel: "legacy"),
        ])

        try self.removeAccountIdentity(fromSnapshotStoreAt: fileURL)

        #expect(store.load(for: [otherMember]).isEmpty)
    }
}
