import Foundation
import Testing
@testable import CodexBarCore

struct ClaudeSwapAccountProjectionTests {
    @Test
    func `adapter failures mark retained account snapshots as stale`() {
        #expect(ClaudeSwapAccountProjection.displayError(
            accountError: nil,
            adapterError: "timed out") == "Showing the last successful update: timed out")
        #expect(ClaudeSwapAccountProjection.displayError(
            accountError: "Token expired.",
            adapterError: "timed out") == "Token expired.")
        #expect(ClaudeSwapAccountProjection.displayError(
            accountError: nil,
            adapterError: "timed out",
            switchError: "store locked") == "Account switch failed: store locked")
        #expect(ClaudeSwapAccountProjection.displayError(
            accountError: "API-key account",
            adapterError: nil,
            switchError: "store locked") == "Account switch failed: store locked")
    }

    private let now = Date(timeIntervalSince1970: 1_782_000_000)

    @Test
    func `projects rows into provider neutral snapshots with active account first`() throws {
        let reset = Date(timeIntervalSince1970: 1_782_170_999)
        let list = ClaudeSwapAccountList(
            activeAccountNumber: 2,
            accounts: [
                ClaudeSwapAccountRow(
                    number: 1,
                    email: "work@example.com",
                    isActive: false,
                    usageStatus: .ok,
                    fiveHour: ClaudeSwapUsageWindow(usedPercent: 25, resetsAt: reset),
                    sevenDay: ClaudeSwapUsageWindow(usedPercent: 16.5, resetsAt: nil)),
                ClaudeSwapAccountRow(
                    number: 2,
                    email: "personal@example.com",
                    isActive: true,
                    usageStatus: .ok,
                    fiveHour: ClaudeSwapUsageWindow(usedPercent: 80, resetsAt: nil),
                    sevenDay: nil),
            ])

        let snapshots = ClaudeSwapAccountProjection.accountSnapshots(from: list, now: self.now)
        #expect(snapshots.count == 2)

        let active = try #require(snapshots.first)
        #expect(active.id == ProviderAccountIdentity(source: "claude-swap", opaqueID: "2"))
        #expect(active.provider == .claude)
        #expect(active.displayLabel == "personal@example.com")
        #expect(active.isActive == true)
        #expect(active.canActivate == false)
        #expect(active.error == nil)
        #expect(active.sourceLabel == "claude-swap")
        #expect(active.snapshot?.primary?.usedPercent == 80)
        #expect(active.snapshot?.primary?.windowMinutes == 300)
        #expect(active.snapshot?.secondary == nil)
        #expect(active.snapshot?.updatedAt == self.now)
        #expect(active.snapshot?.identity?.accountEmail == "personal@example.com")
        #expect(active.snapshot?.identity?.loginMethod == "claude-swap")

        let inactive = try #require(snapshots.last)
        #expect(inactive.id.opaqueID == "1")
        #expect(inactive.isActive == false)
        #expect(inactive.canActivate == true)
        #expect(inactive.snapshot?.primary?.resetsAt == reset)
        #expect(inactive.snapshot?.secondary?.usedPercent == 16.5)
        #expect(inactive.snapshot?.secondary?.windowMinutes == 10080)
    }

    @Test
    func `maps sentinel statuses to per account errors without usage`() throws {
        let rows: [(ClaudeSwapUsageStatus, String)] = [
            (.tokenExpired, "Token expired"),
            (.reloginRequired, "Re-login required"),
            (.apiKey, "API-key account"),
            (.keychainUnavailable, "Keychain"),
            (.noCredentials, "No stored credentials"),
            (.unavailable, "Usage fetch failed"),
            (.unknown("mystery"), "mystery"),
        ]

        for (index, entry) in rows.enumerated() {
            let list = ClaudeSwapAccountList(
                activeAccountNumber: nil,
                accounts: [
                    ClaudeSwapAccountRow(
                        number: index + 1,
                        email: "a@b.c",
                        isActive: false,
                        usageStatus: entry.0,
                        fiveHour: nil,
                        sevenDay: nil),
                ])
            let snapshot = try #require(
                ClaudeSwapAccountProjection.accountSnapshots(from: list, now: self.now).first)
            #expect(snapshot.snapshot == nil)
            let error = try #require(snapshot.error)
            #expect(error.contains(entry.1))
            let expectedCanActivate = entry.0 == .apiKey || entry.0 == .unavailable
            #expect(snapshot.canActivate == expectedCanActivate)
        }
    }

    @Test
    func `ok row without windows reports missing usage instead of an empty card`() throws {
        let list = ClaudeSwapAccountList(
            activeAccountNumber: 1,
            accounts: [
                ClaudeSwapAccountRow(
                    number: 1,
                    email: "a@b.c",
                    isActive: true,
                    usageStatus: .ok,
                    fiveHour: nil,
                    sevenDay: nil),
            ])

        let snapshot = try #require(ClaudeSwapAccountProjection.accountSnapshots(from: list, now: self.now).first)
        #expect(snapshot.snapshot == nil)
        #expect(snapshot.error == "No usage windows reported.")
    }

    @Test
    func `projects model scoped weekly windows through claude usage rows`() throws {
        let reset = Date(timeIntervalSince1970: 1_784_620_800)
        let list = ClaudeSwapAccountList(
            activeAccountNumber: 1,
            accounts: [
                ClaudeSwapAccountRow(
                    number: 1,
                    email: "a@b.c",
                    isActive: true,
                    usageStatus: .ok,
                    fiveHour: nil,
                    sevenDay: nil,
                    scoped: [
                        ClaudeSwapScopedUsageWindow(name: "Fable", usedPercent: 33, resetsAt: reset),
                        ClaudeSwapScopedUsageWindow(name: "All models", usedPercent: 42, resetsAt: reset),
                    ]),
            ])

        let account = try #require(ClaudeSwapAccountProjection.accountSnapshots(from: list, now: self.now).first)
        let snapshot = try #require(account.snapshot)
        #expect(account.error == nil)
        #expect(snapshot.primary == nil)
        #expect(snapshot.secondary == nil)
        let scoped = try #require(snapshot.extraRateWindows)
        #expect(scoped.count == 1)
        #expect(scoped.first?.id == "claude-weekly-scoped-fable")
        #expect(scoped.first?.title == "Fable only")
        #expect(scoped.first?.window.usedPercent == 33)
        #expect(scoped.first?.window.windowMinutes == 10080)
        #expect(scoped.first?.window.resetsAt == reset)
    }

    @Test
    func `filtered generic scope does not hide missing usage error`() throws {
        let list = ClaudeSwapAccountList(
            activeAccountNumber: 1,
            accounts: [
                ClaudeSwapAccountRow(
                    number: 1,
                    email: "a@b.c",
                    isActive: true,
                    usageStatus: .ok,
                    fiveHour: nil,
                    sevenDay: nil,
                    scoped: [
                        ClaudeSwapScopedUsageWindow(name: "All models", usedPercent: 42, resetsAt: nil),
                    ]),
            ])

        let account = try #require(ClaudeSwapAccountProjection.accountSnapshots(from: list, now: self.now).first)
        #expect(account.snapshot == nil)
        #expect(account.error == "No usage windows reported.")
    }

    @Test
    func `falls back to ordinal label when email is empty`() throws {
        let list = ClaudeSwapAccountList(
            activeAccountNumber: nil,
            accounts: [
                ClaudeSwapAccountRow(
                    number: 3,
                    email: "",
                    isActive: false,
                    usageStatus: .noCredentials,
                    fiveHour: nil,
                    sevenDay: nil),
            ])

        let snapshot = try #require(ClaudeSwapAccountProjection.accountSnapshots(from: list, now: self.now).first)
        #expect(snapshot.displayLabel == "Account 3")
    }
}
