import { useCallback, useEffect, useRef, useState } from '@wordpress/element';
import { isEmpty, isEqual } from 'lodash';
import {
	CustomsItem,
	CustomsState,
	RequestPackage,
	RequestPackageWithCustoms,
	ShipmentItem,
} from 'types';
import { select, useSelect } from '@wordpress/data';
import type { FormErrors } from '@woocommerce/components';
import { contentTypes } from '../customs/constants';
import { normalizeITN } from '../customs/validators';
import {
	isCountryInEU,
	isCustomsRequired,
	isHSTariffNumberValid,
	sanitizeHSTariffNumber,
} from 'utils';
import { useShipmentState } from './shipment';
import { labelPurchaseStore } from 'data/label-purchase';

const getInitialShipmentCustomsState = < T >( items: T ) => ( {
	items,
	contentsType: contentTypes[ 0 ].value,
	restrictionType: 'none',
	isReturnToSender: false,
	itn: '',
} );

export function useCustomsState(
	currentShipmentId: string,
	shipments: Record< string, ShipmentItem[] >,
	selections: Record< string, ShipmentItem[] >,
	getShipmentItems: ReturnType<
		typeof useShipmentState
	>[ 'getShipmentItems' ],
	getSelectionItems: ReturnType<
		typeof useShipmentState
	>[ 'getSelectionItems' ],
	getShipmentOrigin: ReturnType<
		typeof useShipmentState
	>[ 'getShipmentOrigin' ],
	getShipmentDestination: ReturnType<
		typeof useShipmentState
	>[ 'getShipmentDestination' ]
) {
	const origin = getShipmentOrigin();
	const destination = getShipmentDestination();

	const storedCustomsInformationForShipment = useSelect(
		( s ) =>
			s( labelPurchaseStore ).getCustomsInformation( currentShipmentId ),
		[ currentShipmentId ]
	);

	const isCustomsNeeded = useCallback(
		() => isCustomsRequired( origin, destination ),
		[ origin, destination ]
	);

	const [ errors, setErrors ] = useState<
		FormErrors< CustomsState > & {
			items: FormErrors< CustomsItem >[];
		}
	>( {
		items: getShipmentItems()?.map( () => ( {} ) ),
	} );

	const getCustomsItems = useCallback(
		( shipmentId = currentShipmentId ) => {
			const items =
				getSelectionItems( shipmentId )?.length > 0
					? getSelectionItems( shipmentId )
					: getShipmentItems( shipmentId );

			return items?.map( ( props ) => ( {
				...props,
				description:
					props.meta?.customs_info?.description ?? props.name,
				hsTariffNumber:
					props.meta?.customs_info?.hs_tariff_number ?? '',
				originCountry:
					props.meta?.customs_info?.origin_country ?? origin.country,
			} ) );
		},
		[
			getSelectionItems,
			getShipmentItems,
			currentShipmentId,
			origin.country,
		]
	);

	const initialCustomsInfo: CustomsState =
		storedCustomsInformationForShipment ??
		getInitialShipmentCustomsState( getCustomsItems() );

	const initialCustomsState: Record<
		typeof currentShipmentId,
		CustomsState
	> = Object.keys( shipments ).reduce(
		(
			customs: Record< typeof currentShipmentId, CustomsState >,
			id: string
		) => {
			customs[ id ] = initialCustomsInfo;
			return customs;
		},
		{}
	);
	const [ state, setState ] =
		useState< Record< typeof currentShipmentId, CustomsState > >(
			initialCustomsState
		);

	const previousStateRef = useRef( state );
	/**
	 * Make sure on shipment change, the shipment has the correct customs information
	 * - If the shipment has no customs information, set it to the default
	 * - If the shipment has customs information, set it to the stored information
	 */
	useEffect( () => {
		const allTheCustomItems = (
			previousStateRef.current
				? Object.values( previousStateRef.current )
				: Object.values( state )
		)
			.map( ( { items } ) => items )
			.flat();

		const currentShipmentCustomsInfo =
			select( labelPurchaseStore ).getCustomsInformation(
				currentShipmentId
			) ??
			getInitialShipmentCustomsState(
				getCustomsItems()?.map(
					( item ) =>
						allTheCustomItems.find(
							( i ) => i.product_id === item.product_id
						) ?? item
				)
			);

		if ( isEmpty( state[ currentShipmentId ] ) ) {
			setState( ( prev ) => ( {
				...prev,
				[ currentShipmentId ]: currentShipmentCustomsInfo,
			} ) );
		}
	}, [ currentShipmentId, getCustomsItems, state ] );

	const getCustomsState = useCallback(
		() => state[ currentShipmentId ],
		[ state, currentShipmentId ]
	);
	const setCustomsState = useCallback(
		( newState: CustomsState ) => {
			setState( ( prev ) => ( {
				...prev,
				[ currentShipmentId ]: newState,
			} ) );
		},
		[ currentShipmentId ]
	);

	/**
	 * Reset the customs errors if customs is no longer needed.
	 *
	 * Make sure that if a destination changes, but we're no longer showing the customs form,
	 * we reset the errors to avoid showing errors from a previous destination.
	 * The reason why we do not have to do something similar when the form is present is because
	 * it will re-validate the customs state when displayed.
	 */
	useEffect( () => {
		if ( ! isCustomsNeeded() ) {
			const newErrors = {
				items: getShipmentItems()?.map( () => ( {} ) ),
			};

			setErrors( ( prevErrors ) => {
				if ( ! isEqual( prevErrors, newErrors ) ) {
					return newErrors;
				}
				return prevErrors;
			} );
		}
	}, [ getShipmentItems, isCustomsNeeded ] );

	const maybeApplyCustomsToPackage = useCallback(
		< T = RequestPackage >(
			pkg: T
		): RequestPackageWithCustoms< T > | T => {
			if ( ! isCustomsNeeded() ) {
				return pkg;
			}
			const {
				contentsType: contents_type,
				contentsExplanation: contents_explanation,
				restrictionType: restriction_type,
				restrictionComments: restriction_comments,
				isReturnToSender,
				itn,
				items,
			} = getCustomsState();

			return {
				...pkg,
				contents_type,
				...( contents_type === 'other'
					? { contents_explanation }
					: {} ),
				restriction_type,
				...( restriction_type === 'other'
					? { restriction_comments }
					: {} ),
				non_delivery_option: isReturnToSender ? 'return' : 'abandon',
				itn: normalizeITN( itn ),
				items: items.map(
					( {
						description,
						quantity,
						weight,
						hsTariffNumber,
						originCountry: origin_country,
						product_id,
						price,
					} ) => ( {
						description,
						quantity,
						weight: parseFloat( weight ),
						hs_tariff_number: sanitizeHSTariffNumber(
							hsTariffNumber ?? ''
						),
						origin_country,
						product_id,
						value: parseFloat( price ),
					} )
				),
			};
		},
		[ getCustomsState, isCustomsNeeded ]
	);

	const isHSTariffNumberRequired = useCallback( () => {
		const destinationAddress = destination;
		return destinationAddress
			? isCountryInEU( destinationAddress.country )
			: false;
	}, [ destination ] );

	const hasErrors = useCallback( () => {
		const { items, ...rest } = errors;

		if ( isHSTariffNumberRequired() ) {
			const customsState = getCustomsState();
			const customItems = customsState ? customsState.items : [];
			const hasInvalidHsTariff = customItems.some(
				( { hsTariffNumber } ) =>
					! isHSTariffNumberValid( hsTariffNumber )
			);
			if ( hasInvalidHsTariff ) {
				return true;
			}
		}
		return (
			Object.values( rest ).length ||
			items.some( ( i ) => Object.values( i ).length )
		);
	}, [ errors, getCustomsState, isHSTariffNumberRequired ] );

	/**
	 * Update the customs state based on the shipment items.
	 * This is to be called when the shipment items change.
	 */
	const updateCustomsItems = () => {
		previousStateRef.current = state; // Store previous state reference for comparison

		// Flatten the customs items from all shipments to avoid redundant iterations
		const allCustomItems = Object.values( state ).flatMap(
			( { items } ) => items
		);

		// Reduce over the state and update the customs items for each shipment
		const newState = Object.entries( state ).reduce(
			( acc, [ shipmentId, customsState ] ) => {
				// Get the customs items for the current shipmentId
				const customsItems = getCustomsItems( shipmentId );

				// If no customs items, return the accumulator as-is (shipment not eligible for customs)
				if ( ! customsItems ) {
					return acc;
				}

				// Map over the customs items and merge with existing items if found
				const updatedItems = customsItems.map( ( customsItem ) => {
					// Try to find the customs item in the current shipment or across all shipments
					const existingItem =
						customsState.items.find(
							( item ) =>
								item.product_id === customsItem.product_id
						) ??
						allCustomItems.find(
							( item ) =>
								item.product_id === customsItem.product_id
						);

					// Merge customs item with existing data (if any), otherwise use new customs item
					return {
						...customsItem,
						...( existingItem ?? {} ), // Retain existing values if present
					};
				} );

				// Return updated state for this shipment
				return {
					...acc,
					[ shipmentId ]: {
						...customsState,
						items: updatedItems,
					},
				};
			},
			{} as typeof state
		);

		// Update the state with the new customs data
		setState( newState );
	};

	const updateCustomsItemsBasedOnSelections = useCallback(
		( currentState: Record< string, CustomsState > ) => {
			// Get selections for current shipment
			const shipmentSelections = selections[ currentShipmentId ];

			// Exit early if no selections exist
			if ( ! shipmentSelections?.length ) {
				return currentState;
			}

			// Get existing customs state or initialize new items
			const shipmentCustomsState = currentState[ currentShipmentId ];
			const customsItems = shipmentCustomsState?.items?.length
				? shipmentCustomsState.items
				: getCustomsItems();

			// Filter customs items based on selections
			const updatedItems = customsItems
				// Only keep items that exist in selections
				.filter( ( item ) =>
					shipmentSelections.some(
						( selection ) =>
							selection.product_id === item.product_id
					)
				)
				// Update quantities based on selections
				.map( ( item ) => {
					const itemQuantity = shipmentSelections
						.filter(
							( selection ) =>
								selection.product_id === item.product_id
						)
						.reduce(
							( total, { quantity } ) =>
								total + ( quantity ?? 1 ),
							0
						);

					return {
						...item,
						quantity: itemQuantity,
					};
				} ) as CustomsItem[];

			// Add missing items from shipmentSelections
			const missingItems = shipmentSelections.filter(
				( selection ) =>
					! updatedItems.some(
						( updatedItem ) =>
							updatedItem.product_id === selection.product_id
					)
			);

			// Return updated state
			return {
				...currentState,
				[ currentShipmentId ]: {
					...shipmentCustomsState,
					items: [
						...updatedItems,
						...getCustomsItems().filter( ( item ) =>
							missingItems.find(
								( missingItem ) =>
									missingItem.product_id === item.product_id
							)
						),
					],
				},
			};
		},
		[ currentShipmentId, getCustomsItems, selections ]
	);

	useEffect( () => {
		setState( updateCustomsItemsBasedOnSelections );
	}, [ updateCustomsItemsBasedOnSelections ] );

	return {
		getCustomsState,
		setCustomsState,
		maybeApplyCustomsToPackage,
		hasErrors,
		setErrors,
		isCustomsNeeded,
		isHSTariffNumberRequired,
		updateCustomsItems,
	};
}
