import Foundation
import Testing
@testable import IMessageSync

/// Covers the two pure/bounded primitives introduced by the send-pipeline
/// async-hardening refactor: the iMessage-vs-SMS routing decision, and the
/// `bounded` timeout wrapper that guarantees no blocking operation can stall
/// the send-queue poller.
@Suite("SendService routing & bounded primitive")
struct SendServiceRoutingTests {

    // MARK: - decideService (pure routing logic)

    @Test("SMS-only history routes to SMS")
    func decideServiceSMSOnly() {
        #expect(SendService.decideService(imessageCount: 0, smsCount: 5) == .sms)
        #expect(SendService.decideService(imessageCount: 0, smsCount: 1) == .sms)
    }

    @Test("any iMessage history routes to iMessage")
    func decideServiceHasIMessage() {
        #expect(SendService.decideService(imessageCount: 3, smsCount: 0) == .iMessage)
        // iMessage history wins even when SMS messages also exist.
        #expect(SendService.decideService(imessageCount: 1, smsCount: 20) == .iMessage)
    }

    @Test("a brand-new number with no history defaults to iMessage")
    func decideServiceNoHistory() {
        #expect(SendService.decideService(imessageCount: 0, smsCount: 0) == .iMessage)
    }

    // MARK: - bounded (timeout wrapper)

    @Test("bounded returns the work's value when it finishes within the deadline")
    func boundedReturnsValue() async {
        let result = await SendService.bounded(5, fallback: "fallback") { "real" }
        #expect(result == "real")
    }

    @Test("bounded returns the fallback when the work exceeds the deadline")
    func boundedTimesOut() async {
        let start = Date()
        let result = await SendService.bounded(0.2, fallback: "fallback") {
            Thread.sleep(forTimeInterval: 3)  // deliberately exceeds the 0.2s cap
            return "real"
        }
        let elapsed = Date().timeIntervalSince(start)
        #expect(result == "fallback")
        // The caller is released at the deadline — it does NOT wait out the
        // 3s of blocking work.
        #expect(elapsed < 2.0)
    }

    @Test("bounded resumes exactly once even when work and timeout race")
    func boundedRacesCleanly() async {
        // Work finishes right around the deadline — the ResumeOnce guard must
        // prevent a double-resume crash regardless of which path wins.
        for _ in 0..<20 {
            _ = await SendService.bounded(0.05, fallback: -1) {
                Thread.sleep(forTimeInterval: 0.05)
                return 1
            }
        }
        // Reaching here without a crash is the assertion.
        #expect(Bool(true))
    }
}
