import Testing
@testable import bookstax
import Foundation

// MARK: - DonationPurchaseState Enum

@Suite("DonationPurchaseState – Computed Properties")
struct DonationPurchaseStateTests {

    @Test("activePurchasingID returns id when purchasing")
    func activePurchasingID() {
        let state = DonationPurchaseState.purchasing(productID: "donatebook")
        #expect(state.activePurchasingID == "donatebook")
    }

    @Test("activePurchasingID returns nil for non-purchasing states")
    @available(iOS 18.0, *)
    func activePurchasingIDNil() {
        #expect(DonationPurchaseState.idle.activePurchasingID == nil)
        #expect(DonationPurchaseState.thankYou(productID: "x").activePurchasingID == nil)
        #expect(DonationPurchaseState.pending(productID: "x").activePurchasingID == nil)
        #expect(DonationPurchaseState.failed(productID: "x", message: "err").activePurchasingID == nil)
    }

    @Test("thankYouID returns id when in thankYou state")
    func thankYouID() {
        let state = DonationPurchaseState.thankYou(productID: "doneatepage")
        #expect(state.thankYouID == "doneatepage")
    }

    @Test("thankYouID returns nil for other states")
    func thankYouIDNil() {
        #expect(DonationPurchaseState.idle.thankYouID == nil)
        #expect(DonationPurchaseState.purchasing(productID: "x").thankYouID == nil)
    }

    @Test("pendingID returns id when in pending state")
    func pendingID() {
        let state = DonationPurchaseState.pending(productID: "donateencyclopaedia")
        #expect(state.pendingID == "donateencyclopaedia")
    }

    @Test("pendingID returns nil for other states")
    func pendingIDNil() {
        #expect(DonationPurchaseState.idle.pendingID == nil)
        #expect(DonationPurchaseState.thankYou(productID: "x").pendingID == nil)
    }

    @Test("errorMessage returns message when failed for matching id")
    func errorMessageMatch() {
        let state = DonationPurchaseState.failed(productID: "donatebook", message: "Payment declined")
        #expect(state.errorMessage(for: "donatebook") == "Payment declined")
    }

    @Test("errorMessage returns nil for wrong product id")
    func errorMessageNoMatch() {
        let state = DonationPurchaseState.failed(productID: "donatebook", message: "error")
        #expect(state.errorMessage(for: "doneatepage") == nil)
    }

    @Test("errorMessage returns nil for non-failed states")
    func errorMessageNotFailed() {
        #expect(DonationPurchaseState.idle.errorMessage(for: "x") == nil)
        #expect(DonationPurchaseState.purchasing(productID: "x").errorMessage(for: "x") == nil)
    }

    @Test("isIdle returns true only for .idle")
    func isIdleCheck() {
        #expect(DonationPurchaseState.idle.isIdle == true)
        #expect(DonationPurchaseState.purchasing(productID: "x").isIdle == false)
        #expect(DonationPurchaseState.thankYou(productID: "x").isIdle == false)
        #expect(DonationPurchaseState.failed(productID: "x", message: "e").isIdle == false)
    }

    @Test("Equatable: same purchasing states are equal")
    func equatablePurchasing() {
        #expect(DonationPurchaseState.purchasing(productID: "a") == DonationPurchaseState.purchasing(productID: "a"))
        #expect(DonationPurchaseState.purchasing(productID: "a") != DonationPurchaseState.purchasing(productID: "b"))
    }

    @Test("Equatable: idle equals idle")
    func equatableIdle() {
        #expect(DonationPurchaseState.idle == DonationPurchaseState.idle)
        #expect(DonationPurchaseState.idle != DonationPurchaseState.purchasing(productID: "x"))
    }
}

// MARK: - shouldShowNudge Timing

@Suite("DonationService – shouldShowNudge", .serialized)
@MainActor
struct DonationServiceNudgeTests {

    private let installKey = "bookstax.installDate"
    private let nudgeKey   = "bookstax.lastNudgeDate"
    private let historyKey = "bookstax.donationHistory"

    init() {
        // Clean UserDefaults before each test so DonationService reads fresh state
        UserDefaults.standard.removeObject(forKey: installKey)
        UserDefaults.standard.removeObject(forKey: nudgeKey)
        UserDefaults.standard.removeObject(forKey: historyKey)
    }

    @Test("Within 3-day grace period: nudge should not show")
    func gracePeriodPreventsNudge() {
        let recentInstall = Date().addingTimeInterval(-(2 * 24 * 3600)) // 2 days ago
        UserDefaults.standard.set(recentInstall, forKey: installKey)
        #expect(DonationService.shared.shouldShowNudge == false)
    }

    @Test("After 3-day grace period: nudge should show")
    func afterGracePeriodNudgeShows() {
        let oldInstall = Date().addingTimeInterval(-(4 * 24 * 3600)) // 4 days ago
        UserDefaults.standard.set(oldInstall, forKey: installKey)
        #expect(DonationService.shared.shouldShowNudge == true)
    }

    @Test("No install date recorded: nudge should not show (safe fallback)")
    func noInstallDateFallback() {
        // No install date stored — graceful fallback
        #expect(DonationService.shared.shouldShowNudge == false)
    }

    @Test("Nudge recently dismissed: should not show again")
    func recentNudgeHidden() {
        let oldInstall = Date().addingTimeInterval(-(90 * 24 * 3600))
        let recentNudge = Date().addingTimeInterval(-(10 * 24 * 3600)) // dismissed 10 days ago
        UserDefaults.standard.set(oldInstall, forKey: installKey)
        UserDefaults.standard.set(recentNudge, forKey: nudgeKey)
        #expect(DonationService.shared.shouldShowNudge == false)
    }

    @Test("Nudge dismissed ~6 months ago: should show again")
    func sixMonthsLaterShowAgain() {
        let oldInstall = Date().addingTimeInterval(-(200 * 24 * 3600))
        let oldNudge   = Date().addingTimeInterval(-(183 * 24 * 3600)) // ~6 months ago
        UserDefaults.standard.set(oldInstall, forKey: installKey)
        UserDefaults.standard.set(oldNudge, forKey: nudgeKey)
        #expect(DonationService.shared.shouldShowNudge == true)
    }

    @Test("Nudge not yet 6 months: should not show")
    func beforeSixMonthsHidden() {
        let oldInstall = Date().addingTimeInterval(-(200 * 24 * 3600))
        let recentNudge = Date().addingTimeInterval(-(100 * 24 * 3600)) // only 100 days
        UserDefaults.standard.set(oldInstall, forKey: installKey)
        UserDefaults.standard.set(recentNudge, forKey: nudgeKey)
        #expect(DonationService.shared.shouldShowNudge == false)
    }
}
