import Combine
import Foundation
import StoreKit

/// Product identifiers — create matching Non-Consumable + Auto-Renewable Subscription in App Store Connect.
enum PremiumProductID: String, CaseIterable {
    case perpetual = "com.googleapps.lifetime"
    case yearly = "com.googleapps.yearly"
}

@MainActor
final class PremiumStore: ObservableObject {
    static let shared = PremiumStore()

    @Published private(set) var products: [Product] = []
    @Published private(set) var isPremiumUnlocked = false
    @Published private(set) var activePremiumProductID: PremiumProductID?
    @Published private(set) var isLoadingProducts = false
    @Published private(set) var loadError: String?
    @Published private(set) var purchaseInProgress = false
    @Published private(set) var restoreInProgress = false
    @Published var purchaseError: String?
    @Published var restoreMessage: String?

    private var transactionListener: Task<Void, Never>?

    private init() {
        transactionListener = Task { await listenForTransactions() }
        Task { await refreshEntitlements() }
    }

    deinit {
        transactionListener?.cancel()
    }

    func product(for premiumID: PremiumProductID) -> Product? {
        products.first { $0.id == premiumID.rawValue }
    }

    func displayPrice(for premiumID: PremiumProductID) -> String? {
        product(for: premiumID)?.displayPrice
    }

    func loadProducts() async {
        await loadProducts(attempt: 1)
    }

    private func loadProducts(attempt: Int) async {
        isLoadingProducts = true
        loadError = nil
        defer { isLoadingProducts = false }

        do {
            let ids = PremiumProductID.allCases.map(\.rawValue)
            let loaded = try await Product.products(for: ids)
            let order = Dictionary(uniqueKeysWithValues: ids.enumerated().map { ($1, $0) })
            products = loaded.sorted { (order[$0.id] ?? Int.max) < (order[$1.id] ?? Int.max) }
            if loaded.isEmpty {
                loadError = """
                No products returned for IDs: \(ids.joined(separator: ", ")).
                If testing locally, confirm Product > Scheme > Edit Scheme > Run > Options > StoreKit Configuration = Products.storekit, then stop/run again.
                """
            } else {
                let loadedIDs = Set(loaded.map(\.id))
                let missing = ids.filter { !loadedIDs.contains($0) }
                if !missing.isEmpty {
                    loadError = "Store returned partial products. Missing IDs: \(missing.joined(separator: ", "))"
                }
            }

            if products.isEmpty && attempt == 1 {
                try? await Task.sleep(nanoseconds: 800_000_000)
                await loadProducts(attempt: 2)
            }
        } catch {
            let ids = PremiumProductID.allCases.map(\.rawValue).joined(separator: ", ")
            loadError = "StoreKit error: \(error.localizedDescription). Requested IDs: \(ids)"
            if attempt == 1 {
                try? await Task.sleep(nanoseconds: 800_000_000)
                await loadProducts(attempt: 2)
            }
        }
    }

    func purchase(_ product: Product) async -> Bool {
        purchaseInProgress = true
        purchaseError = nil
        defer { purchaseInProgress = false }

        do {
            let result = try await product.purchase()
            switch result {
            case .success(let verification):
                switch verification {
                case .verified(let transaction):
                    if PremiumProductID(rawValue: transaction.productID) != nil {
                        isPremiumUnlocked = true
                        RatingPromptManager.shared.requestRatingIfNeeded()
                    }
                    await transaction.finish()
                    await refreshEntitlements()
                    return true
                case .unverified(_, let error):
                    purchaseError = error.localizedDescription
                    return false
                }
            case .userCancelled:
                return false
            case .pending:
                purchaseError = "Purchase is pending (e.g. Ask to Buy)."
                return false
            @unknown default:
                return false
            }
        } catch {
            purchaseError = error.localizedDescription
            return false
        }
    }

    func restorePurchases() async {
        restoreInProgress = true
        restoreMessage = nil
        loadError = nil
        defer { restoreInProgress = false }
        do {
            try await AppStore.sync()
            await refreshEntitlements()
            if isPremiumUnlocked {
                restoreMessage = "Purchases restored successfully."
            } else {
                restoreMessage = "No active purchases were found for this Apple ID."
            }
        } catch {
            loadError = error.localizedDescription
            restoreMessage = nil
        }
    }

    func refreshEntitlements() async {
        let state = await resolvePremiumState()
        isPremiumUnlocked = state.isUnlocked
        activePremiumProductID = state.activeProductID
    }

    private func listenForTransactions() async {
        for await result in Transaction.updates {
            guard case .verified(let transaction) = result else { continue }
            await transaction.finish()
            await refreshEntitlements()
        }
    }

    private func resolvePremiumState() async -> (isUnlocked: Bool, activeProductID: PremiumProductID?) {
        let validIDs = Set(PremiumProductID.allCases.map(\.rawValue))
        var found: [PremiumProductID] = []

        for await result in Transaction.currentEntitlements {
            guard case .verified(let transaction) = result else { continue }
            guard validIDs.contains(transaction.productID) else { continue }
            if isTransactionActive(transaction) {
                if let productID = PremiumProductID(rawValue: transaction.productID) {
                    found.append(productID)
                }
            }
        }

        if let preferred = preferredActiveProductID(from: found) {
            return (true, preferred)
        }

        // Fallback path for cases where `currentEntitlements` is briefly empty.
        found.removeAll()
        for id in PremiumProductID.allCases.map(\.rawValue) {
            guard let latestResult = await Transaction.latest(for: id) else { continue }
            guard case .verified(let transaction) = latestResult else { continue }
            if isTransactionActive(transaction) {
                if let productID = PremiumProductID(rawValue: transaction.productID) {
                    found.append(productID)
                }
            }
        }

        if let preferred = preferredActiveProductID(from: found) {
            return (true, preferred)
        }

        return (false, nil)
    }

    private func isTransactionActive(_ transaction: Transaction) -> Bool {
        if transaction.revocationDate != nil {
            return false
        }
        if let expirationDate = transaction.expirationDate, expirationDate <= Date() {
            return false
        }
        return true
    }

    private func preferredActiveProductID(from ids: [PremiumProductID]) -> PremiumProductID? {
        if ids.contains(.perpetual) {
            return .perpetual
        }
        if ids.contains(.yearly) {
            return .yearly
        }
        return nil
    }
}
