//
//  Guard.swift
//  Poker Analytics 6
//
//  Created by Laurent Morvillier on 20/04/2022.
//

import Foundation
import StoreKit

public enum StoreError: Error {
    case failedVerification
}

enum StorePlan : String, CaseIterable {
    case none
    case unlimited = "com.staxriver.lecountdown.unlimited"
}

extension Notification.Name {
    static let StoreEventHappened = Notification.Name("storeEventHappened")
}

@objc class AppGuard : NSObject {
    
    static var main: AppGuard = AppGuard()
    
    @Published private(set) var purchasedTransactions = Set<StoreKit.Transaction>()

    var currentBestPlan: StoreKit.Transaction? = nil
    
    var updateListenerTask: Task<Void, Error>? = nil

    override init() {
        
        super.init()
        
        self.updateListenerTask = self.listenForTransactions()

        Task {
            await self.refreshPurchasedProducts()
        }
    }
    
    func refreshPurchasedProducts() async {
        
        // Iterate through the user's purchased products.
        for await verificationResult in Transaction.currentEntitlements {
            do {
                let transaction = try await self.processTransactionResult(verificationResult)
                DispatchQueue.main.async {
                    NotificationCenter.default.post(name: Notification.Name.StoreEventHappened, object: nil)
                }
                await transaction.finish()
            } catch {
                Logger.error(error)
            }
        }
    }
    
    func listenForTransactions() -> Task<Void, Error> {
        return Task.detached {
            //Iterate through any transactions which didn't come from a direct call to `purchase()`.
            for await result in Transaction.updates {
                do {
                    let transaction = try self.checkVerified(result)

                    //Deliver content to the user.
                    await self.updatePurchasedIdentifiers(transaction)

                    //Always finish a transaction.
                    await transaction.finish()
                } catch {
                    Logger.error(error)
                    //StoreKit has a receipt it can read but it failed verification. Don't deliver content to the user.
                    print("Transaction failed verification")
                }
            }
        }
    }
    
    func checkVerified<T>(_ result: VerificationResult<T>) throws -> T {
        //Check if the transaction passes StoreKit verification.
        switch result {
        case .unverified:
            //StoreKit has parsed the JWS but failed verification. Don't deliver content to the user.
            throw StoreError.failedVerification
        case .verified(let safe):
            //If the transaction is verified, unwrap and return it.
            return safe
        }
    }
    
    @MainActor
    func updatePurchasedIdentifiers(_ transaction: StoreKit.Transaction) async {
        if transaction.revocationDate == nil {
            // If the App Store has not revoked the transaction, add it to the list of `purchasedIdentifiers`.
            purchasedTransactions.insert(transaction)
        } else {
            // If the App Store has revoked this transaction, remove it from the list of `purchasedIdentifiers`.
            purchasedTransactions.remove(transaction)
        }
        
        self._updateBestPlan()
    }
    
    func processTransactionResult(_ result: VerificationResult<StoreKit.Transaction>) async throws -> StoreKit.Transaction {
        
        let transaction = try checkVerified(result)

        // Deliver content to the user.
        await updatePurchasedIdentifiers(transaction)

        return transaction
    }
    
    var isAuthorized: Bool {
        return self.currentPlan == .unlimited
    }
    
    var currentPlan: StorePlan {
        
        #if DEBUG
        return .unlimited
        #else
        if let currentBestPlan = self.currentBestPlan, let plan = StorePlan(rawValue: currentBestPlan.productID) {
            return plan
        }
        if let vf = Preferences.verifiedTransaction(),
           vf.expiryDate > Date(), vf.graceDate > Date(),
           let plan = StorePlan(rawValue: vf.productId) {
            return plan
        }
        return .none
        #endif
    }
    
    fileprivate func _updateBestPlan() {

        if let unlimited = self.purchasedTransactions.first(where: { $0.productID == StorePlan.unlimited.rawValue }) {
            self.currentBestPlan = unlimited
        } else {
            self.currentBestPlan = nil
        }

    }
    
}
