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

import Foundation
import OloPaySDK

class PaymentCardCvvViewController : UIViewController, OPPaymentCardCvvViewDelegate {
    private let _cvvView = OPPaymentCardCvvView()
    private var _customErrorMessages: CustomErrorMessages? = nil
    
    var onCvvChange: RCTDirectEventBlock? = nil
    var onFocus: RCTDirectEventBlock? = nil
    var onBlur: RCTDirectEventBlock? = nil

    public var customErrorMessages: NSDictionary? = nil {
        didSet {
            if let customErrorMessages = customErrorMessages, customErrorMessages.count != 0 {
                _customErrorMessages = CustomErrorMessages(cvvCustomErrors: customErrorMessages)
            } else {
                _customErrorMessages = nil
            }
            onCvvChange?(getEventDetails())
        }
    }
    
    public var isEnabled: Bool = true {
        didSet {
            _cvvView.isEnabled = isEnabled
        }
    }
    
    public var placeholder: String {
        get { _cvvView.placeholderText }
        set { _cvvView.placeholderText = newValue }
    }
    
    var cvvStyles: NSDictionary = NSDictionary() {
        didSet {
            if let borderWidth = cvvStyles[DataKeys.BorderWidthKey] as? Int {
                _cvvView.borderWidth = CGFloat(borderWidth)
            }
            
            if let backgroundColor = cvvStyles[DataKeys.BackgroundColorKey] as? String {
                _cvvView.backgroundColor = UIColor(hex: backgroundColor)
            }
            
            if let borderColor = cvvStyles[DataKeys.BorderColorKey] as? String {
                _cvvView.borderColor = UIColor(hex: borderColor)
            }
            
            if let borderRadius = cvvStyles[DataKeys.CornerRadiusKey] as? Int {
                _cvvView.cornerRadius = CGFloat(borderRadius)
            }
            
            if let cursorColor = cvvStyles[DataKeys.CursorColorKey] as? String {
                _cvvView.cursorColor = UIColor(hex: cursorColor)
            }
            
            if let textColor = cvvStyles[DataKeys.TextColorKey] as? String {
                _cvvView.cvvTextColor = UIColor(hex: textColor)
            }
            
            if let textErrorColor = cvvStyles[DataKeys.ErrorTextColorKey] as? String {
                _cvvView.errorTextColor = UIColor(hex: textErrorColor)
            }
            
            if let placeholderColor = cvvStyles[DataKeys.PlaceholderColorKey] as? String {
                _cvvView.placeholderColor = UIColor(hex: placeholderColor)
            }
            
            if let textAlign = cvvStyles[DataKeys.TextAlignKey] as? String {
                let alignment = getTextAlignment(textAlign)
                _cvvView.textAlignment = alignment
            }
            
            updateFont()
            updateContentPadding()
        }
    }
    
    public func focus() { let _ = _cvvView.becomeFirstResponder() }
    public func blur() { let _ = _cvvView.resignFirstResponder() }
    public func clear() { _cvvView.clear() }
    
    public func createCvvUpdateToken(resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
            
        guard let params = _cvvView.getCvvTokenParams() else {
            //Unable to get card parameters because the card was not in a valid state
            let errorMessage = _cvvView.getErrorMessage(ignoreUneditedFieldErrors: false)
            reject(ErrorCodes.InvalidCvv, errorMessage, nil)
            return
        }
        
        OloPayAPI().createCvvUpdateToken(with: params) { cvvUpdateToken, error in
            if (error != nil) {
                rejectError(error: error!, reject: reject)
                return
            }

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

            resolve(cvvUpdateToken.toDictionary())
        }
    }
    
    override func viewDidLoad() {
        setupViews()
    }
    
    func setupViews() {
        self.view.addSubview(_cvvView)
        
        _cvvView.displayGeneratedErrorMessages = false
        _cvvView.cvvDetailsDelegate = self
        
        OPPaymentCardCvvView.errorMessageHandler = customErrorMessageHandler(_:_:)
        
        _cvvView.translatesAutoresizingMaskIntoConstraints = false
        let constraints = [
            _cvvView.topAnchor.constraint(equalTo: self.view.topAnchor),
            _cvvView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor),
            _cvvView.leftAnchor.constraint(equalTo: self.view.leftAnchor),
            _cvvView.rightAnchor.constraint(equalTo: self.view.rightAnchor)
        ]
        
        NSLayoutConstraint.activate(constraints)
    }
    
    func fieldChanged(_ cvvTextField: OPPaymentCardCvvView) {
        onCvvChange?(getEventDetails())
    }
    
    func didBeginEditing(_ cvvTextField: OPPaymentCardCvvView) {
        onFocus?(getEventDetails())
    }
    
    func didEndEditing(_ cvvTextField: OPPaymentCardCvvView) {
        onBlur?(getEventDetails())
    }
    
    func customErrorMessageHandler(_ fieldState: OPCardFieldStateProtocol, _ ignoreUneditedFieldErrors: Bool) -> String {
        let defaultError = CustomErrorMessages.getDefaultErrorMessage(
            forCvvState: _cvvView.fieldState,
            ignoreUneditedFieldErrors
        )

        var customError: String? = nil 

        if let customErrorMessages = _customErrorMessages {
            customError = customErrorMessages.getCustomErrorMessage(
                ignoreUneditedFieldErrors,
                [OPCardField.cvv : _cvvView.fieldState]
            )
        }

        return customError ?? defaultError
    }
    
    private func getEventDetails() -> [String: Any] {
        let stateData = [
            DataKeys.IsValidKey: _cvvView.fieldState.isValid,
            DataKeys.isFocusedKey: _cvvView.fieldState.isFirstResponder,
            DataKeys.IsEmptyKey: _cvvView.fieldState.isEmpty,
            DataKeys.WasEditedKey: _cvvView.fieldState.wasEdited,
            DataKeys.WasFocusedKey: _cvvView.fieldState.wasFirstResponder
        ] as [String: Any]
        
        var cvvDetails = [DataKeys.StateKey: stateData] as [String: Any]
        
        if !_cvvView.isValid {
            var errors = [String: String]()
            
            let editedFieldErrorMessage = _cvvView.getErrorMessage(ignoreUneditedFieldErrors: true)
            if !editedFieldErrorMessage.isEmpty {
                errors.updateValue(editedFieldErrorMessage, forKey: DataKeys.EditedFieldErrorKey)
            }
            
            let allFieldsErrorMessage = _cvvView.getErrorMessage(ignoreUneditedFieldErrors: false)
            if !allFieldsErrorMessage.isEmpty {
                errors.updateValue(allFieldsErrorMessage, forKey: DataKeys.UneditedFieldErrorKey)
            }
            
            if !editedFieldErrorMessage.isEmpty || !allFieldsErrorMessage.isEmpty {
                cvvDetails.updateValue(errors, forKey: DataKeys.ErrorsKey)
            }
        }
        
        return cvvDetails
    }
    
    private func updateFont() {
        let fontSize = cvvStyles[DataKeys.FontSizeKey] as? Int ?? 14
        var font: UIFont? = nil
        
        if let fontFamily = cvvStyles[DataKeys.FontFamilyKey] as? String {
            font = UIFont(name: fontFamily, size: CGFloat(fontSize))
        }
        
        font = font ?? UIFont.systemFont(ofSize: CGFloat(fontSize))
        _cvvView.cvvFont = UIFontMetrics.default.scaledFont(for: font!)
    }
    
    private func updateContentPadding() {
        // At this time, it is not possible, without a lot of effort, to have properties coming from React Native change
        // the bounds of the native view so we only allow for the left inset to be set... this allows developers to
        // specify the distance between view's border and the text.
        //
        // Technically it should be possible to change the bounds of the native view, but it requires a custom shadow node
        // class. Newer versions of react native have a new underlying architecture that change how shadow nodes calculate
        // sizes... It would be best to wait until we update to the new architecture before implementing shadow nodes.
        let insets = _cvvView.contentPadding
        
        var leftPadding: CGFloat? = nil
        if let left = cvvStyles[DataKeys.CvvTextPaddingLeftInsetKey] as? Int {
            leftPadding = CGFloat(left)
        } else {
            leftPadding = insets.left
        }

        var rightPadding: CGFloat? = nil
        if let right = cvvStyles[DataKeys.CvvTextPaddingRightInsetKey] as? Int {
            rightPadding = CGFloat(right)
        } else {
            rightPadding = insets.right
        }
        
        _cvvView.contentPadding = UIEdgeInsets(
            top: insets.top,
            left: leftPadding!,
            bottom: insets.bottom,
            right: rightPadding!
        )
    }
    
    private func getTextAlignment(_ alignmentString: String) -> NSTextAlignment {
        if(alignmentString == DataKeys.AlignmentRightKey) {
            return NSTextAlignment.right
        } else if(alignmentString == DataKeys.AlignmentCenterKey) {
            return NSTextAlignment.center
        } else {
            return NSTextAlignment.left
        }
    }
}
