import XCTest
@testable import NucleicCore

final class AFMRequestQueueTests: XCTestCase {
    /// A waiting backlog must be released highest-priority-first, regardless of the order
    /// requests arrived in — chat naming ahead of completion ahead of triage.
    func testHigherPriorityWaitersRunFirst() async {
        let queue = AFMRequestQueue(maxConcurrent: 1)

        // Occupy the single slot first, and hold it until every other request has queued
        // behind it — so the queue, not arrival timing, decides who runs next.
        let blockerRunning = expectation(description: "blocker holds the slot")
        let gate = Gate()
        let blocker = Task {
            await queue.run(priority: .completion) {
                await blockerRunning.fulfill_()
                await gate.wait()
            }
        }
        // Don't enqueue the others until the blocker actually holds the only slot —
        // otherwise one of them races in and runs immediately instead of queueing.
        await fulfillment(of: [blockerRunning], timeout: 2)

        let order = Recorder()
        // Enqueue in deliberately "wrong" order: a background request first, an
        // interactive one last. Priority must reorder them on release.
        async let a: Void = queue.run(priority: .background) { await order.append("background") }
        async let b: Void = queue.run(priority: .interactive) { await order.append("interactive") }
        async let c: Void = queue.run(priority: .completion) { await order.append("completion") }
        async let d: Void = queue.run(priority: .bashSummary) { await order.append("bashSummary") }

        // Give them a beat to actually suspend in the queue before we release.
        try? await Task.sleep(for: .milliseconds(50))
        await gate.open()

        _ = await (a, b, c, d)
        await blocker.value

        // Bash summaries sit just below a live chat name and above session wrap-up.
        let seen = await order.values
        XCTAssertEqual(seen, ["interactive", "bashSummary", "completion", "background"])
    }

    /// `maxConcurrent` is honored: with a cap of 1 the queue never runs two operations at
    /// once, even under a burst.
    func testConcurrencyCapIsRespected() async {
        let queue = AFMRequestQueue(maxConcurrent: 1)
        let meter = ConcurrencyMeter()

        await withTaskGroup(of: Void.self) { group in
            for _ in 0..<8 {
                group.addTask {
                    await queue.run(priority: .background) {
                        await meter.enter()
                        try? await Task.sleep(for: .milliseconds(5))
                        await meter.leave()
                    }
                }
            }
        }
        let peak = await meter.peak
        XCTAssertEqual(peak, 1, "queue ran \(peak) operations at once; cap was 1")
    }

    /// The Private Cloud Compute queue isn't resource-constrained: with an unbounded cap it
    /// fires many requests concurrently rather than serializing them, and stamps each as a
    /// cloud request so the merged inspector can mark it.
    func testCloudQueueRunsConcurrentlyAndFlagsRequests() async {
        let queue = AFMRequestQueue(maxConcurrent: .max, isCloud: true)
        let meter = ConcurrencyMeter()

        await withTaskGroup(of: Void.self) { group in
            for _ in 0..<8 {
                group.addTask {
                    await queue.run(priority: .background) {
                        await meter.enter()
                        try? await Task.sleep(for: .milliseconds(20))
                        await meter.leave()
                    }
                }
            }
        }
        let peak = await meter.peak
        XCTAssertGreaterThan(peak, 1, "cloud queue should run requests concurrently, not serialize")

        // History rows carry the cloud flag, so the inspector can show a cloud glyph on them.
        let snap = await queue.snapshot()
        XCTAssertEqual(snap.history.count, 8)
        XCTAssertTrue(snap.history.allSatisfy(\.isCloud), "cloud queue must flag its requests")
    }

    /// The on-device queue leaves `isCloud` false, so its rows stay un-glyphed.
    func testOnDeviceQueueDoesNotFlagRequestsAsCloud() async {
        let queue = AFMRequestQueue(maxConcurrent: 1)
        await queue.run(priority: .background, label: "local") {}
        let snap = await queue.snapshot()
        XCTAssertEqual(snap.history.map(\.isCloud), [false])
    }

    /// Same-priority requests keep FIFO arrival order.
    func testSamePriorityIsFIFO() async {
        let queue = AFMRequestQueue(maxConcurrent: 1)
        let blockerRunning = expectation(description: "blocker holds the slot")
        let gate = Gate()
        let blocker = Task {
            await queue.run(priority: .background) {
                await blockerRunning.fulfill_()
                await gate.wait()
            }
        }
        await fulfillment(of: [blockerRunning], timeout: 2)

        // Enqueue strictly in order, yielding between each so they suspend in sequence.
        let order = Recorder()
        async let a: Void = queue.run(priority: .background) { await order.append("1") }
        try? await Task.sleep(for: .milliseconds(20))
        async let b: Void = queue.run(priority: .background) { await order.append("2") }
        try? await Task.sleep(for: .milliseconds(20))
        async let c: Void = queue.run(priority: .background) { await order.append("3") }
        try? await Task.sleep(for: .milliseconds(20))
        await gate.open()
        _ = await (a, b, c)
        await blocker.value

        let seen = await order.values
        XCTAssertEqual(seen, ["1", "2", "3"])
    }

    /// Coalescing: when several same-key requests pile up waiting (a burst of backlog
    /// triages), only the newest runs — the earlier waiters are superseded, return nil, and
    /// never execute their operation. Requests with a different key (or none) are untouched.
    func testCoalescingSupersedesEarlierSameKeyWaiters() async {
        let queue = AFMRequestQueue(maxConcurrent: 1)
        let blockerRunning = expectation(description: "blocker holds the slot")
        let gate = Gate()
        let blocker = Task {
            await queue.run(priority: .background, label: "blocker") {
                await blockerRunning.fulfill_()
                await gate.wait()
            }
        }
        await fulfillment(of: [blockerRunning], timeout: 2)

        // Three same-key triages queue behind the blocker; each new arrival supersedes the
        // last. A fourth request under a different key must be left to run normally.
        let order = Recorder()
        async let a = queue.runCoalescing(priority: .background, coalesceKey: "triage", label: "t1") {
            await order.append("t1"); return "t1"
        }
        try? await Task.sleep(for: .milliseconds(20))
        async let b = queue.runCoalescing(priority: .background, coalesceKey: "triage", label: "t2") {
            await order.append("t2"); return "t2"
        }
        try? await Task.sleep(for: .milliseconds(20))
        async let c = queue.runCoalescing(priority: .background, coalesceKey: "triage", label: "t3") {
            await order.append("t3"); return "t3"
        }
        try? await Task.sleep(for: .milliseconds(20))
        async let d = queue.runCoalescing(priority: .background, coalesceKey: "other", label: "o1") {
            await order.append("o1"); return "o1"
        }
        try? await Task.sleep(for: .milliseconds(20))

        await gate.open()
        let results = await (a, b, c, d)
        await blocker.value

        // The two earlier same-key waiters were superseded: nil, and they never ran.
        XCTAssertNil(results.0)
        XCTAssertNil(results.1)
        XCTAssertEqual(results.2, "t3")
        XCTAssertEqual(results.3, "o1")

        let ran = await order.values
        XCTAssertEqual(Set(ran), ["t3", "o1"], "only the newest same-key pass and the other-key pass ran")
    }

    /// The inspector snapshot reports the running request and the waiting backlog in
    /// release order (priority first, ties FIFO), carrying each request's label.
    func testSnapshotReportsRunningAndWaitingInReleaseOrder() async {
        let queue = AFMRequestQueue(maxConcurrent: 1)
        let blockerRunning = expectation(description: "blocker holds the slot")
        let gate = Gate()
        let blocker = Task {
            await queue.run(priority: .completion, label: "blocker") {
                await blockerRunning.fulfill_()
                await gate.wait()
            }
        }
        await fulfillment(of: [blockerRunning], timeout: 2)

        // Queue three behind the blocker, in deliberately "wrong" arrival order.
        async let a: Void = queue.run(priority: .background, label: "bg") {}
        async let b: Void = queue.run(priority: .interactive, label: "ui") {}
        async let c: Void = queue.run(priority: .completion, label: "done") {}
        try? await Task.sleep(for: .milliseconds(50))

        let snap = await queue.snapshot()
        XCTAssertEqual(snap.running.map(\.label), ["blocker"])
        // Waiting list is ordered as it will be released: interactive, then completion,
        // then background — not arrival order.
        XCTAssertEqual(snap.waiting.map(\.label), ["ui", "done", "bg"])
        XCTAssertEqual(snap.waiting.map(\.priority), [.interactive, .completion, .background])

        await gate.open()
        _ = await (a, b, c)
        await blocker.value

        // Everything drained — the snapshot is empty again.
        let drained = await queue.snapshot()
        XCTAssertTrue(drained.isEmpty)
    }

    /// Finished requests land in the snapshot's history, most-recent first, with their run
    /// duration recorded — and history doesn't count toward `isEmpty` (it's a record of
    /// past work, not live activity).
    func testCompletedRequestsAppearInHistoryMostRecentFirst() async {
        let queue = AFMRequestQueue(maxConcurrent: 1)

        // Run three to completion, in order, so history captures all three.
        await queue.run(priority: .background, label: "first") {}
        await queue.run(priority: .interactive, label: "second") {}
        await queue.run(priority: .completion, label: "third") {}

        let snap = await queue.snapshot()
        // No live work, but history is populated, so the snapshot still isn't "empty".
        XCTAssertTrue(snap.running.isEmpty)
        XCTAssertTrue(snap.waiting.isEmpty)
        XCTAssertTrue(snap.isEmpty, "isEmpty tracks only live work, not history")
        // Most-recent first.
        XCTAssertEqual(snap.history.map(\.label), ["third", "second", "first"])
        // Each history entry carries a finish time and a non-negative run duration.
        for request in snap.history {
            XCTAssertNotNil(request.finishedAt)
            XCTAssertNotNil(request.ranFor)
        }
    }

    /// A request that returns text records its output on the history entry — the model's own
    /// reply (a Bash summary line) — for the inspector's expanded detail, while the label
    /// stays the event type. A nil/blank result records no output.
    func testResultTextIsRecordedOnHistoryEntry() async {
        let queue = AFMRequestQueue(maxConcurrent: 1)

        // Text work: the result is recorded as the entry's output; the label is untouched.
        _ = await queue.run(priority: .bashSummary, label: "Command summary",
                            resultText: { $0 }) { "Ran the test suite" }
        // Blank output: nothing recorded.
        _ = await queue.run(priority: .bashSummary, label: "Command summary",
                            resultText: { $0 }) { "   " }
        // No extractor: no output (structured work that isn't text).
        await queue.run(priority: .background, label: "Backlog triage") {}

        let history = await queue.snapshot().history
        // Labels stay the event type for every row, most-recent first.
        XCTAssertEqual(history.map(\.label), ["Backlog triage", "Command summary", "Command summary"])
        // Only the text request with non-blank output carries a recorded result.
        XCTAssertEqual(history.map(\.result), [nil, nil, "Ran the test suite"])
    }

    /// Freezing holds the waiting queue still: while frozen no waiter is promoted, even
    /// after the running slot frees, and the snapshot reports the paused state. Resuming
    /// fills the idle slot and drains the backlog in release order.
    func testFreezeHoldsWaitersUntilResumed() async {
        let queue = AFMRequestQueue(maxConcurrent: 1)

        // Occupy the slot, then freeze while it's still held so nothing is promoted when it
        // releases.
        let blockerRunning = expectation(description: "blocker holds the slot")
        let gate = Gate()
        let blocker = Task {
            await queue.run(priority: .completion, label: "blocker") {
                await blockerRunning.fulfill_()
                await gate.wait()
            }
        }
        await fulfillment(of: [blockerRunning], timeout: 2)

        let order = Recorder()
        async let a: Void = queue.run(priority: .background, label: "bg") { await order.append("bg") }
        async let b: Void = queue.run(priority: .interactive, label: "ui") { await order.append("ui") }
        try? await Task.sleep(for: .milliseconds(50))

        await queue.setFrozen(true)
        let frozenSnap = await queue.snapshot()
        XCTAssertTrue(frozenSnap.frozen)
        XCTAssertEqual(frozenSnap.waiting.map(\.label), ["ui", "bg"])

        // Release the blocker: frozen, the freed slot must stay idle and nobody runs.
        await gate.open()
        await blocker.value
        try? await Task.sleep(for: .milliseconds(50))
        let stillFrozen = await queue.snapshot()
        XCTAssertTrue(stillFrozen.running.isEmpty, "no waiter should be promoted while frozen")
        XCTAssertEqual(stillFrozen.waiting.map(\.label), ["ui", "bg"])
        let ranWhileFrozen = await order.values
        XCTAssertTrue(ranWhileFrozen.isEmpty, "nothing should run while frozen")

        // Resume: the backlog drains, highest-priority first.
        await queue.setFrozen(false)
        _ = await (a, b)
        let seen = await order.values
        XCTAssertEqual(seen, ["ui", "bg"])
        let drained = await queue.snapshot()
        XCTAssertFalse(drained.frozen)
        XCTAssertTrue(drained.isEmpty)
    }

    /// History is bounded: a long-running app can't grow it without limit. With a cap of 50,
    /// running 60 requests keeps only the 50 most recent.
    func testHistoryIsBounded() async {
        let queue = AFMRequestQueue(maxConcurrent: 1)
        for i in 0..<60 {
            await queue.run(priority: .background, label: "req-\(i)") {}
        }
        let snap = await queue.snapshot()
        XCTAssertEqual(snap.history.count, 50)
        // The most recent run is first; the oldest 10 have been dropped.
        XCTAssertEqual(snap.history.first?.label, "req-59")
        XCTAssertEqual(snap.history.last?.label, "req-10")
    }
}

/// Records the order operations run in.
private actor Recorder {
    private(set) var values: [String] = []
    func append(_ value: String) { values.append(value) }
}

/// A one-shot gate the blocking operation waits on until the test opens it.
private actor Gate {
    private var isOpen = false
    private var waiters: [CheckedContinuation<Void, Never>] = []
    func wait() async {
        if isOpen { return }
        await withCheckedContinuation { waiters.append($0) }
    }
    func open() {
        isOpen = true
        for waiter in waiters { waiter.resume() }
        waiters.removeAll()
    }
}

/// Tracks how many operations are inside the critical section at once.
private actor ConcurrencyMeter {
    private var current = 0
    private(set) var peak = 0
    func enter() { current += 1; peak = max(peak, current) }
    func leave() { current -= 1 }
}

private extension XCTestExpectation {
    /// `fulfill()` is `@MainActor`-isolated to nonisolated callers in some toolchains;
    /// this hop keeps the actor-isolated operation bodies above able to signal it.
    func fulfill_() async { await MainActor.run { self.fulfill() } }
}
