import Foundation
import UIKit
import AsyncDisplayKit
import Display
import Postbox
import TelegramCore
import SwiftSignalKit
import PassKit
import TelegramPresentationData
import ItemListUI
import AccountContext
import AlertUI
import TelegramNotices
import TelegramStringFormatting
import PasswordSetupUI
import Stripe
import LocalAuth

final class BotCheckoutControllerArguments {
    fileprivate let account: Account
    fileprivate let openInfo: (BotCheckoutInfoControllerFocus) -> Void
    fileprivate let openPaymentMethod: () -> Void
    fileprivate let openShippingMethod: () -> Void
    
    fileprivate init(account: Account, openInfo: @escaping (BotCheckoutInfoControllerFocus) -> Void, openPaymentMethod: @escaping () -> Void, openShippingMethod: @escaping () -> Void) {
        self.account = account
        self.openInfo = openInfo
        self.openPaymentMethod = openPaymentMethod
        self.openShippingMethod = openShippingMethod
    }
}

private enum BotCheckoutSection: Int32 {
    case header
    case prices
    case info
}

enum BotCheckoutEntry: ItemListNodeEntry {
    case header(PresentationTheme, TelegramMediaInvoice, String)
    case price(Int, PresentationTheme, String, String, Bool)
    case paymentMethod(PresentationTheme, String, String)
    case shippingInfo(PresentationTheme, String, String)
    case shippingMethod(PresentationTheme, String, String)
    case nameInfo(PresentationTheme, String, String)
    case emailInfo(PresentationTheme, String, String)
    case phoneInfo(PresentationTheme, String, String)
    
    var section: ItemListSectionId {
        switch self {
            case .header:
                return BotCheckoutSection.header.rawValue
            case .price:
                return BotCheckoutSection.prices.rawValue
            default:
                return BotCheckoutSection.info.rawValue
        }
    }
    
    var stableId: Int32 {
        switch self {
            case .header:
                return 0
            case let .price(index, _, _, _, _):
                return 1 + Int32(index)
            case .paymentMethod:
                return 10000 + 0
            case .shippingInfo:
                return 10000 + 1
            case .shippingMethod:
                return 10000 + 2
            case .nameInfo:
                return 10000 + 3
            case .emailInfo:
                return 10000 + 4
            case .phoneInfo:
                return 10000 + 5
        }
    }
    
    static func ==(lhs: BotCheckoutEntry, rhs: BotCheckoutEntry) -> Bool {
        switch lhs {
            case let .header(lhsTheme, lhsInvoice, lhsName):
                if case let .header(rhsTheme, rhsInvoice, rhsName) = rhs {
                    if lhsTheme !== rhsTheme {
                        return false
                    }
                    if !lhsInvoice.isEqual(to: rhsInvoice) {
                        return false
                    }
                    if lhsName != rhsName {
                        return false
                    }
                    return true
                } else {
                    return false
                }
            case let .price(lhsIndex, lhsTheme, lhsText, lhsValue, lhsFinal):
                if case let .price(rhsIndex, rhsTheme, rhsText, rhsValue, rhsFinal) = rhs {
                    if lhsIndex != rhsIndex {
                        return false
                    }
                    if lhsTheme !== rhsTheme {
                        return false
                    }
                    if lhsText != rhsText {
                        return false
                    }
                    if lhsValue != rhsValue {
                        return false
                    }
                    if lhsFinal != rhsFinal {
                        return false
                    }
                    return true
                } else {
                    return false
                }
            case let .paymentMethod(lhsTheme, lhsText, lhsValue):
                if case let .paymentMethod(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
                    return true
                } else {
                    return false
                }
            case let .shippingInfo(lhsTheme, lhsText, lhsValue):
                if case let .shippingInfo(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
                    return true
                } else {
                    return false
                }
            case let .shippingMethod(lhsTheme, lhsText, lhsValue):
                if case let .shippingMethod(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
                    return true
                } else {
                    return false
                }
            case let .nameInfo(lhsTheme, lhsText, lhsValue):
                if case let .nameInfo(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
                    return true
                } else {
                    return false
                }
            case let .emailInfo(lhsTheme, lhsText, lhsValue):
                if case let .emailInfo(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
                    return true
                } else {
                    return false
                }
            case let .phoneInfo(lhsTheme, lhsText, lhsValue):
                if case let .phoneInfo(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
                    return true
                } else {
                    return false
                }
        }
    }
    
    static func <(lhs: BotCheckoutEntry, rhs: BotCheckoutEntry) -> Bool {
        return lhs.stableId < rhs.stableId
    }
    
    func item(_ arguments: BotCheckoutControllerArguments) -> ListViewItem {
        switch self {
            case let .header(theme, invoice, botName):
                return BotCheckoutHeaderItem(account: arguments.account, theme: theme, invoice: invoice, botName: botName, sectionId: self.section)
            case let .price(_, theme, text, value, isFinal):
                return BotCheckoutPriceItem(theme: theme, title: text, label: value, isFinal: isFinal, sectionId: self.section)
            case let .paymentMethod(theme, text, value):
                return ItemListDisclosureItem(theme: theme, title: text, label: value, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: {
                    arguments.openPaymentMethod()
                })
            case let .shippingInfo(theme, text, value):
                return ItemListDisclosureItem(theme: theme, title: text, label: value, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: {
                    arguments.openInfo(.address(.street1))
                })
            case let .shippingMethod(theme, text, value):
                return ItemListDisclosureItem(theme: theme, title: text, label: value, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: {
                    arguments.openShippingMethod()
                })
            case let .nameInfo(theme, text, value):
                return ItemListDisclosureItem(theme: theme, title: text, label: value, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: {
                    arguments.openInfo(.name)
                })
            case let .emailInfo(theme, text, value):
                return ItemListDisclosureItem(theme: theme, title: text, label: value, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: {
                    arguments.openInfo(.email)
                })
            case let .phoneInfo(theme, text, value):
                return ItemListDisclosureItem(theme: theme, title: text, label: value, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: {
                    arguments.openInfo(.phone)
                })
        }
    }
}

private struct BotCheckoutControllerState: Equatable {
    init() {
    }
    
    static func ==(lhs: BotCheckoutControllerState, rhs: BotCheckoutControllerState) -> Bool {
        return true
    }
}

private func currentTotalPrice(paymentForm: BotPaymentForm?, validatedFormInfo: BotPaymentValidatedFormInfo?, currentShippingOptionId: String?) -> Int64 {
    guard let paymentForm = paymentForm else {
        return 0
    }
    
    var totalPrice: Int64 = 0
    
    var index = 0
    for price in paymentForm.invoice.prices {
        totalPrice += price.amount
        index += 1
    }
    
    if let validatedFormInfo = validatedFormInfo, let shippingOptions = validatedFormInfo.shippingOptions {
        if let currentShippingOptionId = currentShippingOptionId {
            for option in shippingOptions {
                if option.id == currentShippingOptionId {
                    for price in option.prices {
                        totalPrice += price.amount
                    }
                    break
                }
            }
        }
    }
    
    return totalPrice
}

private func botCheckoutControllerEntries(presentationData: PresentationData, state: BotCheckoutControllerState, invoice: TelegramMediaInvoice, paymentForm: BotPaymentForm?, formInfo: BotPaymentRequestedInfo?, validatedFormInfo: BotPaymentValidatedFormInfo?, currentShippingOptionId: String?, currentPaymentMethod: BotCheckoutPaymentMethod?, botPeer: Peer?) -> [BotCheckoutEntry] {
    var entries: [BotCheckoutEntry] = []
    
    var botName = ""
    if let botPeer = botPeer {
        botName = botPeer.displayTitle
    }
    entries.append(.header(presentationData.theme, invoice, botName))
    
    if let paymentForm = paymentForm {
        var totalPrice: Int64 = 0
        
        var index = 0
        for price in paymentForm.invoice.prices {
            entries.append(.price(index, presentationData.theme, price.label, formatCurrencyAmount(price.amount, currency: paymentForm.invoice.currency), false))
            totalPrice += price.amount
            index += 1
        }
        
        var shippingOptionString: String?
        if let validatedFormInfo = validatedFormInfo, let shippingOptions = validatedFormInfo.shippingOptions {
            shippingOptionString = ""
            if let currentShippingOptionId = currentShippingOptionId {
                for option in shippingOptions {
                    if option.id == currentShippingOptionId {
                        shippingOptionString = option.title
                        
                        for price in option.prices {
                            entries.append(.price(index, presentationData.theme, price.label, formatCurrencyAmount(price.amount, currency: paymentForm.invoice.currency), false))
                            totalPrice += price.amount
                            index += 1
                        }
                        
                        break
                    }
                }
            }
        }
        
        entries.append(.price(index, presentationData.theme, presentationData.strings.Checkout_TotalAmount, formatCurrencyAmount(totalPrice, currency: paymentForm.invoice.currency), true))
        
        var paymentMethodTitle = ""
        if let currentPaymentMethod = currentPaymentMethod {
            paymentMethodTitle = currentPaymentMethod.title
        }
        entries.append(.paymentMethod(presentationData.theme, presentationData.strings.Checkout_PaymentMethod, paymentMethodTitle))
        if paymentForm.invoice.requestedFields.contains(.shippingAddress) {
            var addressString = ""
            if let address = formInfo?.shippingAddress {
                let components: [String] = [
                    address.city,
                    address.streetLine1,
                    address.streetLine2,
                    address.state
                ]
                for component in components {
                    if !component.isEmpty {
                        if !addressString.isEmpty {
                            addressString.append(", ")
                        }
                        addressString.append(component)
                    }
                }
            }
            entries.append(.shippingInfo(presentationData.theme, presentationData.strings.Checkout_ShippingAddress, addressString))
            
            if let shippingOptionString = shippingOptionString {
                entries.append(.shippingMethod(presentationData.theme, presentationData.strings.Checkout_ShippingMethod, shippingOptionString))
            }
        }
        
        if paymentForm.invoice.requestedFields.contains(.name) {
            entries.append(.nameInfo(presentationData.theme, presentationData.strings.Checkout_Name, formInfo?.name ?? ""))
        }
        
        if paymentForm.invoice.requestedFields.contains(.email) {
            entries.append(.emailInfo(presentationData.theme, presentationData.strings.Checkout_Email, formInfo?.email ?? ""))
        }
        
        if paymentForm.invoice.requestedFields.contains(.phone) {
            entries.append(.phoneInfo(presentationData.theme, presentationData.strings.Checkout_Phone, formInfo?.phone ?? ""))
        }
    }
    
    return entries
}

private let hasApplePaySupport: Bool = PKPaymentAuthorizationViewController.canMakePayments(usingNetworks: [.visa, .masterCard, .amex])

private func formSupportApplePay(_ paymentForm: BotPaymentForm) -> Bool {
    if !hasApplePaySupport {
        return false
    }
    guard let nativeProvider = paymentForm.nativeProvider else {
        return false
    }
    let applePayProviders = Set<String>([
        "stripe",
        "sberbank",
        "yandex",
        "privatbank"
    ])
    if !applePayProviders.contains(nativeProvider.name) {
        return false
    }
    guard let nativeParamsData = nativeProvider.params.data(using: .utf8) else {
        return false
    }
    guard let nativeParams = (try? JSONSerialization.jsonObject(with: nativeParamsData, options: [])) as? [String: Any] else {
        return false
    }
    
    var merchantId: String?
    if nativeProvider.name == "stripe" {
        merchantId = "merchant.ph.telegra.Telegraph"
    } else if let paramsId = nativeParams["apple_pay_merchant_id"] as? String {
        merchantId = paramsId
    }
    
    return merchantId != nil
}

private func availablePaymentMethods(form: BotPaymentForm, current: BotCheckoutPaymentMethod?) -> [BotCheckoutPaymentMethod] {
    var methods: [BotCheckoutPaymentMethod] = []
    if formSupportApplePay(form) && hasApplePaySupport {
        methods.append(.applePay)
    }
    if let current = current {
        if !methods.contains(current) {
            methods.append(current)
        }
    }
    return methods
}

final class BotCheckoutControllerNode: ItemListControllerNode<BotCheckoutEntry>, PKPaymentAuthorizationViewControllerDelegate {
    private let context: AccountContext
    private let messageId: MessageId
    private let present: (ViewController, Any?) -> Void
    private let dismissAnimated: () -> Void
    
    private var stateValue = BotCheckoutControllerState()
    private let state = ValuePromise(BotCheckoutControllerState(), ignoreRepeated: true)
    private var arguments: BotCheckoutControllerArguments?
    
    private var presentationData: PresentationData
    
    private let paymentFormAndInfo = Promise<(BotPaymentForm, BotPaymentRequestedInfo, BotPaymentValidatedFormInfo?, String?, BotCheckoutPaymentMethod?)?>(nil)
    private var paymentFormValue: BotPaymentForm?
    private var currentFormInfo: BotPaymentRequestedInfo?
    private var currentValidatedFormInfo: BotPaymentValidatedFormInfo?
    private var currentShippingOptionId: String?
    private var currentPaymentMethod: BotCheckoutPaymentMethod?
    private var formRequestDisposable: Disposable?
    
    private let actionButton: BotCheckoutActionButton
    private let inProgressDimNode: ASDisplayNode
    
    private let payDisposable = MetaDisposable()
    private let paymentAuthDisposable = MetaDisposable()
    private var applePayAuthrorizationCompletion: ((PKPaymentAuthorizationStatus) -> Void)?
    private var applePayController: PKPaymentAuthorizationViewController?
    
    init(controller: ItemListController<BotCheckoutEntry>?, navigationBar: NavigationBar, updateNavigationOffset: @escaping (CGFloat) -> Void, context: AccountContext, invoice: TelegramMediaInvoice, messageId: MessageId, present: @escaping (ViewController, Any?) -> Void, dismissAnimated: @escaping () -> Void) {
        self.context = context
        self.messageId = messageId
        self.present = present
        self.dismissAnimated = dismissAnimated
        
        self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
        
        var openInfoImpl: ((BotCheckoutInfoControllerFocus) -> Void)?
        var openPaymentMethodImpl: (() -> Void)?
        var openShippingMethodImpl: (() -> Void)?
        
        let arguments = BotCheckoutControllerArguments(account: context.account, openInfo: { item in
            openInfoImpl?(item)
        }, openPaymentMethod: {
            openPaymentMethodImpl?()
        }, openShippingMethod: {
            openShippingMethodImpl?()
        })
        
        let signal: Signal<(PresentationTheme, (ItemListNodeState<BotCheckoutEntry>, BotCheckoutEntry.ItemGenerationArguments)), NoError> = combineLatest(context.sharedContext.presentationData, self.state.get(), paymentFormAndInfo.get(), context.account.postbox.loadedPeerWithId(messageId.peerId))
            |> map { presentationData, state, paymentFormAndInfo, botPeer -> (PresentationTheme, (ItemListNodeState<BotCheckoutEntry>, BotCheckoutEntry.ItemGenerationArguments)) in
                let nodeState = ItemListNodeState(entries: botCheckoutControllerEntries(presentationData: presentationData, state: state, invoice: invoice, paymentForm: paymentFormAndInfo?.0, formInfo: paymentFormAndInfo?.1, validatedFormInfo: paymentFormAndInfo?.2, currentShippingOptionId: paymentFormAndInfo?.3, currentPaymentMethod: paymentFormAndInfo?.4, botPeer: botPeer), style: .plain, focusItemTag: nil, emptyStateItem: nil, animateChanges: false)
                
                return (presentationData.theme, (nodeState, arguments))
            }
        
        self.actionButton = BotCheckoutActionButton(inactiveFillColor: self.presentationData.theme.list.plainBackgroundColor, activeFillColor: self.presentationData.theme.list.itemAccentColor, foregroundColor: self.presentationData.theme.list.itemCheckColors.foregroundColor)
        self.actionButton.setState(.loading)
        
        self.inProgressDimNode = ASDisplayNode()
        self.inProgressDimNode.alpha = 0.0
        self.inProgressDimNode.isUserInteractionEnabled = false
        self.inProgressDimNode.backgroundColor = self.presentationData.theme.list.plainBackgroundColor.withAlphaComponent(0.5)
        
        super.init(controller: controller, navigationBar: navigationBar, updateNavigationOffset: updateNavigationOffset, state: signal)
        
        self.arguments = arguments
        
        openInfoImpl = { [weak self] focus in
            if let strongSelf = self, let paymentFormValue = strongSelf.paymentFormValue, let currentFormInfo = strongSelf.currentFormInfo {
                strongSelf.present(BotCheckoutInfoController(context: context, invoice: paymentFormValue.invoice, messageId: messageId, initialFormInfo: currentFormInfo, focus: focus, formInfoUpdated: { formInfo, validatedInfo in
                    if let strongSelf = self, let paymentFormValue = strongSelf.paymentFormValue {
                        strongSelf.currentFormInfo = formInfo
                        strongSelf.currentValidatedFormInfo = validatedInfo
                        var updatedCurrentShippingOptionId: String?
                        if let currentShippingOptionId = strongSelf.currentShippingOptionId, let shippingOptions = validatedInfo.shippingOptions {
                            if shippingOptions.contains(where: { $0.id == currentShippingOptionId }) {
                                updatedCurrentShippingOptionId = currentShippingOptionId
                            }
                        }
                        strongSelf.paymentFormAndInfo.set(.single((paymentFormValue, formInfo, validatedInfo, updatedCurrentShippingOptionId, strongSelf.currentPaymentMethod)))
                        
                        strongSelf.updateActionButton()
                    }
                }), ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
            }
        }
        
        let applyPaymentMethod: (BotCheckoutPaymentMethod) -> Void = { [weak self] method in
            if let strongSelf = self, let paymentFormValue = strongSelf.paymentFormValue, let currentFormInfo = strongSelf.currentFormInfo {
                strongSelf.currentPaymentMethod = method
                strongSelf.paymentFormAndInfo.set(.single((paymentFormValue, currentFormInfo, strongSelf.currentValidatedFormInfo, strongSelf.currentShippingOptionId, strongSelf.currentPaymentMethod)))
            }
        }
        
        let openNewCard: () -> Void = { [weak self] in
            if let strongSelf = self, let paymentForm = strongSelf.paymentFormValue {
                if let nativeProvider = paymentForm.nativeProvider, nativeProvider.name == "stripe" {
                    guard let paramsData = nativeProvider.params.data(using: .utf8) else {
                        return
                    }
                    guard let nativeParams = (try? JSONSerialization.jsonObject(with: paramsData)) as? [String: Any] else {
                        return
                    }
                    guard let publishableKey = nativeParams["publishable_key"] as? String else {
                        return
                    }
                    
                    var additionalFields: BotCheckoutNativeCardEntryAdditionalFields = []
                    if let needCardholderName = nativeParams["need_cardholder_name"] as? NSNumber, needCardholderName.boolValue {
                        additionalFields.insert(.cardholderName)
                    }
                    if let needCountry = nativeParams["need_country"] as? NSNumber, needCountry.boolValue {
                        additionalFields.insert(.country)
                    }
                    if let needZip = nativeParams["need_zip"] as? NSNumber, needZip.boolValue {
                        additionalFields.insert(.zipCode)
                    }
                    
                    var dismissImpl: (() -> Void)?
                    let canSave = paymentForm.canSaveCredentials || paymentForm.passwordMissing
                    let controller = BotCheckoutNativeCardEntryController(context: strongSelf.context, additionalFields: additionalFields, publishableKey: publishableKey, completion: { method in
                        guard let strongSelf = self else {
                            return
                        }
                        if canSave && paymentForm.passwordMissing {
                            switch method {
                                case let .webToken(webToken) where webToken.saveOnServer:
                                    var text = strongSelf.presentationData.strings.Checkout_NewCard_SaveInfoEnableHelp
                                    text = text.replacingOccurrences(of: "[", with: "")
                                    text = text.replacingOccurrences(of: "]", with: "")
                                    present(textAlertController(context: strongSelf.context, title: nil, text: text, actions: [TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_NotNow, action: {
                                        var updatedToken = webToken
                                        updatedToken.saveOnServer = false
                                        applyPaymentMethod(.webToken(updatedToken))
                                    }), TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_Yes, action: {
                                        guard let strongSelf = self else {
                                            return
                                        }
                                        if paymentForm.passwordMissing {
                                            var updatedToken = webToken
                                            updatedToken.saveOnServer = false
                                            applyPaymentMethod(.webToken(updatedToken))
                                            
                                            let controller = SetupTwoStepVerificationController(context: strongSelf.context, initialState: .automatic, stateUpdated: { update, shouldDismiss, controller in
                                                if shouldDismiss {
                                                    controller.dismiss()
                                                }
                                                switch update {
                                                    case .noPassword, .awaitingEmailConfirmation:
                                                        break
                                                    case .passwordSet:
                                                        var updatedToken = webToken
                                                        updatedToken.saveOnServer = true
                                                        applyPaymentMethod(.webToken(updatedToken))
                                                }
                                            })
                                            strongSelf.present(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
                                        } else {
                                            var updatedToken = webToken
                                            updatedToken.saveOnServer = true
                                            applyPaymentMethod(.webToken(updatedToken))
                                        }
                                    })]), nil)
                                default:
                                    break
                            }
                        } else {
                            applyPaymentMethod(method)
                        }
                        dismissImpl?()
                    })
                    dismissImpl = { [weak controller] in
                        controller?.dismiss()
                    }
                    strongSelf.present(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
                } else {
                    var dismissImpl: (() -> Void)?
                    let controller = BotCheckoutWebInteractionController(context: context, url: paymentForm.url, intent: .addPaymentMethod({ [weak self] token in
                        dismissImpl?()
                        
                        guard let strongSelf = self else {
                            return
                        }
                        let canSave = paymentForm.canSaveCredentials || paymentForm.passwordMissing
                        let allowSaving = paymentForm.canSaveCredentials && !paymentForm.passwordMissing
                        if canSave {
                            present(textAlertController(context: strongSelf.context, title: nil, text: strongSelf.presentationData.strings.Checkout_NewCard_SaveInfoHelp, actions: [TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_NotNow, action: {
                                var updatedToken = token
                                updatedToken.saveOnServer = false
                                applyPaymentMethod(.webToken(updatedToken))
                            }), TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_Yes, action: {
                                guard let strongSelf = self else {
                                    return
                                }
                                if paymentForm.passwordMissing {
                                    var updatedToken = token
                                    updatedToken.saveOnServer = false
                                    applyPaymentMethod(.webToken(updatedToken))
                                    
                                    let controller = SetupTwoStepVerificationController(context: strongSelf.context, initialState: .automatic, stateUpdated: { update, shouldDismiss, controller in
                                        if shouldDismiss {
                                            controller.dismiss()
                                        }
                                        switch update {
                                            case .noPassword, .awaitingEmailConfirmation:
                                                break
                                            case .passwordSet:
                                                var updatedToken = token
                                                updatedToken.saveOnServer = true
                                                applyPaymentMethod(.webToken(updatedToken))
                                        }
                                    })
                                    strongSelf.present(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
                                } else {
                                    var updatedToken = token
                                    updatedToken.saveOnServer = true
                                    applyPaymentMethod(.webToken(updatedToken))
                                }
                            })]), nil)
                        } else {
                            var updatedToken = token
                            updatedToken.saveOnServer = false
                            applyPaymentMethod(.webToken(updatedToken))
                            
                            if allowSaving {
                                present(textAlertController(context: strongSelf.context, title: nil, text: strongSelf.presentationData.strings.Checkout_NewCard_SaveInfoEnableHelp.replacingOccurrences(of: "]", with: "").replacingOccurrences(of: "[", with: ""), actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {
                                })]), nil)
                            }
                        }
                    }))
                    dismissImpl = { [weak controller] in
                        controller?.dismiss()
                    }
                    strongSelf.present(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
                }
            }
        }
        
        openPaymentMethodImpl = { [weak self] in
            if let strongSelf = self, let paymentForm = strongSelf.paymentFormValue {
                let methods = availablePaymentMethods(form: paymentForm, current: strongSelf.currentPaymentMethod)
                if methods.isEmpty {
                    openNewCard()
                } else {
                    strongSelf.present(BotCheckoutPaymentMethodSheetController(context: strongSelf.context, currentMethod: strongSelf.currentPaymentMethod, methods: methods, applyValue: { method in
                        applyPaymentMethod(method)
                    }, newCard: {
                        openNewCard()
                    }), ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
                }
            }
        }
        
        openShippingMethodImpl = { [weak self] in
            if let strongSelf = self, let paymentFormValue = strongSelf.paymentFormValue, let shippingOptions = strongSelf.currentValidatedFormInfo?.shippingOptions, !shippingOptions.isEmpty {
                strongSelf.present(BotCheckoutPaymentShippingOptionSheetController(context: strongSelf.context, currency: paymentFormValue.invoice.currency, options: shippingOptions, currentId: strongSelf.currentShippingOptionId, applyValue: { id in
                    if let strongSelf = self, let paymentFormValue = strongSelf.paymentFormValue, let currentFormInfo = strongSelf.currentFormInfo {
                        strongSelf.currentShippingOptionId = id
                        strongSelf.paymentFormAndInfo.set(.single((paymentFormValue, currentFormInfo, strongSelf.currentValidatedFormInfo, strongSelf.currentShippingOptionId, strongSelf.currentPaymentMethod)))
                        
                        strongSelf.updateActionButton()
                    }
                }), ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
            }
        }
        
        let formAndMaybeValidatedInfo = fetchBotPaymentForm(postbox: context.account.postbox, network: context.account.network, messageId: messageId)
            |> mapToSignal { paymentForm -> Signal<(BotPaymentForm, BotPaymentValidatedFormInfo?), BotPaymentFormRequestError> in
                if let current = paymentForm.savedInfo {
                    return validateBotPaymentForm(network: context.account.network, saveInfo: true, messageId: messageId, formInfo: current)
                        |> mapError { _ -> BotPaymentFormRequestError in
                            return .generic
                        }
                        |> map { result -> (BotPaymentForm, BotPaymentValidatedFormInfo?) in
                            return (paymentForm, result)
                        }
                        |> `catch` { _ -> Signal<(BotPaymentForm, BotPaymentValidatedFormInfo?), BotPaymentFormRequestError> in
                            return .single((paymentForm, nil))
                        }
                } else {
                    return .single((paymentForm, nil))
                }
            }
        
        self.formRequestDisposable = (formAndMaybeValidatedInfo |> deliverOnMainQueue).start(next: { [weak self] form, validatedInfo in
            if let strongSelf = self {
                let savedInfo: BotPaymentRequestedInfo
                if let current = form.savedInfo {
                    savedInfo = current
                } else {
                    savedInfo = BotPaymentRequestedInfo(name: nil, phone: nil, email: nil, shippingAddress: nil)
                }
                strongSelf.paymentFormValue = form
                strongSelf.currentFormInfo = savedInfo
                strongSelf.currentValidatedFormInfo = validatedInfo
                if let savedCredentials = form.savedCredentials {
                    strongSelf.currentPaymentMethod = .savedCredentials(savedCredentials)
                }
                strongSelf.actionButton.isEnabled = true
                strongSelf.paymentFormAndInfo.set(.single((form, savedInfo, validatedInfo, nil, strongSelf.currentPaymentMethod)))
                
                strongSelf.updateActionButton()
            }
        }, error: { _ in
            
        })
        
        self.actionButton.addTarget(self, action: #selector(self.actionButtonPressed), forControlEvents: .touchUpInside)
        self.actionButton.isEnabled = false
        self.addSubnode(self.actionButton)
        
        self.listNode.supernode?.insertSubnode(self.inProgressDimNode, aboveSubnode: self.listNode)
    }
    
    deinit {
        self.formRequestDisposable?.dispose()
        self.payDisposable.dispose()
        self.paymentAuthDisposable.dispose()
    }
    
    private func updateActionButton() {
        let totalAmount = currentTotalPrice(paymentForm: self.paymentFormValue, validatedFormInfo: self.currentValidatedFormInfo, currentShippingOptionId: self.currentShippingOptionId)
        let payString: String
        if let paymentForm = self.paymentFormValue, totalAmount > 0 {
            payString = self.presentationData.strings.Checkout_PayPrice(formatCurrencyAmount(totalAmount, currency: paymentForm.invoice.currency)).0
        } else {
            payString = self.presentationData.strings.CheckoutInfo_Pay
        }
        if self.actionButton.isEnabled {
            self.actionButton.setState(.active(payString))
        } else {
            self.actionButton.setState(.loading)
        }
    }
    
    override func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
        var updatedInsets = layout.intrinsicInsets
        updatedInsets.bottom += BotCheckoutActionButton.diameter + 20.0
        super.containerLayoutUpdated(ContainerViewLayout(size: layout.size, metrics: layout.metrics, intrinsicInsets: updatedInsets, safeInsets: layout.safeInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight, standardInputHeight: layout.standardInputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver), navigationBarHeight: navigationBarHeight, transition: transition)
        
        let actionButtonFrame = CGRect(origin: CGPoint(x: 10.0, y: layout.size.height - 10.0 - BotCheckoutActionButton.diameter - layout.intrinsicInsets.bottom), size: CGSize(width: layout.size.width - 20.0, height: BotCheckoutActionButton.diameter))
        transition.updateFrame(node: self.actionButton, frame: actionButtonFrame)
        self.actionButton.updateLayout(size: actionButtonFrame.size, transition: transition)
        
        transition.updateFrame(node: self.inProgressDimNode, frame: self.listNode.frame)
    }
    
    @objc func actionButtonPressed() {
        self.pay()
    }
    
    private func pay(savedCredentialsToken: TemporaryTwoStepPasswordToken? = nil, liabilityNoticeAccepted: Bool = false, receivedCredentials: BotPaymentCredentials? = nil) {
        guard let paymentForm = self.paymentFormValue else {
            return
        }
        
        if !paymentForm.invoice.requestedFields.isEmpty {
            guard let validatedFormInfo = self.currentValidatedFormInfo else {
                if paymentForm.invoice.requestedFields.contains(.shippingAddress) {
                    self.arguments?.openInfo(.address(.street1))
                } else if paymentForm.invoice.requestedFields.contains(.name) {
                    self.arguments?.openInfo(.name)
                } else if paymentForm.invoice.requestedFields.contains(.email) {
                    self.arguments?.openInfo(.email)
                } else if paymentForm.invoice.requestedFields.contains(.phone) {
                    self.arguments?.openInfo(.phone)
                }
                return
            }
            
            if let _ = validatedFormInfo.shippingOptions {
                if self.currentShippingOptionId == nil {
                    self.arguments?.openShippingMethod()
                    return
                }
            }
        }
        
        guard let paymentMethod = self.currentPaymentMethod else {
            self.arguments?.openPaymentMethod()
            return
        }
        
        let credentials: BotPaymentCredentials
        if let receivedCredentials = receivedCredentials {
            credentials = receivedCredentials
        } else {
            switch paymentMethod {
                case let .savedCredentials(savedCredentials):
                    switch savedCredentials {
                        case let .card(id, title):
                            if let savedCredentialsToken = savedCredentialsToken {
                                credentials = .saved(id: id, tempPassword: savedCredentialsToken.token)
                            } else {
                                let _ = (cachedTwoStepPasswordToken(postbox: self.context.account.postbox)
                                |> deliverOnMainQueue).start(next: { [weak self] token in
                                    if let strongSelf = self {
                                        let timestamp = strongSelf.context.account.network.getApproximateRemoteTimestamp()
                                        if let token = token, token.validUntilDate > timestamp - 1 * 60 {
                                            if token.requiresBiometrics {
                                                let reasonText: String
                                                if let biometricAuthentication = LocalAuth.biometricAuthentication, case .faceId = biometricAuthentication {
                                                    reasonText = strongSelf.presentationData.strings.Checkout_PayWithFaceId
                                                } else {
                                                    reasonText = strongSelf.presentationData.strings.Checkout_PayWithTouchId
                                                }
                                                let _ = (LocalAuth.auth(reason: reasonText) |> deliverOnMainQueue).start(next: { value, _ in
                                                    if let strongSelf = self {
                                                        if value {
                                                            strongSelf.pay(savedCredentialsToken: token)
                                                        } else {
                                                            strongSelf.requestPassword(cardTitle: title)
                                                        }
                                                    }
                                                })
                                            } else {
                                                strongSelf.pay(savedCredentialsToken: token)
                                            }
                                        } else {
                                            strongSelf.requestPassword(cardTitle: title)
                                        }
                                    }
                                })
                                return
                            }
                    }
                case let .webToken(token):
                    credentials = .generic(data: token.data, saveOnServer: token.saveOnServer)
                case .applePay:
                    guard let paymentForm = self.paymentFormValue, let nativeProvider = paymentForm.nativeProvider else {
                        return
                    }
                    guard let nativeParamsData = nativeProvider.params.data(using: .utf8) else {
                        return
                    }
                    guard let nativeParams = (try? JSONSerialization.jsonObject(with: nativeParamsData, options: [])) as? [String: Any] else {
                        return
                    }
                    
                    let merchantId: String
                    if nativeProvider.name == "stripe" {
                        merchantId = "merchant.ph.telegra.Telegraph"
                    } else if let paramsId = nativeParams["apple_pay_merchant_id"] as? String {
                        merchantId = paramsId
                    } else {
                        return
                    }
                    
                    let botPeerId = self.messageId.peerId
                    let _ = (self.context.account.postbox.transaction({ transaction -> Peer? in
                        return transaction.getPeer(botPeerId)
                    }) |> deliverOnMainQueue).start(next: { [weak self] botPeer in
                        if let strongSelf = self, let botPeer = botPeer {
                            let request = PKPaymentRequest()
                            
                            request.merchantIdentifier = merchantId
                            request.supportedNetworks = [.visa, .amex, .masterCard]
                            request.merchantCapabilities = [.capability3DS]
                            request.countryCode = "US"
                            request.currencyCode = paymentForm.invoice.currency.uppercased()
                            
                            var items: [PKPaymentSummaryItem] = []
                            
                            var totalAmount: Int64 = 0
                            for price in paymentForm.invoice.prices {
                                totalAmount += price.amount
                                
                                let amount = NSDecimalNumber(value: Double(price.amount) * 0.01)
                                items.append(PKPaymentSummaryItem(label: price.label, amount: amount))
                            }
                            
                            if let shippingOptions = strongSelf.currentValidatedFormInfo?.shippingOptions, let shippingOptionId = strongSelf.currentShippingOptionId {
                                if let shippingOptionIndex = shippingOptions.firstIndex(where: { $0.id == shippingOptionId }) {
                                    for price in shippingOptions[shippingOptionIndex].prices {
                                        totalAmount += price.amount
                                        
                                        let amount = NSDecimalNumber(value: Double(price.amount) * 0.01)
                                        items.append(PKPaymentSummaryItem(label: price.label, amount: amount))
                                    }
                                }
                            }
                            
                            let amount = NSDecimalNumber(value: Double(totalAmount) * 0.01)
                            items.append(PKPaymentSummaryItem(label: botPeer.displayTitle, amount: amount))
                            
                            request.paymentSummaryItems = items
                            
                            if let controller = PKPaymentAuthorizationViewController(paymentRequest: request) {
                                controller.delegate = strongSelf
                                if let window = strongSelf.view.window {
                                    strongSelf.applePayController = controller
                                    controller.popoverPresentationController?.sourceView = window
                                    controller.popoverPresentationController?.sourceRect = CGRect(origin: CGPoint(x: window.bounds.width / 2.0, y: window.bounds.size.height - 1.0), size: CGSize(width: 1.0, height: 1.0))
                                    window.rootViewController?.present(controller, animated: true)
                                }
                            }
                        }
                    })
                    return
            }
        }
        
        if !liabilityNoticeAccepted {
            let messageId = self.messageId
            let botPeer: Signal<Peer?, NoError> = self.context.account.postbox.transaction { transaction -> Peer? in
                if let message = transaction.getMessage(messageId) {
                    return message.author
                }
                return nil
            }
            let _ = (combineLatest(ApplicationSpecificNotice.getBotPaymentLiability(accountManager: self.context.sharedContext.accountManager, peerId: self.messageId.peerId), botPeer, self.context.account.postbox.loadedPeerWithId(paymentForm.providerId))
            |> deliverOnMainQueue).start(next: { [weak self] value, botPeer, providerPeer in
                if let strongSelf = self, let botPeer = botPeer {
                    if value {
                        strongSelf.pay(savedCredentialsToken: savedCredentialsToken, liabilityNoticeAccepted: true)
                    } else {
                        strongSelf.present(textAlertController(context: strongSelf.context, title: strongSelf.presentationData.strings.Checkout_LiabilityAlertTitle, text: strongSelf.presentationData.strings.Checkout_LiabilityAlert(botPeer.displayTitle, providerPeer.displayTitle).0, actions: [TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: { }), TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {
                            if let strongSelf = self {
                                let _ = ApplicationSpecificNotice.setBotPaymentLiability(accountManager: strongSelf.context.sharedContext.accountManager, peerId: strongSelf.messageId.peerId).start()
                                strongSelf.pay(savedCredentialsToken: savedCredentialsToken, liabilityNoticeAccepted: true)
                            }
                        })]), nil)
                    }
                }
            })
        } else {
            self.inProgressDimNode.isUserInteractionEnabled = true
            self.inProgressDimNode.alpha = 1.0
            self.actionButton.isEnabled = false
            self.updateActionButton()
            self.payDisposable.set((sendBotPaymentForm(account: self.context.account, messageId: self.messageId, validatedInfoId: self.currentValidatedFormInfo?.id, shippingOptionId: self.currentShippingOptionId, credentials: credentials) |> deliverOnMainQueue).start(next: { [weak self] result in
                if let strongSelf = self {
                    strongSelf.inProgressDimNode.isUserInteractionEnabled = false
                    strongSelf.inProgressDimNode.alpha = 0.0
                    strongSelf.actionButton.isEnabled = true
                    if let applePayAuthrorizationCompletion = strongSelf.applePayAuthrorizationCompletion {
                        strongSelf.applePayAuthrorizationCompletion = nil
                        applePayAuthrorizationCompletion(.success)
                    }
                    if let applePayController = strongSelf.applePayController {
                        strongSelf.applePayController = nil
                        applePayController.presentingViewController?.dismiss(animated: true, completion: nil)
                    }
                    
                    switch result {
                        case .done:
                            strongSelf.dismissAnimated()
                        case let .externalVerificationRequired(url):
                            strongSelf.updateActionButton()
                            var dismissImpl: (() -> Void)?
                            let controller = BotCheckoutWebInteractionController(context: strongSelf.context, url: url, intent: .externalVerification({ _ in
                                dismissImpl?()
                            }))
                            dismissImpl = { [weak controller] in
                                controller?.dismiss()
                                self?.dismissAnimated()
                            }
                            strongSelf.present(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
                    }
                }
            }, error: { [weak self] error in
                if let strongSelf = self {
                    strongSelf.inProgressDimNode.isUserInteractionEnabled = false
                    strongSelf.inProgressDimNode.alpha = 0.0
                    strongSelf.actionButton.isEnabled = true
                    strongSelf.updateActionButton()
                    if let applePayAuthrorizationCompletion = strongSelf.applePayAuthrorizationCompletion {
                        strongSelf.applePayAuthrorizationCompletion = nil
                        applePayAuthrorizationCompletion(.failure)
                    }
                    if let applePayController = strongSelf.applePayController {
                        strongSelf.applePayController = nil
                        applePayController.presentingViewController?.dismiss(animated: true, completion: nil)
                    }
                    
                    let text: String
                    switch error {
                        case .precheckoutFailed:
                            text = strongSelf.presentationData.strings.Checkout_ErrorPrecheckoutFailed
                        case .paymentFailed:
                            text = strongSelf.presentationData.strings.Checkout_ErrorPaymentFailed
                        case .alreadyPaid:
                            text = strongSelf.presentationData.strings.Checkout_ErrorInvoiceAlreadyPaid
                        case .generic:
                            text = strongSelf.presentationData.strings.Checkout_ErrorGeneric
                    }
                    
                    strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), nil)
                }
            }))
        }
    }
    
    private func requestPassword(cardTitle: String) {
        let period: Int32
        let requiresBiometrics: Bool
        if LocalAuth.biometricAuthentication != nil {
            period = 5 * 60 * 60
            requiresBiometrics = true
        } else {
            period = 1 * 60 * 60
            requiresBiometrics = false
        }
        self.present(botCheckoutPasswordEntryController(context: self.context, strings: self.presentationData.strings, cartTitle: cardTitle, period: period, requiresBiometrics: requiresBiometrics, completion: { [weak self] token in
            if let strongSelf = self {
                let durationString = timeIntervalString(strings: strongSelf.presentationData.strings, value: period)
                
                let alertText: String
                if requiresBiometrics {
                    if let biometricAuthentication = LocalAuth.biometricAuthentication, case .faceId = biometricAuthentication {
                        alertText = strongSelf.presentationData.strings.Checkout_SavePasswordTimeoutAndFaceId(durationString).0
                    } else {
                        alertText = strongSelf.presentationData.strings.Checkout_SavePasswordTimeoutAndTouchId(durationString).0
                    }
                } else {
                    alertText = strongSelf.presentationData.strings.Checkout_SavePasswordTimeout(durationString).0
                }
                
                strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: alertText, actions: [
                    TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_No, action: {
                        if let strongSelf = self {
                            strongSelf.pay(savedCredentialsToken: token)
                        }
                    }),
                    TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Yes, action: {
                        if let strongSelf = self {
                            let _ = cacheTwoStepPasswordToken(postbox: strongSelf.context.account.postbox, token: token).start()
                            strongSelf.pay(savedCredentialsToken: token)
                        }
                    })
                ]), nil)
            }
        }), nil)
    }
    
    func paymentAuthorizationViewController(_ controller: PKPaymentAuthorizationViewController, didAuthorizePayment payment: PKPayment, completion: @escaping (PKPaymentAuthorizationStatus) -> Void) {
        guard let paymentForm = self.paymentFormValue else {
            completion(.failure)
            return
        }
        if !formSupportApplePay(paymentForm) {
            completion(.failure)
            return
        }
        guard let nativeProvider = paymentForm.nativeProvider else {
            completion(.failure)
            return
        }
        guard let paramsData = nativeProvider.params.data(using: .utf8) else {
            return
        }
        guard let nativeParams = (try? JSONSerialization.jsonObject(with: paramsData)) as? [String: Any] else {
            return
        }
        
        if nativeProvider.name == "stripe" {
            guard let publishableKey = nativeParams["publishable_key"] as? String else {
                return
            }
            
            let signal: Signal<STPToken, Error> = Signal { subscriber in
                let configuration = STPPaymentConfiguration.shared().copy() as! STPPaymentConfiguration
                configuration.smsAutofillDisabled = true
                configuration.publishableKey = publishableKey
                configuration.appleMerchantIdentifier = "merchant.ph.telegra.Telegraph"
                
                let apiClient = STPAPIClient(configuration: configuration)
                
                apiClient.createToken(with: payment, completion: { token, error in
                    if let token = token {
                        subscriber.putNext(token)
                        subscriber.putCompletion()
                    } else if let error = error {
                        subscriber.putError(error)
                    }
                })
                
                return ActionDisposable {
                }
            }
            
            self.paymentAuthDisposable.set((signal |> deliverOnMainQueue).start(next: { [weak self] token in
                if let strongSelf = self {
                    strongSelf.applePayAuthrorizationCompletion = completion
                    strongSelf.pay(liabilityNoticeAccepted: true, receivedCredentials: .generic(data: "{\"type\": \"card\", \"id\": \"\(token.tokenId)\"}", saveOnServer: false))
                } else {
                    completion(.failure)
                }
            }, error: { _ in
                completion(.failure)
            }))
        } else {
            self.applePayAuthrorizationCompletion = completion
            guard let paymentString = String(data: payment.token.paymentData, encoding: .utf8) else {
                return
            }
            self.pay(liabilityNoticeAccepted: true, receivedCredentials: .applePay(data: paymentString))
        }
    }
    
    func paymentAuthorizationViewControllerDidFinish(_ controller: PKPaymentAuthorizationViewController) {
        controller.presentingViewController?.dismiss(animated: true, completion: nil)
        self.paymentAuthDisposable.set(nil)
    }
}