// 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 PaymentCardDetailsViewDelegateAdapter: NSObject, OPPaymentCardDetailsViewDelegate {
    weak var fabricView: PaymentCardDetailsView?

    func paymentCardDetailsViewDidChange(with fieldStates: NSDictionary, isValid: Bool) {
        fabricView?.handleCardDetailsChange()
    }

    func paymentCardDetailsViewDidBeginEditing(_ cardDetails: OPPaymentCardDetailsView) {
        fabricView?.handleBeginEditing()
    }

    func paymentCardDetailsViewDidEndEditing(_ cardDetails: OPPaymentCardDetailsView) {
        fabricView?.handleEndEditing()
    }

    func paymentCardDetailsViewFieldDidBeginEditing(_ cardDetails: OPPaymentCardDetailsView, field: OPCardField) {
        fabricView?.handleFieldBeginEditing(field: field.description)
    }
}

@objc(PaymentCardDetailsView)
public class PaymentCardDetailsView: UIView {
    private var cardDetailsView: OPPaymentCardDetailsView?
    private var _customErrorMessages: CustomErrorMessages? = nil
    private var delegateAdapter: PaymentCardDetailsViewDelegateAdapter?

    @objc public var onCardInputChange: ((NSDictionary) -> Void)?
    @objc public var onFocus: ((NSDictionary) -> Void)?
    @objc public var onBlur: ((NSDictionary) -> Void)?
    @objc public var onFocusField: ((NSDictionary) -> Void)?
    @objc public var onPaymentMethodResult: ((NSDictionary) -> Void)?

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

    @objc public var postalCodeEnabled: Bool = true {
        didSet {
            cardDetailsView?.postalCodeEntryEnabled = postalCodeEnabled
        }
    }

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

    @objc public var displayGeneratedErrorMessages: Bool = false {
        didSet {
            cardDetailsView?.displayGeneratedErrorMessages = displayGeneratedErrorMessages
        }
    }

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

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

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

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

        view.displayGeneratedErrorMessages = false

        OPPaymentCardDetailsView.errorMessageHandler = errorMessageHandler(_:_:_:)

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

        cardDetailsView = view
    }

    @objc public func updateCardStyles(_ styles: NSDictionary) {
        guard let cardView = cardDetailsView else { return }

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

        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))
        cardView.font = UIFontMetrics.default.scaledFont(for: font!)

        cardView.setNeedsLayout()
    }

    @objc public func updatePlaceholders(_ placeholders: NSDictionary) {
        guard let cardView = cardDetailsView else { return }

        if let numberPlaceholder = placeholders[DataKeys.NumberPlaceholderKey] as? String {
            cardView.numberPlaceholder = numberPlaceholder
        }
        if let expirationPlaceholder = placeholders[DataKeys.ExpirationPlaceholderKey] as? String {
            cardView.expirationPlaceholder = expirationPlaceholder
        }
        if let cvvPlaceholder = placeholders[DataKeys.CvvPlaceholderKey] as? String {
            cardView.cvvPlaceholder = cvvPlaceholder
        }
        if let postalCodePlaceholder = placeholders[DataKeys.PostalCodePlaceholderKey] as? String {
            cardView.postalCodePlaceholder = postalCodePlaceholder
        }
    }

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

    @objc public func focus() {
        // Default: focus the card number field
        cardDetailsView?.becomeFirstResponder(at: .number)
    }

    @objc public func focusField(_ field: String) {
        guard let cardView = cardDetailsView else { return }

        switch field {
        case DataKeys.CardNumberFieldKey:
            cardView.becomeFirstResponder(at: .number)
        case DataKeys.ExpirationFieldKey:
            cardView.becomeFirstResponder(at: .expiration)
        case DataKeys.CvvFieldKey:
            cardView.becomeFirstResponder(at: .cvv)
        case DataKeys.PostalCodeFieldKey:
            cardView.becomeFirstResponder(at: .postalCode)
        default:
            break
        }
    }

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

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

    @objc public func createPaymentMethod() {
        guard let cardView = cardDetailsView else { return }

        guard let params = cardView.getPaymentMethodParams() else {
            // Emit result with error - use NSString explicitly for proper bridging
            let errorMessage = errorMessageHandler(cardView.fieldStates as NSDictionary, cardView.cardType, false)
            let errorDict: NSDictionary = [
                DataKeys.MessageKey as NSString: errorMessage as NSString,
                DataKeys.CodeKey as NSString: getValidationErrorCode() as NSString
            ]
            let resultDict: NSDictionary = [DataKeys.ErrorKey as NSString: errorDict]
            onPaymentMethodResult?(resultDict)
            return
        }

        OloPayAPI().createPaymentMethod(with: params) { [weak self] paymentMethod, 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?.onPaymentMethodResult?(resultDict)
                    return
                }

                guard let paymentMethod = paymentMethod 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?.onPaymentMethodResult?(resultDict)
                    return
                }

                // Emit result with success
                let resultDict: NSDictionary = [DataKeys.PaymentMethodKey as NSString: paymentMethod.toDictionary()]
                self?.onPaymentMethodResult?(resultDict)
            }
        }
    }

    private func errorMessageHandler(_ cardState: NSDictionary, _ cardBrand: OPCardBrand, _ ignoreUneditedFieldErrors: Bool) -> String {
        guard let cardView = cardDetailsView else { return "" }

        let defaultError = CustomErrorMessages.getDefaultErrorMessage(
            ignoreUneditedFieldErrors,
            cardView.fieldStates,
            cardBrand
        )

        var customError: String? = nil

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

        return customError ?? defaultError
    }

    private func getValidationErrorCode() -> String {
        guard let cardView = cardDetailsView else { return ErrorCodes.InvalidCardDetails }

        if let numberState = cardView.fieldStates[OPCardField.number], !numberState.isValid {
            return ErrorCodes.InvalidNumber
        } else if let expirationState = cardView.fieldStates[OPCardField.expiration], !expirationState.isValid {
            return ErrorCodes.InvalidExpiration
        } else if let cvvState = cardView.fieldStates[OPCardField.cvv], !cvvState.isValid {
            return ErrorCodes.InvalidCvv
        } else if let postalCodeState = cardView.fieldStates[OPCardField.postalCode], !postalCodeState.isValid {
            return ErrorCodes.InvalidPostalCode
        }

        return ErrorCodes.InvalidCardDetails
    }

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

    func handleCardDetailsChange() {
        emitCardDetailsChangedEvent()
    }

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

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

    func handleFieldBeginEditing(field: String) {
        emitCardDetailsChangedEvent()
        onFocusField?([DataKeys.FieldKey: field])
    }

    private func emitCardDetailsChangedEvent() {
        guard let cardView = cardDetailsView, onCardInputChange != nil else { return }

        let cardState = cardView.fieldStates

        var cardDetails = [
            DataKeys.IsValidKey: cardView.isValid,
            DataKeys.CardTypeKey: cardView.cardType.description
        ] as [String : Any]

        if !cardView.isValid {
            var errors = [String: String]()

            let editedFieldsErrorMessage = errorMessageHandler(cardState as NSDictionary, cardView.cardType, true)
            if (!editedFieldsErrorMessage.isEmpty) {
                errors[DataKeys.EditedFieldsErrorKey] = editedFieldsErrorMessage
            }

            let allFieldsErrorMessage = errorMessageHandler(cardState as NSDictionary, cardView.cardType, false)
            if (!allFieldsErrorMessage.isEmpty) {
                errors[DataKeys.AllFieldsErrorKey] = allFieldsErrorMessage
            }

            if (!editedFieldsErrorMessage.isEmpty || !allFieldsErrorMessage.isEmpty) {
                cardDetails[DataKeys.ErrorsKey] = errors
            }
        }

        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[DataKeys.InvalidFieldsKey] = invalidFields
        }

        if !emptyFields.isEmpty {
            cardDetails[DataKeys.EmptyFieldsKey] = emptyFields
        }

        onCardInputChange?(cardDetails as NSDictionary)
    }

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

        // Build state string based on card validity
        let state = cardView.isValid ? DataKeys.ValidStateValue : DataKeys.InvalidStateValue

        var eventDetails = [DataKeys.StateKey: state] as [String: Any]

        // Add per-field errors if card is invalid
        if !cardView.isValid {
            var errors = [String: String]()

            for (field, fieldState) in cardView.fieldStates {
                if !fieldState.isValid && fieldState.wasEdited {
                    let errorMessage = cardView.getErrorMessage(true)
                    if !errorMessage.isEmpty {
                        errors[field.description.lowercased()] = errorMessage
                    }
                }
            }

            if !errors.isEmpty {
                eventDetails[DataKeys.ErrorsKey] = errors
            }
        }

        return eventDetails as NSDictionary
    }
}
