import Foundation
import Testing
@testable import ClientTLS

// B3 · The silent rotation scheduler decides — from the installed identity's
// rotation timing — whether to renew before expiry, then drives an injected
// mTLS renew. Pure and transport-free: fully unit-testable with fakes.

private let t0 = ISO8601DateFormatter().date(from: "2026-09-05T00:00:00Z")!
private let renewAfter = ISO8601DateFormatter().date(from: "2026-09-05T00:00:00Z")!
private let notAfter = ISO8601DateFormatter().date(from: "2026-10-06T00:00:00Z")!
private let before = ISO8601DateFormatter().date(from: "2026-09-04T00:00:00Z")!
private let after = ISO8601DateFormatter().date(from: "2026-09-06T00:00:00Z")!

private func state(renewAfter: Date? = renewAfter) -> DeviceRenewalState {
    DeviceRenewalState(deviceId: "dev-1", notAfter: notAfter, renewAfter: renewAfter)
}

/// Records how many times the renew operation ran. `@unchecked Sendable`: guarded
/// by a lock.
private final class RenewSpy: @unchecked Sendable {
    private let lock = NSLock()
    private var _count = 0
    var count: Int { lock.withLock { _count } }
    func bump() { lock.withLock { _count += 1 } }
}

@Test("no enrolled identity → notEnrolled, and renew never runs")
func schedulerNotEnrolled() async {
    let spy = RenewSpy()
    let scheduler = CertificateRotationScheduler(
        renewalState: { nil },
        performRenew: { spy.bump(); return nil },
        now: { after }
    )
    let outcome = await scheduler.runIfDue()
    #expect(outcome == .notEnrolled)
    #expect(spy.count == 0)
}

@Test("cert not yet due → notDue, and renew never runs")
func schedulerNotDue() async {
    let spy = RenewSpy()
    let scheduler = CertificateRotationScheduler(
        renewalState: { state() },
        performRenew: { spy.bump(); return nil },
        now: { before }
    )
    let outcome = await scheduler.runIfDue()
    #expect(outcome == .notDue(renewAfter: renewAfter))
    #expect(spy.count == 0)
}

@Test("past renewAfter → renews exactly once")
func schedulerRenews() async {
    let spy = RenewSpy()
    let scheduler = CertificateRotationScheduler(
        renewalState: { state() },
        performRenew: { spy.bump(); return nil },
        now: { after }
    )
    let outcome = await scheduler.runIfDue()
    #expect(outcome == .renewed)
    #expect(spy.count == 1)
}

@Test("due exactly at renewAfter (>= boundary) renews")
func schedulerRenewsAtBoundary() async {
    let scheduler = CertificateRotationScheduler(
        renewalState: { state() },
        performRenew: { nil },
        now: { renewAfter } // now == renewAfter
    )
    #expect(await scheduler.runIfDue() == .renewed)
}

@Test("a missing renewAfter never triggers (fail-safe — TLS stack is the gate)")
func schedulerMissingRenewAfterNeverDue() async {
    let scheduler = CertificateRotationScheduler(
        renewalState: { state(renewAfter: nil) },
        performRenew: { nil },
        now: { after }
    )
    #expect(await scheduler.runIfDue() == .notDue(renewAfter: nil))
}

@Test("a renew that throws is surfaced as .failed (never crashes, cert still valid)")
func schedulerRenewFailure() async {
    struct Boom: Error {}
    let scheduler = CertificateRotationScheduler(
        renewalState: { state() },
        performRenew: { throw Boom() },
        now: { after }
    )
    #expect(await scheduler.runIfDue() == .failed)
}

@Test("a keychain read fault is surfaced as .failed, not a crash")
func schedulerReadFailure() async {
    struct Boom: Error {}
    let scheduler = CertificateRotationScheduler(
        renewalState: { throw Boom() },
        performRenew: { nil },
        now: { after }
    )
    #expect(await scheduler.runIfDue() == .failed)
}
