//
//  LNPurchaseManager.swift
//  Lanu
//
//  Created by OneeChan on 2025/11/6.
//

import Foundation
import StoreKit


var myWalletInfo: LNUserWalletInfo {
    LNPurchaseManager.shared.myWalletInfo
}


protocol LNPurchaseManagerNotify {
    func onUserWalletInfoChanged(info: LNUserWalletInfo)
    func onUserPurchaseResult(err: LNPurchaseError?)
}
extension LNPurchaseManagerNotify {
    func onUserWalletInfoChanged(info: LNUserWalletInfo) {}
    func onUserPurchaseResult(err: LNPurchaseError?) {}
}


enum LNPurchaseError: LocalizedError {
    case productNotFound
    case createOrderfailed
    case paymentInvalid
    case paymentCancelled
    case paymentFailed(Error)
    case receiptVerifyFailed
    case receiptParseFailed
    case unknownError(Error?)
    
    var errorDesc: String? {
        switch self {
        case .productNotFound: return .init(key: "A00195")
        case .createOrderfailed: return .init(key: "A00196")
        case .paymentInvalid: return .init(key: "A00281")
        case .paymentCancelled: return .init(key: "A00197")
        case .paymentFailed(let error): return .init(key: "A00198", error.localizedDescription)
        case .receiptVerifyFailed: return .init(key: "A00199")
        case .receiptParseFailed: return .init(key: "A00282")
        case .unknownError(let error): return .init(key: "A00283", error?.localizedDescription ?? "")
        }
    }
}


class LNPurchaseManager {
    static let shared = LNPurchaseManager()
    
    private(set) var myWalletInfo: LNUserWalletInfo = LNUserWalletInfo()
    
    private let lock = NSLock()
    private var goodsCache: [LNCurrencyType: [LNPurchaseGoodsVO]] = [:]
    private var productMap: [String: Product] = [:]
    private var transactionListeningTask: Task<Void, Never>?
    
    private init() {
        LNEventDeliver.addObserver(self)
    }
    
    func goodsFro(_ code: String) -> (LNCurrencyType, LNPurchaseGoodsVO)? {
        for (type, list) in goodsCache {
            if let goods = list.first(where: { $0.code == code }) {
                return (type, goods)
            }
        }
        return nil
    }
    
    func reloadWalletInfo() {
        LNHttpManager.shared.getWalletInfo { [weak self] res, err in
            guard let self else { return }
            guard let res, err == nil else { return }
            myWalletInfo = LNUserWalletInfo()
            myWalletInfo.diamond = res.diamond
            myWalletInfo.coin = res.goldCoin
            myWalletInfo.bean = res.beanTotal
            myWalletInfo.unsettledBean = res.unsettledBean
            myWalletInfo.availableBean = res.availableBean
            notifyWalletInfoChanged()
        }
    }
    
    func updateWallet(diamond: Double? = nil, coin: Double? = nil) {
        if let diamond {
            myWalletInfo.diamond = diamond
        }
        if let coin {
            myWalletInfo.coin = coin
        }
        notifyWalletInfoChanged()
    }
    
    func loadGoodsList(currencyType: LNCurrencyType,
                       queue: DispatchQueue = .main,
                       handler: @escaping ([LNPurchaseGoodsVO]?) -> Void)
    {
        lock.lock()
        let cache = goodsCache[currencyType]
        lock.unlock()
        
        var cacheAvailable = false
        if let cache, !cache.isEmpty {
            // 如果有缓存，先返回缓存，并且触发更新(但是不回调)
            cacheAvailable = true
            handler(cache)
        }
        LNHttpManager.shared.getGoodsList(currencyType: currencyType) { [weak self] res, err in
            guard let self else { return }
            if let res, err == nil {
                var codes = Set<String>()
                lock.lock()
                goodsCache[currencyType] = res.items
                res.items.forEach {
                    codes.insert($0.code)
                }
                lock.unlock()
                getRechargeProductInfo(ids: codes, handler: nil)
                RechargeManager.shared.loadRechargeProducts(productIds: codes.sorted(), completion: nil)
            }
            if !cacheAvailable {
                // 如果前面没有缓存，则在更新时触发回调
                queue.asyncIfNotGlobal {
                    handler(res?.items)
                }
            }
        }
    }
}
 
extension LNPurchaseManager {
    func exchangeCurrency(exchangeType: LNExchangeType,
                          amount: Double, queue: DispatchQueue = .main,
                          handler: @escaping (Bool) -> Void)
    {
        let (from, to) = exchangeType.exchangeType
        LNHttpManager.shared.exchangeCurrency(from: from, to: to, amount: amount) { [weak self] err in
            guard let self else { return }
            queue.asyncIfNotGlobal {
                handler(err == nil)
            }
            if let err {
                showToast(err.errorDesc)
            } else {
                reloadWalletInfo()
            }
        }
    }
    
    func expectResult(exchangeType: LNExchangeType,
                      count: Double, queue: DispatchQueue = .main,
                      handler: @escaping (Double?) -> Void) {
        let (from, to) = exchangeType.exchangeType
        LNHttpManager.shared.getExchangeExpectResult(from: from, to: to, count: count) { res, err in
            queue.asyncIfNotGlobal {
                handler(res?.amount)
            }
            if let err {
                showToast(err.errorDesc)
            }
        }
    }
}

extension LNPurchaseManager: LNAccountManagerNotify {
    func onUserLogin() {
        reloadWalletInfo()
        
        // 拉取新的商品列表
        loadGoodsList(currencyType: .coin) { _ in }
        loadGoodsList(currencyType: .diamond) { _ in }

//        restoreCompletedTransactions()
//        startObservingTransactionUpdates()
    }
    
    func onUserLogout() {
        myWalletInfo = LNUserWalletInfo()
        
        transactionListeningTask?.cancel()
        transactionListeningTask = nil
    }
}

extension LNPurchaseManager {
    func purchaseProduct(goods: LNPurchaseGoodsVO) {
        guard AppStore.canMakePayments else {
            log("In-app purchase is not enable")
            notifyPurchaseResult(err: .paymentInvalid)
            return
        }
        
        getRechargeProductInfo(ids: [goods.code]) { [weak self] list in
            guard let self else { return }
            guard let product = list.first else {
                log("product not found for \(goods.code)")
                notifyPurchaseResult(err: .productNotFound)
                return
            }
            
            LNHttpManager.shared.createPurchase(id: goods.id) { [weak self] res, err in
                guard let self else { return }
                guard let res, err == nil else {
                    log("create order failed \(String(describing: err?.errorDesc))")
                    notifyPurchaseResult(err: .createOrderfailed)
                    return
                }
                doPurchase(orderId: res.result, product: product)
            }
        }
    }
    
    private func doPurchase(orderId: String, product: Product) {
        Task { [weak self] in
            guard let self else { return }
                
            do {
                log("start puchase order \(orderId) product \(product.id)")
                
                let uuid = Product.PurchaseOption.appAccountToken(UUID(uuidString: orderId)!)
                let result = try await product.purchase(options: [uuid])
                    
                switch result {
                case .success(let verification):
                    // 验证收据
                    await checkTransationVerify(result: verification)
                case .userCancelled:
                    log("puchase order \(orderId) product \(product.id) user cancelled")
                    notifyPurchaseResult(err: .paymentCancelled)
                case .pending:
                    break
                @unknown default:
                    log("puchase order \(orderId) product \(product.id) return unknown err")
                    notifyPurchaseResult(err: .unknownError(nil))
                }
            } catch (let err) {
                log("puchase order \(orderId) product \(product.id) return err \(err)")
                notifyPurchaseResult(err: .paymentFailed(err))
            }
        }
    }
    
    private func restoreCompletedTransactions() {
        Task { [weak self] in
            guard let self else { return }
            
            for await result in Transaction.currentEntitlements {
                // 验证收据
                await checkTransationVerify(result: result)
            }
        }
    }
    
    private func startObservingTransactionUpdates() {
        // 避免重复创建Task
        guard transactionListeningTask == nil else { return }
        
        transactionListeningTask = Task(priority: .background) { [weak self] in
            guard let self = self else { return }
            
            // 持续监听所有交易更新（异步序列）
            for await transactionUpdate in Transaction.updates {
                // 验证收据
                await checkTransationVerify(result: transactionUpdate)
            }
        }
    }
    
    private func getRechargeProductInfo(ids: Set<String>, handler: (([Product]) -> Void)?) {
        let validIds = ids.filter { !$0.isEmpty }
        guard !validIds.isEmpty else {
            handler?([])
            return
        }
        Task {
            guard AppStore.canMakePayments else {
                handler?([])
                return
            }
            
            // 检查缓存
            let cachedProducts = validIds.compactMap { productMap[$0] }
            if cachedProducts.count == validIds.count {
                handler?(cachedProducts)
                return
            }
            
            do {
                let products = try await Product.products(for: Set(ids))
                guard !products.isEmpty else {
                    handler?([])
                    return
                }
                
                products.forEach {
                    productMap[$0.id] = $0
                }
                handler?(products)
            } catch (_) {
                handler?([])
            }
        }
    }
    
    private func checkTransationVerify(result: VerificationResult<Transaction>) async {
        switch result {
        case .unverified(let transaction, let error):
            if let orderId = transaction.appAccountToken?.uuidString {
                log("puchase order \(orderId) product \(transaction.productID) user unverified err \(error)")
            }
            
            await transaction.finish()
            notifyPurchaseResult(err: .receiptVerifyFailed)
        case .verified(let transaction):
            guard let orderId = transaction.appAccountToken?.uuidString else {
                notifyPurchaseResult(err: .receiptVerifyFailed)
                return
            }
            log("puchase order \(orderId) product \(transaction.productID) user verified")
            
            guard let receiptURL = Bundle.main.appStoreReceiptURL,
                  FileManager.default.fileExists(atPath: receiptURL.path),
                  let receiptData = try? Data(contentsOf: receiptURL)
            else {
                log("can't find appStore Receipt File data")
                notifyPurchaseResult(err: .receiptParseFailed)
                return
            }
            
            let receiptBase64 = receiptData.base64EncodedString()
            log("start verify order \(orderId)")
            LNHttpManager.shared.verifyPurchase(orderId: orderId, receipt: receiptBase64) { [weak self] err in
                guard let self else { return }
                log("verify order \(orderId) return err \(String(describing: err?.errorDesc))")
                notifyPurchaseResult(err: err == nil ? nil : .receiptVerifyFailed)
            }
            
        }
    }
}

extension LNPurchaseManager {
    private func notifyWalletInfoChanged() {
        let info = myWalletInfo
        LNEventDeliver.notifyEvent { ($0 as? LNPurchaseManagerNotify)?.onUserWalletInfoChanged(info: info) }
    }
    
    private func notifyPurchaseResult(err: LNPurchaseError?) {
        LNEventDeliver.notifyEvent {
            ($0 as? LNPurchaseManagerNotify)?.onUserPurchaseResult(err: err)
        }
    }
}

extension LNPurchaseManager {
    func log(_ items: Any...,) {
        Log.w("-----> LNPurchaseManager <----- ", items)
    }
}
