// Copyright © 2022 Olo Inc. All rights reserved.
// This software is made available under the Olo Pay SDK License (See LICENSE.md file)
//
//  PaymentCardDetailsViewController.swift
//  OlopaysdkReactNative
//
//  Created by Justin Anderson on 3/2/23.
//  Copyright © 2023 Facebook. All rights reserved.
//

import Foundation
import OloPaySDK

class PaymentCardDetailsViewController : UIViewController, OPPaymentCardDetailsViewDelegate {
    private let _cardDetails = OPPaymentCardDetailsView()
    private var _customErrorMessages: CustomErrorMessages? = nil
    
    public func focus() { _cardDetails.becomeFirstResponder() }
    public func blur() { _cardDetails.resignFirstResponder() }
    public func clear() { _cardDetails.clear() }
    
    public func createPaymentMethod(resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
        guard let params = _cardDetails.getPaymentMethodParams() else {
            reject(getErrorCode(), _cardDetails.getErrorMessage(false), nil)
            return
        }
        
        OloPayAPI().createPaymentMethod(with: params) { paymentMethod, error in
            if (error != nil) {
                rejectError(error: error!, reject: reject)
                return
            }

            guard let paymentMethod = paymentMethod else {
                reject(ErrorCodes.GeneralError, "Unexpected error occurred", nil)
                return
            }

            resolve(paymentMethod.toDictionary())
        }
    }
    
    var onCardChange: RCTDirectEventBlock? = nil
    var onFocus: RCTDirectEventBlock? = nil
    var onFocusField: RCTDirectEventBlock? = nil
    var onBlur: RCTDirectEventBlock? = nil
    
    var customErrorMessages: NSDictionary? = nil {
        didSet {
            if let customErrorMessages = customErrorMessages, customErrorMessages.count != 0 {
               _customErrorMessages = CustomErrorMessages(customErrorMessages: customErrorMessages)
           } else {
               _customErrorMessages = nil
           }
       }
    }

    public var postalCodeEnabled: Bool = true {
        didSet {
            _cardDetails.postalCodeEntryEnabled = postalCodeEnabled
            _cardDetails.setNeedsLayout()
        }
    }

    public var isEnabled: Bool = true {
        didSet { _cardDetails.isEnabled = isEnabled }
    }

    public var placeholders: NSDictionary = NSDictionary() {
        didSet {
            if let numberPlaceholder = placeholders[DataKeys.NumberPlaceholderKey] as? String {
                _cardDetails.numberPlaceholder = numberPlaceholder
            }

            if let expirationPlaceholder = placeholders[DataKeys.ExpirationPlaceholderKey] as? String {
                _cardDetails.expirationPlaceholder = expirationPlaceholder
            }

            if let cvvPlaceholder = placeholders[DataKeys.CvvPlaceholderKey] as? String {
                _cardDetails.cvvPlaceholder = cvvPlaceholder
            }

            if let postalCodePlaceholder = placeholders[DataKeys.PostalCodePlaceholderKey] as? String {
                _cardDetails.postalCodePlaceholder = postalCodePlaceholder
            }
        }
    }

    public var cardStyles: NSDictionary = NSDictionary() {
        didSet {
            if let borderWidth = cardStyles[DataKeys.BorderWidthKey] as? Int {
                _cardDetails.borderWidth = CGFloat(borderWidth)
            }

            if let backgroundColor = cardStyles[DataKeys.BackgroundColorKey] as? String {
                _cardDetails.backgroundColor = UIColor(hex: backgroundColor)
            }
            
            if let borderColor = cardStyles[DataKeys.BorderColorKey] as? String {
                _cardDetails.borderColor = UIColor(hex: borderColor)
            }
            
            if let borderRadius = cardStyles[DataKeys.CornerRadiusKey] as? Int {
                _cardDetails.cornerRadius = CGFloat(borderRadius)
            }
            
            if let cursorColor = cardStyles[DataKeys.CursorColorKey] as? String {
                _cardDetails.cursorColor = UIColor(hex: cursorColor)
            }
            
            if let textColor = cardStyles[DataKeys.TextColorKey] as? String {
                _cardDetails.textColor = UIColor(hex: textColor)
            }
            
            if let textErrorColor = cardStyles[DataKeys.ErrorTextColorKey] as? String {
                _cardDetails.textErrorColor = UIColor(hex: textErrorColor)
            }

            if let placeholderColor = cardStyles[DataKeys.PlaceholderColorKey] as? String {
                _cardDetails.placeholderColor = UIColor(hex: placeholderColor)
            }
            
            let fontSize = cardStyles[DataKeys.FontSizeKey] as? Int ?? 14
            var font: UIFont? = nil

            if let fontFamily = cardStyles[DataKeys.FontFamilyKey] as? String {
                font = UIFont(name: fontFamily, size: CGFloat(fontSize))
            }
            
            font = font ?? UIFont.systemFont(ofSize: CGFloat(fontSize))
            _cardDetails.font = UIFontMetrics.default.scaledFont(for: font!)

            _cardDetails.setNeedsLayout()
        }
    }

    override func viewDidLoad() {
        setupViews()
    }

    func setupViews() {
        self.view.addSubview(_cardDetails)
        _cardDetails.cardDetailsDelegate = self
        _cardDetails.displayGeneratedErrorMessages = false
        
        OPPaymentCardDetailsView.errorMessageHandler = errorMessageHandler(_:_:_:)

        _cardDetails.translatesAutoresizingMaskIntoConstraints = false
        let constraints = [
            _cardDetails.topAnchor.constraint(equalTo: self.view.topAnchor),
            _cardDetails.bottomAnchor.constraint(equalTo: self.view.bottomAnchor),
            _cardDetails.leftAnchor.constraint(equalTo: self.view.leftAnchor),
            _cardDetails.rightAnchor.constraint(equalTo: self.view.rightAnchor)
        ]

        NSLayoutConstraint.activate(constraints)
    }

    @objc func paymentCardDetailsViewDidChange(with fieldStates: NSDictionary, isValid: Bool) {
        emitCardDetailsChangedEvent()
    }
    
    private func errorMessageHandler(_ cardState: NSDictionary, _ cardBrand: OPCardBrand, _ ignoreUneditedFieldErrors: Bool) -> String {
        let defaultError = CustomErrorMessages.getDefaultErrorMessage(
            ignoreUneditedFieldErrors,
            _cardDetails.fieldStates,
            cardBrand
        )

        var customError: String? = nil

        if let customErrorMessages = _customErrorMessages {
            customError = customErrorMessages.getCustomErrorMessage(
                ignoreUneditedFieldErrors,
                _cardDetails.fieldStates,
                cardBrand
            )
        }

        return customError ?? defaultError
    } 

    func paymentCardDetailsViewDidBeginEditing(_ cardDetails: OPPaymentCardDetailsView) {
        onFocus?(nil)
    }

    func paymentCardDetailsViewDidEndEditing(_ cardDetails: OPPaymentCardDetailsView) {
        emitCardDetailsChangedEvent()
        onBlur?(nil)
    }

    func paymentCardDetailsViewFieldDidBeginEditing(_ cardDetails: OPPaymentCardDetailsView, field: OPCardField) {
        emitCardDetailsChangedEvent()
        onFocusField?([DataKeys.FieldKey: field.description])
    }
    
    func emitCardDetailsChangedEvent() {
        guard onCardChange != nil else {
            return
        }
        
        let cardState = _cardDetails.fieldStates
        
        var cardDetails = [
            DataKeys.IsValidKey: _cardDetails.isValid,
            DataKeys.CardTypeKey: _cardDetails.cardType.description
        ] as [String : Any]
        
        if !_cardDetails.isValid {
            var errors = [String: String]()

            let editedFieldsErrorMessage = _cardDetails.getErrorMessage(true)
            if (!editedFieldsErrorMessage.isEmpty) {
                errors.updateValue(editedFieldsErrorMessage, forKey: DataKeys.EditedFieldsErrorKey)
            }

            let allFieldsErrorMessage = _cardDetails.getErrorMessage(false)
            if (!allFieldsErrorMessage.isEmpty) {
                errors.updateValue(allFieldsErrorMessage, forKey: DataKeys.AllFieldsErrorKey)
            }

            if (!editedFieldsErrorMessage.isEmpty || !allFieldsErrorMessage.isEmpty) {
                cardDetails.updateValue(errors, forKey: DataKeys.ErrorsKey)
            }
        }
        
        var invalidFields = [String]()
        var emptyFields = [String]()
        
        cardState.forEach {
            if !$0.value.isValid {
                invalidFields.append($0.key.description)
            }
            
            if $0.value.isEmpty {
                emptyFields.append($0.key.description)
            }
        }
        
        if !invalidFields.isEmpty {
            cardDetails.updateValue(invalidFields, forKey: DataKeys.InvalidFieldsKey)
        }

        if !emptyFields.isEmpty {
            cardDetails.updateValue(emptyFields, forKey: DataKeys.EmptyFieldsKey)
        }
        
        onCardChange?(cardDetails)
    }

    func getErrorCode() -> String {
        if !_cardDetails.fieldStates[OPCardField.number]!.isValid {
            return ErrorCodes.InvalidNumber
        } else if !_cardDetails.fieldStates[OPCardField.expiration]!.isValid {
            return ErrorCodes.InvalidExpiration
        } else if !_cardDetails.fieldStates[OPCardField.cvv]!.isValid {
            return ErrorCodes.InvalidCvv
        } else if !_cardDetails.fieldStates[OPCardField.postalCode]!.isValid {
            return ErrorCodes.InvalidPostalCode
        }
        
        return ErrorCodes.InvalidCardDetails
    }
    
}
