import { useIsMobileViewport } from '@greenpay/hooks'
import { usePaymentDispatch, useSettingsSelect, useValidationDispatch } from '@greenpay/redux'
import {
	appendRibbitSessionBillingToFormData,
	checkRibbitBankTan,
	getCheckoutBillingDetails,
	validateRibbitSessionBilling,
} from '@greenpay/utils'
import { useEffect, useRef, useState } from '@wordpress/element'
import { RIBBITConnect } from 'react-ribbit-connect'
import { parseConnectEventData } from './parseConnectEventData'

interface RibbitWidgetProps {
	showWidget: boolean
	onWidget: (value: boolean) => void
	onError: (message: string) => void
	onBusyChange?: (busy: boolean) => void
}

const RibbitWidget: React.FC<RibbitWidgetProps> = ({ showWidget, onWidget, onError, onBusyChange }) => {
	const settings = useSettingsSelect().getSettings()
	const { setRibbit, setMethod, setAccountMask } = usePaymentDispatch()
	const { setPlaidError, setPlaidTouched } = useValidationDispatch()
	const isMobile = useIsMobileViewport()
	const [sessionToken, setSessionToken] = useState<string | null>(null)
	const [loading, setLoading] = useState(false)
	const connectDetailsRef = useRef<ReturnType<typeof parseConnectEventData>>({
		customerToken: '',
		accountToken: '',
		institutionName: '',
		accountMask: '',
	})

	useEffect(() => {
		if (!showWidget) {
			connectDetailsRef.current = {
				customerToken: '',
				accountToken: '',
				institutionName: '',
				accountMask: '',
			}
		}
	}, [showWidget])

	useEffect(() => {
		const ajaxUrl = settings?.ribbitSessionAjaxUrl
		if (!showWidget || !ajaxUrl) return

		let cancelled = false

		const fetchSession = async () => {
			setLoading(true)
			setSessionToken(null)

			const billing = getCheckoutBillingDetails()
			const validation = validateRibbitSessionBilling(billing)

			if (!validation.valid) {
				if (!cancelled) {
					onError(validation.message)
					onWidget(false)
					setLoading(false)
				}
				return
			}

			try {
				const formData = new FormData()
				formData.append('action', 'get_ribbit_connect_session')
				formData.append('_ajax_nonce', settings.ribbitSessionNonce ?? '')
				appendRibbitSessionBillingToFormData(formData, billing)

				const response = await fetch(ajaxUrl, {
					method: 'POST',
					body: formData,
					credentials: 'same-origin',
				})
				const json = (await response.json()) as {
					success: boolean
					data?: { sessionToken?: string; message?: string; code?: string }
				}

				if (cancelled) return

				if (!json.success || !json.data?.sessionToken) {
					const message = json.data?.message ?? 'Bank Login failed. Please refresh the page and try again!'
					onError(message)
					onWidget(false)
					return
				}

				setSessionToken(json.data.sessionToken)
			} catch {
				if (!cancelled) {
					onError('Bank Login failed. Please refresh the page and try again!')
					onWidget(false)
				}
			} finally {
				if (!cancelled) {
					setLoading(false)
				}
			}
		}

		fetchSession()

		return () => {
			cancelled = true
		}
	}, [showWidget, settings, onError, onWidget])

	const mergeConnectDetails = (eventData: unknown) => {
		const parsed = parseConnectEventData(eventData)
		const current = connectDetailsRef.current

		connectDetailsRef.current = {
			customerToken: parsed.customerToken || current.customerToken,
			accountToken: parsed.accountToken || current.accountToken,
			institutionName: parsed.institutionName || current.institutionName,
			accountMask: parsed.accountMask || current.accountMask,
			bankId: parsed.bankId ?? current.bankId,
		}

		return connectDetailsRef.current
	}

	const handleConnectMessage = (_functionName: string, message: unknown) => {
		mergeConnectDetails(message)
	}

	const handleComplete = async (eventData: unknown) => {
		const tokens = mergeConnectDetails(eventData)

		if (!tokens.accountToken) {
			onError('Bank login was unsuccessful, please try again.')
			onWidget(false)
			return
		}

		onWidget(false)
		onBusyChange?.(true)

		const institutionName = tokens.institutionName || 'Bank Account'

		try {
			const tanResult = await checkRibbitBankTan(tokens.bankId, institutionName, settings)

			setRibbit({
				customerToken: tokens.customerToken || settings?.clientId || '',
				accountToken: tokens.accountToken,
				institutionName,
				accountMask: tokens.accountMask,
				success: true,
				isTAN: tanResult.isTAN,
				helpLink: tanResult.helpLink,
				bankId: tokens.bankId,
			})

			if (!tanResult.isTAN) {
				setAccountMask(tokens.accountMask)
				setMethod('ribbit')
			}

			setPlaidError('')
			setPlaidTouched(false)
		} catch {
			onError('Bank login was unsuccessful, please try again.')
		} finally {
			onBusyChange?.(false)
		}
	}

	const handleExit = () => {
		onWidget(false)
	}

	const handleManualEnrollment = () => {
		onError('Manual bank entry is not supported for bank login. Use Manual Debit Entry or try another bank.')
		onWidget(false)
	}

	if (!showWidget || loading || !sessionToken) {
		return null
	}

	return (
		<RIBBITConnect
			key={isMobile ? 'ribbit-mobile' : 'ribbit-desktop'}
			token={sessionToken}
			open={showWidget}
			fullscreen={isMobile}
			environment={settings?.ribbitEnvironment ?? 'Test'}
			settings={{
				curtainColor: 'rgba(0,0,0,0.5)',
			}}
			onComplete={handleComplete}
			onExit={handleExit}
			onManualEnrollmentSelected={handleManualEnrollment}
			onMessage={handleConnectMessage}
		/>
	)
}

export default RibbitWidget
