import CodexBarCore
import Foundation
import Testing
@testable import CodexBar

@Suite(.serialized)
@MainActor
struct ManagedCodexAccountCoordinatorTests {
    @Test
    func `coordinator exposes in flight state and rejects overlapping managed authentication`() async throws {
        let root = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString, isDirectory: true)
        defer { try? FileManager.default.removeItem(at: root) }

        let existingAccountID = try #require(UUID(uuidString: "AAAAAAAA-BBBB-CCCC-DDDD-111111111111"))
        let runner = BlockingManagedCodexLoginRunner()
        let service = ManagedCodexAccountService(
            store: InMemoryManagedCodexAccountStoreForCoordinatorTests(
                accounts: ManagedCodexAccountSet(version: 1, accounts: [])),
            homeFactory: CoordinatorTestManagedCodexHomeFactory(root: root),
            loginRunner: runner,
            identityReader: CoordinatorStubManagedCodexIdentityReader(email: "user@example.com"))
        let coordinator = ManagedCodexAccountCoordinator(service: service)

        let authTask = Task { try await coordinator.authenticateManagedAccount(existingAccountID: existingAccountID) }
        await runner.waitUntilStarted()

        #expect(coordinator.isAuthenticatingManagedAccount)
        #expect(coordinator.authenticatingManagedAccountID == existingAccountID)

        await #expect(throws: ManagedCodexAccountCoordinatorError.authenticationInProgress) {
            try await coordinator.authenticateManagedAccount()
        }

        await runner.resume()
        let account = try await authTask.value

        #expect(account.email == "user@example.com")
        #expect(coordinator.isAuthenticatingManagedAccount == false)
        #expect(coordinator.authenticatingManagedAccountID == nil)
    }

    @Test
    func `coordinator clears in flight state after managed login timeout`() async throws {
        let root = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString, isDirectory: true)
        defer { try? FileManager.default.removeItem(at: root) }

        let loginResult = CodexLoginRunner.Result(outcome: .timedOut, output: "timed out")
        let existingAccountID = try #require(UUID(uuidString: "AAAAAAAA-BBBB-CCCC-DDDD-222222222222"))
        let service = ManagedCodexAccountService(
            store: InMemoryManagedCodexAccountStoreForCoordinatorTests(
                accounts: ManagedCodexAccountSet(version: 1, accounts: [])),
            homeFactory: CoordinatorTestManagedCodexHomeFactory(root: root),
            loginRunner: TimedOutManagedCodexLoginRunner(result: loginResult),
            identityReader: CoordinatorStubManagedCodexIdentityReader(email: "user@example.com"))
        let coordinator = ManagedCodexAccountCoordinator(service: service)

        do {
            _ = try await coordinator.authenticateManagedAccount(existingAccountID: existingAccountID, timeout: 0.2)
            Issue.record("Expected managed login timeout to throw")
        } catch let error as ManagedCodexAccountServiceError {
            #expect(error == .loginFailed(loginResult))
        } catch {
            Issue.record("Expected ManagedCodexAccountServiceError.loginFailed, got \(error)")
        }

        #expect(coordinator.isAuthenticatingManagedAccount == false)
        #expect(coordinator.authenticatingManagedAccountID == nil)
    }
}

private actor BlockingManagedCodexLoginRunner: 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 struct TimedOutManagedCodexLoginRunner: ManagedCodexLoginRunning {
    let result: CodexLoginRunner.Result

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

private final class InMemoryManagedCodexAccountStoreForCoordinatorTests: ManagedCodexAccountStoring,
@unchecked Sendable {
    var snapshot: ManagedCodexAccountSet

    init(accounts: ManagedCodexAccountSet) {
        self.snapshot = accounts
    }

    func loadAccounts() throws -> ManagedCodexAccountSet {
        self.snapshot
    }

    func storeAccounts(_ accounts: ManagedCodexAccountSet) throws {
        self.snapshot = accounts
    }

    func ensureFileExists() throws -> URL {
        FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
    }
}

private final class CoordinatorTestManagedCodexHomeFactory: ManagedCodexHomeProducing, @unchecked Sendable {
    let root: URL

    init(root: URL) {
        self.root = root
    }

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

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

private final class CoordinatorStubManagedCodexIdentityReader: ManagedCodexIdentityReading, @unchecked Sendable {
    let email: String

    init(email: String) {
        self.email = email
    }

    func loadAccountIdentity(homePath _: String) throws -> CodexAuthBackedAccount {
        CodexAuthBackedAccount(
            identity: CodexIdentityResolver.resolve(accountId: nil, email: self.email),
            email: self.email,
            plan: "Pro")
    }
}
