import Testing
@testable import Rosetta

// MARK: - Pending Chat Route Expiry Tests

/// Tests for the push notification → chat navigation fix.
/// Bug: stale `pendingChatRoute` was consumed on tab switches (`.onAppear`)
/// instead of only on cold start / foreground resume.
/// Fix: `consumeFreshPendingRoute()` checks timestamp < 3 seconds.
struct PendingChatRouteExpiryTests {

    private let routeA = ChatRoute(publicKey: "02aaa", title: "Alice", username: "", verified: 0)
    private let routeB = ChatRoute(publicKey: "02bbb", title: "Bob", username: "", verified: 0)

    private func clearStatics() {
        AppDelegate.pendingChatRoute = nil
        AppDelegate.pendingChatRouteTimestamp = nil
    }

    // MARK: - Fresh Route Consumption

    @Test("Fresh route (just set) is consumed successfully")
    func freshRouteConsumed() {
        clearStatics()
        AppDelegate.pendingChatRoute = routeA
        AppDelegate.pendingChatRouteTimestamp = Date()

        let result = AppDelegate.consumeFreshPendingRoute()
        #expect(result != nil)
        #expect(result?.publicKey == "02aaa")
        #expect(result?.title == "Alice")
    }

    @Test("After consumption, both statics are nil")
    func staticsNilAfterConsumption() {
        clearStatics()
        AppDelegate.pendingChatRoute = routeA
        AppDelegate.pendingChatRouteTimestamp = Date()

        _ = AppDelegate.consumeFreshPendingRoute()
        #expect(AppDelegate.pendingChatRoute == nil)
        #expect(AppDelegate.pendingChatRouteTimestamp == nil)
    }

    @Test("Second consumption returns nil (idempotent)")
    func doubleConsumptionReturnsNil() {
        clearStatics()
        AppDelegate.pendingChatRoute = routeA
        AppDelegate.pendingChatRouteTimestamp = Date()

        let first = AppDelegate.consumeFreshPendingRoute()
        let second = AppDelegate.consumeFreshPendingRoute()
        #expect(first != nil)
        #expect(second == nil)
    }

    // MARK: - Stale Route Rejection

    @Test("Stale route (5s old) is rejected and cleared")
    func staleRouteRejected() {
        clearStatics()
        AppDelegate.pendingChatRoute = routeA
        AppDelegate.pendingChatRouteTimestamp = Date().addingTimeInterval(-5)

        let result = AppDelegate.consumeFreshPendingRoute()
        #expect(result == nil)
        // Both statics must be cleared even on rejection
        #expect(AppDelegate.pendingChatRoute == nil)
        #expect(AppDelegate.pendingChatRouteTimestamp == nil)
    }

    @Test("Route exactly at expiry boundary (3s) is rejected")
    func routeAtExpiryBoundary() {
        clearStatics()
        AppDelegate.pendingChatRoute = routeA
        AppDelegate.pendingChatRouteTimestamp = Date().addingTimeInterval(-3.0)

        let result = AppDelegate.consumeFreshPendingRoute()
        #expect(result == nil)
    }

    @Test("Route just before expiry (2.9s) is consumed")
    func routeJustBeforeExpiry() {
        clearStatics()
        AppDelegate.pendingChatRoute = routeA
        AppDelegate.pendingChatRouteTimestamp = Date().addingTimeInterval(-2.9)

        let result = AppDelegate.consumeFreshPendingRoute()
        #expect(result != nil)
        #expect(result?.publicKey == "02aaa")
    }

    @Test("Very old route (60s) is rejected")
    func veryOldRouteRejected() {
        clearStatics()
        AppDelegate.pendingChatRoute = routeA
        AppDelegate.pendingChatRouteTimestamp = Date().addingTimeInterval(-60)

        let result = AppDelegate.consumeFreshPendingRoute()
        #expect(result == nil)
    }

    // MARK: - Missing Data

    @Test("Route without timestamp is rejected and cleared")
    func routeWithoutTimestamp() {
        clearStatics()
        AppDelegate.pendingChatRoute = routeA
        AppDelegate.pendingChatRouteTimestamp = nil

        let result = AppDelegate.consumeFreshPendingRoute()
        #expect(result == nil)
        #expect(AppDelegate.pendingChatRoute == nil)
    }

    @Test("Timestamp without route is rejected and cleared")
    func timestampWithoutRoute() {
        clearStatics()
        AppDelegate.pendingChatRoute = nil
        AppDelegate.pendingChatRouteTimestamp = Date()

        let result = AppDelegate.consumeFreshPendingRoute()
        #expect(result == nil)
        #expect(AppDelegate.pendingChatRouteTimestamp == nil)
    }

    @Test("Both nil — returns nil, no crash")
    func bothNil() {
        clearStatics()
        let result = AppDelegate.consumeFreshPendingRoute()
        #expect(result == nil)
    }

    // MARK: - Route Overwrite (rapid notification taps)

    @Test("Second notification overwrites first — only second is consumed")
    func overwriteWithSecondNotification() {
        clearStatics()
        // First notification
        AppDelegate.pendingChatRoute = routeA
        AppDelegate.pendingChatRouteTimestamp = Date()
        // Second notification immediately after (overwrites)
        AppDelegate.pendingChatRoute = routeB
        AppDelegate.pendingChatRouteTimestamp = Date()

        let result = AppDelegate.consumeFreshPendingRoute()
        #expect(result != nil)
        #expect(result?.publicKey == "02bbb")
        #expect(result?.title == "Bob")
    }

    // MARK: - Expiry Constant

    @Test("Expiry constant is 3 seconds")
    func expiryConstantValue() {
        #expect(AppDelegate.pendingRouteExpirySeconds == 3.0)
    }
}

// MARK: - Tab Switch Scenario Tests

/// Simulates the exact bug scenario:
/// 1. Route set from notification tap
/// 2. .onReceive fails to consume (simulated by skipping)
/// 3. Tab switch triggers .onAppear → stale route must NOT navigate
struct TabSwitchScenarioTests {

    private let route = ChatRoute(publicKey: "02ccc", title: "Charlie", username: "", verified: 0)

    private func clearStatics() {
        AppDelegate.pendingChatRoute = nil
        AppDelegate.pendingChatRouteTimestamp = nil
    }

    @Test("Stale route from 10s ago is NOT consumed on tab switch")
    func staleRouteOnTabSwitch() {
        clearStatics()
        // Simulate: notification tapped 10 seconds ago, .onReceive missed it
        AppDelegate.pendingChatRoute = route
        AppDelegate.pendingChatRouteTimestamp = Date().addingTimeInterval(-10)

        // Simulate: .onAppear fires on tab switch
        let consumed = AppDelegate.consumeFreshPendingRoute()
        #expect(consumed == nil, "Stale route must NOT be consumed on tab switch")
        #expect(AppDelegate.pendingChatRoute == nil, "Stale route must be cleared")
    }

    @Test("Fresh route from notification tap IS consumed on cold start .onAppear")
    func freshRouteOnColdStart() {
        clearStatics()
        // Simulate: notification tapped just now, app is launching
        AppDelegate.pendingChatRoute = route
        AppDelegate.pendingChatRouteTimestamp = Date()

        // Simulate: .onAppear fires on cold start (< 3s since notification tap)
        let consumed = AppDelegate.consumeFreshPendingRoute()
        #expect(consumed != nil, "Fresh route must be consumed on cold start")
        #expect(consumed?.publicKey == "02ccc")
    }

    @Test("Fresh route consumed by .onReceive → didBecomeActive finds nil")
    func onReceiveConsumesThenDidBecomeActiveNoOp() {
        clearStatics()
        // Simulate: notification posted + .onReceive fires (primary path)
        AppDelegate.pendingChatRoute = route
        AppDelegate.pendingChatRouteTimestamp = Date()

        // .onReceive consumes (simulated by direct clear, same as ChatListView line 110-111)
        AppDelegate.pendingChatRoute = nil
        AppDelegate.pendingChatRouteTimestamp = nil

        // didBecomeActiveNotification fires after
        let consumed = AppDelegate.consumeFreshPendingRoute()
        #expect(consumed == nil, "didBecomeActive must be no-op after .onReceive consumed")
    }

    @Test("Route set → first consumeFresh consumes → second consumeFresh returns nil")
    func sequentialConsumption() {
        clearStatics()
        AppDelegate.pendingChatRoute = route
        AppDelegate.pendingChatRouteTimestamp = Date()

        // First call: .onAppear on cold start
        let first = AppDelegate.consumeFreshPendingRoute()
        // Second call: didBecomeActiveNotification a moment later
        let second = AppDelegate.consumeFreshPendingRoute()

        #expect(first != nil)
        #expect(second == nil, "Only the first caller should get the route")
    }
}
