// Copyright © 2022 Olo Inc. All rights reserved.
// This software is made available under the Olo Pay SDK License (See LICENSE.md file)

import UIKit
import OloPaySDK

// Internal delegate adapter to avoid exposing OloPaySDK protocol conformance in bridging header
private class PaymentCardCvvViewDelegateAdapter: NSObject, OPPaymentCardCvvViewDelegate {
    weak var fabricView: PaymentCardCvvView?

    func fieldChanged(_ cvvTextField: OPPaymentCardCvvView) {
        fabricView?.handleFieldChanged()
    }

    func didBeginEditing(_ cvvTextField: OPPaymentCardCvvView) {
        fabricView?.handleBeginEditing()
    }

    func didEndEditing(_ cvvTextField: OPPaymentCardCvvView) {
        fabricView?.handleEndEditing()
    }
}

@objc(PaymentCardCvvView)
public class PaymentCardCvvView: UIView {
    private var cvvView: OPPaymentCardCvvView?
    private var _customErrorMessages: CustomErrorMessages? = nil
    private var delegateAdapter: PaymentCardCvvViewDelegateAdapter?

    @objc public var onCvvChange: ((NSDictionary) -> Void)?
    @objc public var onFocus: ((NSDictionary) -> Void)?
    @objc public var onBlur: ((NSDictionary) -> Void)?
    @objc public var onCvvTokenResult: ((NSDictionary) -> Void)?

    @objc public var isEnabled: Bool = true {
        didSet {
            cvvView?.isEnabled = isEnabled
        }
    }

    @objc public var placeholder: String = "CVV" {
        didSet {
            cvvView?.placeholderText = placeholder
        }
    }

    @objc public var customErrorMessages: NSDictionary? = nil {
        didSet {
            if let customErrorMessages = customErrorMessages, customErrorMessages.count != 0 {
                _customErrorMessages = CustomErrorMessages(cvvCustomErrors: customErrorMessages)
            } else {
                _customErrorMessages = nil
            }
        }
    }

    public override init(frame: CGRect) {
        super.init(frame: frame)
        setupView()
    }

    required init?(coder: NSCoder) {
        super.init(coder: coder)
        setupView()
    }

    private func setupView() {
        let view = OPPaymentCardCvvView()
        view.translatesAutoresizingMaskIntoConstraints = false

        // Create and set up delegate adapter
        let adapter = PaymentCardCvvViewDelegateAdapter()
        adapter.fabricView = self
        delegateAdapter = adapter
        view.cvvDetailsDelegate = adapter

        view.displayGeneratedErrorMessages = false

        OPPaymentCardCvvView.errorMessageHandler = customErrorMessageHandler(_:_:)

        addSubview(view)
        NSLayoutConstraint.activate([
            view.leadingAnchor.constraint(equalTo: leadingAnchor),
            view.trailingAnchor.constraint(equalTo: trailingAnchor),
            view.topAnchor.constraint(equalTo: topAnchor),
            view.bottomAnchor.constraint(equalTo: bottomAnchor)
        ])

        cvvView = view
    }

    @objc public func updateCustomErrorMessages(_ messages: NSDictionary) {
        if messages.count != 0 {
            _customErrorMessages = CustomErrorMessages(cvvCustomErrors: messages)
        } else {
            _customErrorMessages = nil
        }
    }

    @objc public func updateCvvStyles(_ styles: NSDictionary) {
        guard let cvvView = cvvView else { return }

        if let borderWidth = styles[DataKeys.BorderWidthKey] as? Int {
            cvvView.borderWidth = CGFloat(borderWidth)
        }
        if let backgroundColor = styles[DataKeys.BackgroundColorKey] as? String {
            cvvView.backgroundColor = UIColor(hex: backgroundColor)
        }
        if let borderColor = styles[DataKeys.BorderColorKey] as? String {
            cvvView.borderColor = UIColor(hex: borderColor)
        }
        if let cornerRadius = styles[DataKeys.CornerRadiusKey] as? Int {
            cvvView.cornerRadius = CGFloat(cornerRadius)
        }
        if let cursorColor = styles[DataKeys.CursorColorKey] as? String {
            cvvView.cursorColor = UIColor(hex: cursorColor)
        }
        if let textColor = styles[DataKeys.TextColorKey] as? String {
            cvvView.cvvTextColor = UIColor(hex: textColor)
        }
        if let errorTextColor = styles[DataKeys.ErrorTextColorKey] as? String {
            cvvView.errorTextColor = UIColor(hex: errorTextColor)
        }
        if let placeholderColor = styles[DataKeys.PlaceholderColorKey] as? String {
            cvvView.placeholderColor = UIColor(hex: placeholderColor)
        }
        if let textAlign = styles[DataKeys.TextAlignKey] as? String {
            cvvView.textAlignment = getTextAlignment(textAlign)
        }

        let fontSize = styles[DataKeys.FontSizeKey] as? Int ?? 14
        var font: UIFont? = nil

        if let fontFamily = styles[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!)

        // Handle content padding
        let insets = cvvView.contentPadding
        var leftPadding = insets.left
        var rightPadding = insets.right

        if let left = styles[DataKeys.CvvTextPaddingLeftInsetKey] as? Int {
            leftPadding = CGFloat(left)
        }
        if let right = styles[DataKeys.CvvTextPaddingRightInsetKey] as? Int {
            rightPadding = CGFloat(right)
        }

        cvvView.contentPadding = UIEdgeInsets(
            top: insets.top,
            left: leftPadding,
            bottom: insets.bottom,
            right: rightPadding
        )
    }

    @objc public func focus() {
        cvvView?.becomeFirstResponder()
    }

    @objc public func blur() {
        cvvView?.resignFirstResponder()
    }

    @objc public func clear() {
        cvvView?.clear()
    }

    @objc public func createCvvUpdateToken() {
        guard let cvvView = cvvView else { return }

        guard let params = cvvView.getCvvTokenParams() else {
            // Emit result with error - use NSString explicitly for proper bridging
            let errorMessage = customErrorMessageHandler(cvvView.fieldState, false)
            let errorDict: NSDictionary = [
                DataKeys.MessageKey as NSString: errorMessage as NSString,
                DataKeys.CodeKey as NSString: ErrorCodes.InvalidCvv as NSString
            ]
            let resultDict: NSDictionary = [DataKeys.ErrorKey as NSString: errorDict]
            onCvvTokenResult?(resultDict)
            return
        }

        OloPayAPI().createCvvUpdateToken(with: params) { [weak self] cvvUpdateToken, error in
            DispatchQueue.main.async {
                if let error = error {
                    // Emit result with error
                    let errorDict: NSDictionary = [
                        DataKeys.MessageKey as NSString: error.localizedDescription as NSString,
                        DataKeys.CodeKey as NSString: getErrorCode(error) as NSString
                    ]
                    let resultDict: NSDictionary = [DataKeys.ErrorKey as NSString: errorDict]
                    self?.onCvvTokenResult?(resultDict)
                    return
                }

                guard let cvvUpdateToken = cvvUpdateToken else {
                    // Emit result with error
                    let errorDict: NSDictionary = [
                        DataKeys.MessageKey as NSString: "Unexpected error occurred" as NSString,
                        DataKeys.CodeKey as NSString: ErrorCodes.GeneralError as NSString
                    ]
                    let resultDict: NSDictionary = [DataKeys.ErrorKey as NSString: errorDict]
                    self?.onCvvTokenResult?(resultDict)
                    return
                }

                // Emit result with success
                let resultDict: NSDictionary = [DataKeys.CvvTokenKey as NSString: cvvUpdateToken.toDictionary()]
                self?.onCvvTokenResult?(resultDict)
            }
        }
    }

    private func customErrorMessageHandler(_ fieldState: OPCardFieldStateProtocol, _ ignoreUneditedFieldErrors: Bool) -> String {
        guard let cvvView = cvvView else { return "" }

        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 getTextAlignment(_ alignmentString: String) -> NSTextAlignment {
        if alignmentString == DataKeys.AlignmentRightKey {
            return .right
        } else if alignmentString == DataKeys.AlignmentCenterKey {
            return .center
        } else {
            return .left
        }
    }

    // MARK: - Internal delegate callbacks (called by adapter)

    func handleFieldChanged() {
        emitCvvChangeEvent()
    }

    func handleBeginEditing() {
        onFocus?(getEventDetails())
    }

    func handleEndEditing() {
        onBlur?(getEventDetails())
    }

    private func emitCvvChangeEvent() {
        guard onCvvChange != nil else { return }
        onCvvChange?(getEventDetails())
    }

    private func getEventDetails() -> NSDictionary {
        guard let cvvView = cvvView else { return [:] }

        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 as NSDictionary
    }
}
