import Foundation

/// Abstraction over "load Pro plans and buy one" so `PaywallView` doesn't
/// need to know whether it's talking to real StoreKit or a stub/preview
/// implementation.
///
/// Apple requires (App Store Review Guideline 3.1.1) that any digital
/// feature unlocked *inside* the app (here: Pro status — more scans,
/// folders, export) be sold via Apple's own In-App Purchase, not an
/// external payment link, when the purchase is initiated from inside the
/// app. This protocol is deliberately StoreKit-shaped for that reason.
///
/// Product identifiers (reverse-DNS, matching the app's bundle ID
/// `app.scan-receipts.ios` from project.yml) — these must be created with
/// EXACTLY these identifiers in App Store Connect (real submission) and/or
/// `Configuration.storekit` (local Simulator testing, see that file) before
/// `StoreKitPurchaseService.loadProducts()` will return anything:
///   - `app.scan-receipts.ios.pro.weekly`  — auto-renewable subscription
///   - `app.scan-receipts.ios.pro.annual`  — auto-renewable subscription
///   - `app.scan-receipts.ios.pro.lifetime` — non-consumable
enum PurchaseProductID {
    static let weekly = "app.scan-receipts.ios.pro.weekly"
    static let annual = "app.scan-receipts.ios.pro.annual"
    static let lifetime = "app.scan-receipts.ios.pro.lifetime"
    static let all = [weekly, annual, lifetime]
}

/// One purchasable plan, already localized/priced by StoreKit (or filled in
/// with placeholder copy by `StubPurchaseService`). `planID` is the
/// backend's own plan vocabulary (`"weekly" | "annual" | "lifetime"` — see
/// `users.plan` in `src/lib/schema/db.ts`), kept separate from the StoreKit
/// product identifier since they're different namespaces that happen to be
/// related, not the same thing.
struct PurchaseProduct: Identifiable, Equatable {
    let id: String // StoreKit product identifier (PurchaseProductID.*)
    let planID: String
    let displayName: String
    let displayPrice: String
    /// e.g. "per week" — nil for the one-time lifetime product.
    let periodDescription: String?
}

protocol PurchaseService {
    /// Fetches the current products with their StoreKit-localized prices.
    /// Call before showing `PaywallView`'s plan list — never hardcode prices,
    /// Apple requires displaying the actual localized App Store price.
    func loadProducts() async throws -> [PurchaseProduct]

    /// Initiates a purchase for the given StoreKit product identifier.
    /// Returns `true` only once the transaction is verified and finished;
    /// `false` means the user cancelled the sheet (not an error). Throws for
    /// an actual failure (network, StoreKit error, failed verification).
    ///
    /// `appAccountToken` should be the signed-in user's own
    /// `User.appleAccountToken` — StoreKit attaches it to the resulting
    /// transaction, and `POST /api/webhooks/apple` on the backend uses it to
    /// know which account to grant Pro to (see that property's doc comment).
    /// Pass `nil` only if genuinely no user is signed in (shouldn't happen —
    /// the paywall is only ever shown to a signed-in account — but a
    /// purchase with no attributable owner is still better than none at
    /// all, since `restorePurchases()`/a later manual reconciliation can
    /// recover it).
    func purchase(productID: String, appAccountToken: UUID?) async throws -> Bool

    /// Re-syncs already-owned entitlements — wired to a "Käufe
    /// wiederherstellen" button (required by App Store guidelines for
    /// non-consumable/subscription products) and worth calling once at
    /// launch too, since Apple doesn't otherwise notify a fresh install
    /// about prior purchases made on the same Apple ID.
    func restorePurchases() async throws
}

/// Stub/preview implementation — returns illustrative placeholder products
/// (so `PaywallView` has something to render in SwiftUI previews and before
/// `StoreKitPurchaseService` is wired in) and always throws on an actual
/// purchase attempt, since there is nothing real behind it.
final class StubPurchaseService: PurchaseService {
    func loadProducts() async throws -> [PurchaseProduct] {
        [
            PurchaseProduct(id: PurchaseProductID.weekly, planID: "weekly", displayName: "Wochen-Pass", displayPrice: "4,99 €", periodDescription: "pro Woche"),
            PurchaseProduct(id: PurchaseProductID.annual, planID: "annual", displayName: "Jahres-Pass", displayPrice: "39,99 €", periodDescription: "pro Jahr"),
            PurchaseProduct(id: PurchaseProductID.lifetime, planID: "lifetime", displayName: "Lifetime", displayPrice: "59,99 €", periodDescription: nil),
        ]
    }

    func purchase(productID: String, appAccountToken: UUID?) async throws -> Bool {
        throw NSError(
            domain: "ScanReceipts.Purchase",
            code: -1,
            userInfo: [
                NSLocalizedDescriptionKey: "In-App-Käufe sind in dieser Vorschau-Version noch nicht verfügbar."
            ]
        )
    }

    func restorePurchases() async throws {}
}
