import { __, sprintf } from '@wordpress/i18n';
import type { FormErrors } from '@woocommerce/components';
import { isEmpty, isNil } from 'lodash';
import { CustomsItem, CustomsValidationInput, Destination } from 'types';
import {
	isCountryInEU,
	isHSTariffNumberValid,
	hasInvalidChar,
	USPS_ITN_REQUIRED_DESTINATIONS,
} from 'utils';
import { getCurrentOrderItems } from 'utils/order';
import { itnMatchingRegex } from './constants';

export const createLocalErrors = (
	items: CustomsItem[]
): CustomsValidationInput[ 'errors' ] => ( {
	items: Array( items.length )
		.fill( 0 )
		.map( () => ( {} ) as FormErrors< CustomsItem > ),
} );

/**
 * Normalize the ITN to a consistent format, be sure to run through the itnMatchingRegex first.
 *
 * @param {string} itn - The ITN to normalize.
 * @return {string} The normalized ITN.
 */
export const normalizeITN = ( itn: string ): string => {
	// Trim whitespace and convert to uppercase for standardization
	const cleanITN = itn
		.trim()
		.toUpperCase()
		.replace( /^(?:AES\s*ITN:?\s*)?(?:AES\s*)?/, '' );

	// Check if the ITN is a 14-digit number, optionally prefixed with 'X'
	if ( /^X?\d{14}$/.test( cleanITN.replace( /\s+/g, '' ) ) ) {
		// Extract digits and format as 'AES X{14 digits}'
		const digits = cleanITN.replace( /[^0-9]/g, '' );
		return `AES X${ digits }`;
	}

	// If the ITN starts with 'NOEEI', normalize spacing and lowercase text within brackets
	if ( cleanITN.startsWith( 'NOEEI' ) ) {
		// Convert text within brackets to lowercase
		return cleanITN
			.replace( /\s+/g, ' ' )
			.replace(
				/\(([^)]+)\)/g,
				( match, p1 ) => `(${ p1.toLowerCase() })`
			);
	}

	// Return the cleaned ITN if no specific format is matched
	return cleanITN;
};

export const validateContentTypes = ( {
	values: { contentsType, contentsExplanation, items, ...rest },
	errors,
}: CustomsValidationInput ): CustomsValidationInput => {
	const localErrors = createLocalErrors( items );
	if ( contentsType === 'other' ) {
		if ( ! contentsExplanation || contentsExplanation.length < 3 ) {
			localErrors.contentsExplanation = __(
				'Please describe what kind of goods this package contains.',
				'woocommerce-shipping'
			);
		}
	}

	return {
		errors: {
			...errors,
			...localErrors,
		},
		values: { contentsType, contentsExplanation, items, ...rest },
	};
};

export const validateRestrictionType = ( {
	values: { restrictionType, restrictionComments, items, ...rest },
	errors,
}: CustomsValidationInput ): CustomsValidationInput => {
	const localErrors = createLocalErrors( items );
	if ( restrictionType === 'other' ) {
		if ( ! restrictionComments || restrictionComments.length < 3 ) {
			localErrors.restrictionComments = __(
				'Please describe what kind of restrictions this package must have.',
				'woocommerce-shipping'
			);
		}
	}

	return {
		errors: {
			...errors,
			...localErrors,
		},
		values: { restrictionType, restrictionComments, items, ...rest },
	};
};

export const calculateTotalPrice = (
	orderItems: { total: string }[]
): number => {
	return orderItems.reduce(
		( accu: number, curr: { total: string } ) =>
			accu + parseFloat( curr.total ),
		0
	);
};

export const calculateValuesByProductId = (
	items: { product_id: number; price: string; quantity: number }[]
): Record< number, number > => {
	return items.reduce(
		( acc: Record< number, number >, { product_id, price, quantity } ) => {
			acc[ product_id ] = parseFloat( `${ price }` ) * quantity;
			return acc;
		},
		{}
	);
};

export const calculateValuesByTariffNumber = (
	items: { product_id: number; hsTariffNumber: string }[],
	valuesByProductId: Record< number, number >
): Record< string, number > => {
	return items.reduce(
		( acc: Record< string, number >, { product_id, hsTariffNumber } ) => {
			if ( hsTariffNumber?.length === 6 ) {
				if ( ! acc[ hsTariffNumber ] ) {
					acc[ hsTariffNumber ] = 0;
				}
				acc[ hsTariffNumber ] += valuesByProductId[ product_id ];
			}
			return acc;
		},
		{}
	);
};

export const findClassesAbove2500usd = (
	items: { product_id: number; hsTariffNumber: string }[],
	valuesByTariffNumber: Record< string, number >
): Set< string > => {
	return items.reduce( ( acc: Set< string >, { hsTariffNumber } ) => {
		if (
			hsTariffNumber !== '' &&
			valuesByTariffNumber[ hsTariffNumber ] > 2500
		) {
			acc.add( hsTariffNumber );
		}
		return acc;
	}, new Set< string >() );
};

export const validateITN =
	( {
		country,
		countryName,
	}: {
		country: Destination[ 'country' ];
		countryName: string;
	} ) =>
	( {
		values: { itn, items, ...rest },
		errors,
	}: CustomsValidationInput ): CustomsValidationInput => {
		const localErrors = createLocalErrors( items );
		const orderItems = getCurrentOrderItems();
		const totalPrice = calculateTotalPrice( orderItems );

		if ( ! itn && totalPrice > 2500 ) {
			localErrors.itn = __(
				'For shipments exceeding $2,500, obtaining a 14-digit AES ITN is required for U.S. export reporting.',
				'woocommerce-shipping'
			);
		}

		const valuesByProductId = calculateValuesByProductId( items );
		const valuesByTariffNumber = calculateValuesByTariffNumber(
			items,
			valuesByProductId
		);
		const classesAbove2500usd = findClassesAbove2500usd(
			items,
			valuesByTariffNumber
		);

		if ( itn && itn.length > 0 ) {
			if ( ! itnMatchingRegex.test( itn.trim() ) ) {
				localErrors.itn = __(
					'Please enter a valid ITN in one of these formats: X12345678901234, AES X12345678901234, or NOEEI 30.37(a)',
					'woocommerce-shipping'
				);
			}
		} else if ( country !== 'CA' ) {
			if ( ! isEmpty( classesAbove2500usd ) ) {
				localErrors.itn = sprintf(
					// translators: %s is the tariff number
					__(
						'International Transaction Number is required for shipping items valued over $2,500 per tariff number. Products with tariff number %s add up to more than $2,500.',
						'woocommerce-shipping'
					),
					classesAbove2500usd.values().next().value ?? ''
				);
			} else if ( USPS_ITN_REQUIRED_DESTINATIONS.includes( country ) ) {
				localErrors.itn = sprintf(
					// translators: %s is the country name
					__(
						'International Transaction Number is required for shipments to %s',
						'woocommerce-shipping'
					),
					countryName
				);
			}
		}

		return {
			errors: {
				...errors,
				...localErrors,
			},
			values: {
				...rest,
				items,
				// Normalize the ITN before sending to the API
				itn: itn ? normalizeITN( itn ) : itn,
			},
		};
	};

export const validateItems =
	( { country }: Pick< Destination, 'country' > ) =>
	( {
		values: { items, ...rest },
		errors,
	}: CustomsValidationInput ): CustomsValidationInput => {
		const localErrors = createLocalErrors( items );
		items.forEach(
			( { description, weight, hsTariffNumber, price }, index ) => {
				if ( ! description ) {
					localErrors.items[ index ].description = __(
						'This field is required',
						'woocommerce-shipping'
					);
				} else if ( hasInvalidChar( description ) ) {
					localErrors.items[ index ].description = __(
						'This field contains invalid characters.',
						'woocommerce-shipping'
					);
				}

				if ( isNil( weight ) || weight === '' ) {
					localErrors.items[ index ].weight = __(
						'This field is required',
						'woocommerce-shipping'
					);
				} else if ( ! ( parseFloat( weight ) > 0 ) ) {
					localErrors.items[ index ].weight = __(
						'Weight must be greater than zero',
						'woocommerce-shipping'
					);
				}
				if ( isNil( price ) || price === '' ) {
					localErrors.items[ index ].price = __(
						'This field is required',
						'woocommerce-shipping'
					);
				} else if ( ! ( parseFloat( price ) > 0 ) ) {
					localErrors.items[ index ].price = __(
						'Declared value must be greater than zero',
						'woocommerce-shipping'
					);
				}

				const shouldValidateHSTariffNumber = isCountryInEU( country )
					? true
					: Boolean( hsTariffNumber );
				if (
					shouldValidateHSTariffNumber &&
					! isHSTariffNumberValid( hsTariffNumber ) &&
					localErrors?.items?.[ index ]
				) {
					localErrors.items[ index ].hsTariffNumber = __(
						'The tariff number must be between 6 and 12 digits long',
						'woocommerce-shipping'
					);
				}
				return errors;
			}
		);

		return {
			errors: {
				...errors,
				...localErrors,
			},
			values: { items, ...rest },
		};
	};
