import { mapKeys, mapValues } from 'lodash';
import {
	camelCaseKeys,
	camelCasePackageResponse,
	createReducer,
	getCarrierStrategies,
	getConfig,
	getCustomsInformation,
	getLabelDestinations,
	getLabelOrigins,
	getPurchasedLabels,
	getSelectedHazmat,
	getSelectedRateOptions,
	getSelectedRates,
	groupRatesByCarrier,
} from 'utils';
import {
	ORDER_STATUS_UPDATE_FAILED,
	ORDER_STATUS_UPDATED,
	RATES_FETCHED,
	RATES_RESET,
	STATE_RESET,
} from './action-types';
import { LabelPurchaseState } from '../types';
import {
	LabelPurchaseActions,
	LabelPurchaseSuccessAction,
	LabelStatusResolvedAction,
	OrderStatusUpdatedAction,
	OrderStatusUpdatedFailedAction,
	PackageUpdateAction,
	PackageUpdateFailedAction,
	RatesFetchedAction,
	StageLabelsNewShipmentIdsAction,
} from './types.d';
import {
	PACKAGES_UPDATE,
	PACKAGES_UPDATE_ERROR,
} from './packages/action-types';
import {
	LABEL_PURCHASE_SUCCESS,
	LABEL_STAGE_NEW_SHIPMENT_IDS,
	LABEL_STATUS_RESOLVED,
} from './label/action-types';
import { SelectedRates } from 'types';
import { LABEL_PURCHASE_STATUS } from '../constants';

const {
	packagesSettings: { packages },
} = getConfig();

const defaultState = (): LabelPurchaseState =>
	( {
		packages: {
			...camelCasePackageResponse( packages ),
			errors: {},
		},
		rates: {},
		labels: getPurchasedLabels(),
		selectedRates: getSelectedRates(),
		selectedHazmatConfig: getSelectedHazmat(),
		selectedDestinations: getLabelDestinations(),
		selectedOrigins: getLabelOrigins(),
		purchaseAPIErrors: {},
		customsInformation: getCustomsInformation(),
		carrierStrategies: mapValues( getCarrierStrategies(), ( value ) =>
			camelCaseKeys( value )
		),
		selectedRateOptions: getSelectedRateOptions(),
	} as const );

export const labelPurchaseReducer = createReducer( defaultState() )
	.on( PACKAGES_UPDATE, ( state, { payload }: PackageUpdateAction ) => ( {
		...state,
		packages: {
			...state.packages,
			...payload,
		},
	} ) )
	.on(
		PACKAGES_UPDATE_ERROR,
		( state, { payload }: PackageUpdateFailedAction ) => {
			return {
				...state,
				packages: {
					...state.packages,
					errors: payload,
				},
			};
		}
	)
	.on( RATES_FETCHED, ( state, { payload }: RatesFetchedAction ) => ( {
		...state,
		rates: {
			...state.rates,
			...groupRatesByCarrier( payload ),
		},
	} ) )
	.on( RATES_RESET, ( state ) => ( {
		...state,
		rates: undefined,
	} ) )
	.on( STATE_RESET, () => ( { ...defaultState() } ) )
	.on(
		LABEL_STATUS_RESOLVED,
		( state, { payload }: LabelStatusResolvedAction ) => ( {
			...state,
			labels: payload?.labelId
				? mapValues( state.labels, ( shipmentLabels ) => {
						return shipmentLabels.map( ( label ) => {
							if ( label.labelId === payload.labelId ) {
								return payload;
							}
							return label;
						} );
				  } )
				: state.labels,
			// If the label is refunded or status is unknown or purchase_error, we need to remove the selected rate from the selectedRates object
			selectedRates: Object.entries( state.selectedRates ?? {} ).reduce(
				( acc, [ key, rate ] ) => {
					// Find the shipment index (0, 1, 2, etc.) using label.labelId
					const shipmentIndex =
						( payload?.refund ??
							[
								LABEL_PURCHASE_STATUS.PURCHASE_ERROR,
								LABEL_PURCHASE_STATUS.UNKNOWN,
							].includes( payload?.status ?? '' ) ) &&
						typeof state.labels === 'object' &&
						state.labels
							? Object.keys( state.labels ).find(
									( shipmentId ) => {
										return state.labels![ shipmentId ].some(
											( label ) =>
												label.labelId ===
												payload?.labelId
										);
									}
							  )
							: null;

					if (
						shipmentIndex &&
						key === `shipment_${ shipmentIndex }`
					) {
						return acc;
					}

					// Safe to cast as keyof SelectedRates because we know the key is in the object
					acc[ key as keyof SelectedRates ] = rate;
					return acc;
				},
				{} as SelectedRates
			),
		} )
	)
	.on(
		LABEL_PURCHASE_SUCCESS,
		(
			state,
			{
				payload: {
					label,
					selectedRates,
					selectedHazmat,
					selectedDestinations,
					selectedOrigins,
				},
			}: LabelPurchaseSuccessAction
		) => {
			// Note: Package dimensions sync to global config is now handled
			// in the action (see label/actions.ts) to keep reducer pure
			return {
				...state,
				labels: {
					...state.labels,
					...label,
				},
				selectedRates: {
					...state.selectedRates,
					...selectedRates,
				},
				selectedHazmatConfig: {
					...state.selectedHazmatConfig,
					...selectedHazmat,
				},
				selectedDestinations: {
					...state.selectedDestinations,
					...selectedDestinations,
				},
				selectedOrigins: {
					...state.selectedOrigins,
					...selectedOrigins,
				},
			};
		}
	)
	.on(
		LABEL_STAGE_NEW_SHIPMENT_IDS,
		(
			state,
			{ payload: shipmentIdsToUpdate }: StageLabelsNewShipmentIdsAction
		) => ( {
			...state,
			labels: mapKeys(
				mapValues( state.labels, ( label, key ) =>
					key === shipmentIdsToUpdate[ key ]
						? {
								...label,
								id: shipmentIdsToUpdate[ key ],
						  }
						: label
				),
				( _, key ) => shipmentIdsToUpdate[ key ] ?? key
			),
		} )
	)
	.on(
		ORDER_STATUS_UPDATED,
		( state, { payload }: OrderStatusUpdatedAction ) => {
			return {
				...state,
				order: {
					...state.order,
					status: payload.status,
				},
			};
		}
	)
	.on(
		ORDER_STATUS_UPDATE_FAILED,
		( state, { payload }: OrderStatusUpdatedFailedAction ) => {
			return {
				...state,
				order: {
					...state.order,
					status: '',
					error: payload.error,
				},
			};
		}
	)
	.bind< LabelPurchaseActions >();
