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

import Foundation
import StoreKit
import LeStorage

public enum StoreManagerError: Error {
    case failedVerification
    case missingPlan
}

protocol StoreDelegate {
    func productsReceived(products: [Product])
    func errorDidOccur(error: Error)
}

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

class StoreManager {
    
    @Published private(set) var purchasedTransactions = Set<StoreKit.Transaction>()

    var delegate: StoreDelegate? = nil
    
    var updateListenerTask: Task<Void, Error>? = nil

    init(delegate: StoreDelegate?) {
        
        self.delegate = delegate
        self.updateListenerTask = listenForTransactions()

        Task {
            //Initialize the store by starting a product request.
            await self.requestProducts()
        }
    }
    
    deinit {
        self.updateListenerTask?.cancel()
    }

    @MainActor
    func requestProducts() async {
        do {
//            let identifiers: [String] = StoreItem.allCases.map { $0.rawValue }
            
            var products: [Product] = try await Product.products(for: self._productIdentifiers())
            products = products.sorted { p1, p2 in
                return p2.price > p1.price
            }
            
            Logger.log("products = \(products.count)")
            self.delegate?.productsReceived(products: products)
        } catch {
            self.delegate?.errorDidOccur(error: error)
            Logger.error(error)
        }
    }
    
    fileprivate func _productIdentifiers() -> [String] {
        var items: [StoreItem] = []
        switch Guard.main.currentPlan {
        case .fivePerMonth:
            items = [StoreItem.unit, StoreItem.monthlyUnlimited]
        case .monthlyUnlimited:
            break
        default:
            items = StoreItem.allCases
        }
        return items.map { $0.rawValue }
    }
    
    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 await Guard.main.processTransactionResult(result)

                    //Always finish a transaction.
                    await transaction.finish()
                } catch {
                    self.delegate?.errorDidOccur(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 purchase(_ product: Product, quantity: Int? = nil) async throws -> StoreKit.Transaction? {
        Logger.log("Store purchase started...")
        
        guard let userId = Store.main.userId, let uuid: UUID = UUID(uuidString: userId) else {
            throw StoreError.missingUserId
        }
        
        var options: Set<Product.PurchaseOption> = []
        let tokenOption = Product.PurchaseOption.appAccountToken(uuid)
        options.insert(tokenOption)
        
        if let quantity = quantity {
            let quantityOption = Product.PurchaseOption.quantity(quantity)
            options.insert(quantityOption)
        }
        
        let result = try await product.purchase(options: options)
        
        Logger.log("Store purchase ended with result: \(result)")

        switch result {
        case .success(let verificationResult):
            
            let transaction = try await Guard.main.processTransactionResult(verificationResult)
            
            // Always finish a transaction.
            await transaction.finish()

            DispatchQueue.main.asyncAfter(deadline: DispatchTime(uptimeNanoseconds: 100000), execute: {
                NotificationCenter.default.post(name: Notification.Name.StoreEventHappened, object: nil)
            })
                                          
            return transaction
        case .userCancelled, .pending:
            return nil
        default:
            return nil
        }
    }

}
