import XCTest
@testable import MacSyncShared

private struct FakeItem: Sendable, Equatable {
    let id: String
    let body: String
}

private actor FakeTransportState {
    var pending: [FakeItem]
    var reportedSent: [String] = []
    var reportedFailed: [(id: String, error: String?)] = []
    var fetchCalls: Int = 0

    init(pending: [FakeItem]) {
        self.pending = pending
    }

    func popAll() -> [FakeItem] {
        fetchCalls += 1
        let items = pending
        pending = []
        return items
    }

    func recordSent(_ id: String) { reportedSent.append(id) }
    func recordFailed(_ id: String, _ error: String?) { reportedFailed.append((id, error)) }
}

private struct FakeTransport: SendQueueTransport {
    typealias PendingItem = FakeItem
    let state: FakeTransportState

    func id(of item: FakeItem) -> String { item.id }
    func fetchPending() async throws -> [FakeItem] { await state.popAll() }
    func reportResult(id: String, status: String, error: String?) async throws {
        if status == "sent" {
            await state.recordSent(id)
        } else {
            await state.recordFailed(id, error)
        }
    }
}

final class SendQueueClientTests: XCTestCase {
    @MainActor
    func test_drainOnce_appliesAndAcksEachItem() async {
        let state = FakeTransportState(pending: [
            FakeItem(id: "a", body: "hello"),
            FakeItem(id: "b", body: "world"),
        ])
        let transport = FakeTransport(state: state)

        let client = SendQueueClient(
            label: "test",
            transport: transport,
            interval: 60
        ) { item in
            item.body == "world" ? .failed(reason: "bad world") : .sent
        }

        await client.drainOnce()

        let sent = await state.reportedSent
        let failed = await state.reportedFailed
        XCTAssertEqual(sent, ["a"])
        XCTAssertEqual(failed.count, 1)
        XCTAssertEqual(failed.first?.id, "b")
        XCTAssertEqual(failed.first?.error, "bad world")
    }

    @MainActor
    func test_drainOnce_isReentrantSafe() async {
        let state = FakeTransportState(pending: (0..<5).map { FakeItem(id: "\($0)", body: "x") })
        let transport = FakeTransport(state: state)

        let client = SendQueueClient(
            label: "test",
            transport: transport,
            interval: 60
        ) { _ in
            try? await Task.sleep(nanoseconds: 20_000_000)
            return .sent
        }

        async let a: Void = client.drainOnce()
        async let b: Void = client.drainOnce()
        async let c: Void = client.drainOnce()
        _ = await (a, b, c)

        let calls = await state.fetchCalls
        XCTAssertEqual(calls, 1, "concurrent drains must coalesce")
        let sent = await state.reportedSent
        XCTAssertEqual(sent.count, 5)
    }

    @MainActor
    func test_drainOnce_emptyQueueIsNoop() async {
        let state = FakeTransportState(pending: [])
        let transport = FakeTransport(state: state)
        let client = SendQueueClient(
            label: "test",
            transport: transport,
            interval: 60
        ) { _ in .sent }

        await client.drainOnce()
        let sent = await state.reportedSent
        let failed = await state.reportedFailed
        XCTAssertTrue(sent.isEmpty)
        XCTAssertTrue(failed.isEmpty)
    }
}
