import { apiFetch } from '@wordpress/data-controls';
import { __ } from '@wordpress/i18n';
import {
	ADD_ORIGIN_ADDRESS,
	ADD_ORIGIN_ADDRESS_FAILED,
	ADDRESS_NORMALIZATION,
	ADDRESS_NORMALIZATION_FAILED,
	DELETE_ORIGIN_ADDRESS,
	FETCH_ORIGIN_ADDRESSES,
	RESET_ADDRESS_NORMALIZATION,
	SET_DESTINATION_RESIDENTIAL,
	SET_STORE_ADDRESS_SYNC_STATUS,
	STATE_RESET,
	UPDATE_SHIPMENT_ADDRESS,
	UPDATE_SHIPMENT_ADDRESS_FAILED,
	VERIFY_ORDER_SHIPPING_ADDRESS,
	VERIFY_ORDER_SHIPPING_ADDRESS_FAILED,
	VERIFY_ORDER_SHIPPING_ADDRESS_START,
} from './action-types';
import {
	getAddressNormalizationPath,
	getDeleteOriginAddressPath,
	getOriginAddressesPath,
	getStoreAddressSyncPath,
	getUpdateDestinationPath,
	getUpdateOriginPath,
	getVerifyOrderShippingAddressPath,
} from 'data/routes';
import {
	camelCaseKeys,
	composeAddress,
	composeName,
	normalizeStoreAddressDraft,
	snakeCaseKeys,
} from 'utils';
import { ADDRESS_TYPES } from 'data/constants';
import {
	AddressTypes,
	Destination,
	LocationResponse,
	OriginAddress,
} from 'types';
import {
	AddOriginAddressAction,
	AddOriginAddressFailedAction,
	DeleteOriginAddressAction,
	NormalizationAddressAction,
	NormalizationAddressFailedAction,
	SetDestinationResidentialAction,
	SetStoreAddressSyncStatusAction,
	ShippingAddressVerifyAction,
	ShippingAddressVerifyFailedAction,
	ShippingAddressVerifyStartAction,
	StateResetAction,
	UpdateShipmentAddressAction,
	UpdateShipmentAddressFailedAction,
} from './types.d';

export function* verifyOrderShippingAddress( {
	orderId,
}: {
	orderId: string;
} ): Generator<
	ReturnType< typeof apiFetch > | ShippingAddressVerifyStartAction,
	ShippingAddressVerifyAction | ShippingAddressVerifyFailedAction,
	{
		success: boolean;
		normalizedAddress: LocationResponse;
		isTrivialNormalization: boolean;
		isVerified: boolean;
	}
> {
	yield {
		type: VERIFY_ORDER_SHIPPING_ADDRESS_START,
		payload: {
			addressType: 'destination',
		},
	};
	try {
		const result = yield apiFetch( {
			path: getVerifyOrderShippingAddressPath( orderId ),
		} );

		return {
			type: VERIFY_ORDER_SHIPPING_ADDRESS,
			payload: {
				...result,
				addressType: 'destination',
			},
		};
	} catch {
		return {
			type: VERIFY_ORDER_SHIPPING_ADDRESS_FAILED,
			payload: {
				addressType: 'destination',
			},
		};
	}
}

export function* normalizeAddress(
	{
		address,
	}: {
		address: Destination | OriginAddress;
	},
	addressType: 'origin' | 'destination'
): Generator<
	ReturnType< typeof apiFetch > | ShippingAddressVerifyStartAction,
	NormalizationAddressAction | NormalizationAddressFailedAction,
	{
		success: boolean;
		errors?: Record< string, string >;
		isTrivialNormalization: boolean;
		address: LocationResponse;
		normalizedAddress: LocationResponse;
	}
> {
	yield {
		type: VERIFY_ORDER_SHIPPING_ADDRESS_START,
		payload: {
			addressType,
		},
	};
	try {
		const {
			address: submittedAddress,
			normalizedAddress,
			success,
			errors,
			...rest
		} = yield apiFetch( {
			path: getAddressNormalizationPath(),
			method: 'POST',
			data: {
				address: snakeCaseKeys( address ),
				// Forward the type so the server-bound request body wraps
				// the address as `origin` (vs. the historical `destination`
				// fallback) when the caller is normalizing a store origin.
				// See WOOSHIP-2230.
				addressType,
			},
		} );

		if ( success ) {
			return {
				type: ADDRESS_NORMALIZATION,
				payload: {
					...rest,
					normalizedAddress: camelCaseKeys( normalizedAddress ),
					address: camelCaseKeys( submittedAddress ),
					addressType,
				},
			};
		}
		return {
			type: ADDRESS_NORMALIZATION_FAILED,
			payload: {
				errors,
				address,
				addressType,
			},
		};
	} catch ( error ) {
		return {
			type: ADDRESS_NORMALIZATION_FAILED,
			payload: {
				errors:
					( error as Record< string, Record< string, string > > )
						?.errors ?? {},
				message: ( error as Record< string, string > )?.message ?? '',
				address,
				addressType,
			},
		};
	}
}

export function resetAddressNormalizationResponse( addressType: AddressTypes ) {
	return {
		type: RESET_ADDRESS_NORMALIZATION,
		payload: { addressType } as {
			addressType: AddressTypes;
		},
	};
}

export function* updateShipmentAddress(
	{
		orderId,
		address,
		isVerified,
		clearStoreAddressDrift = false,
	}: {
		orderId: string;
		address: Destination | OriginAddress;
		isVerified: boolean;
		clearStoreAddressDrift?: boolean;
	},
	type: AddressTypes
): Generator<
	ReturnType< typeof apiFetch > | ShippingAddressVerifyStartAction,
	UpdateShipmentAddressAction | UpdateShipmentAddressFailedAction,
	{
		success: boolean;
		address: LocationResponse;
		isVerified: boolean;
		message?: string;
	}
> {
	yield {
		type: VERIFY_ORDER_SHIPPING_ADDRESS_START,
		payload: {
			addressType: type,
		},
	};

	try {
		const snakeCaseAddress = snakeCaseKeys<
			Destination | OriginAddress,
			LocationResponse
		>( address );
		const requestData = {
			address: {
				...snakeCaseAddress,
				address: composeAddress( snakeCaseAddress ),
				name: composeName( snakeCaseAddress ),
			},
			isVerified,
			clearStoreAddressDrift,
		};

		if ( type === ADDRESS_TYPES.ORIGIN ) {
			// we only attempt to save the origin address if it's verified by the merchant
			requestData.address.is_verified = true;
		}

		const {
			success,
			address: resultAddress,
			...rest
		} = yield apiFetch( {
			path:
				type === ADDRESS_TYPES.ORIGIN
					? getUpdateOriginPath()
					: getUpdateDestinationPath( orderId ),
			method: 'POST',
			data: requestData,
		} );
		if ( success ) {
			return {
				type: UPDATE_SHIPMENT_ADDRESS,
				payload: {
					...rest,
					address: {
						...camelCaseKeys( resultAddress ),
						name: composeName( resultAddress ),
						address: composeAddress( resultAddress ),
					},
					addressType: type,
				},
			};
		}
		return {
			type: UPDATE_SHIPMENT_ADDRESS_FAILED,
			payload: {
				message: __(
					'Failed to update shipment address',
					'woocommerce-shipping'
				),
				addressType: type,
			},
		};
	} catch ( error ) {
		return {
			type: UPDATE_SHIPMENT_ADDRESS_FAILED,
			payload: {
				message: ( error as Record< string, string > )?.message ?? '',
				addressType: type,
			},
		};
	}
}

export function* deleteOriginAddress( addressId: string ): Generator<
	ReturnType< typeof apiFetch >,
	DeleteOriginAddressAction,
	{
		success: boolean;
		addresses: LocationResponse[];
		deleted_id: string;
	}
> {
	const { deleted_id: deletedId } = yield apiFetch( {
		path: getDeleteOriginAddressPath( addressId ),
		method: 'DELETE',
	} );

	return {
		type: DELETE_ORIGIN_ADDRESS,
		payload: {
			deletedId,
		},
	};
}

export function* addOriginAddress( address: OriginAddress ): Generator<
	ReturnType< typeof apiFetch >,
	AddOriginAddressAction | AddOriginAddressFailedAction,
	{
		success: boolean;
		address: LocationResponse;
		isVerified: boolean;
		message?: string;
	}
> {
	// we only attempt to save the origin address if it's verified by the merchant, but this parameter is needed by the API
	const isVerified = true;
	try {
		const snakeCaseAddress = snakeCaseKeys<
			Destination | OriginAddress,
			LocationResponse
		>( address );
		const requestData = {
			address: {
				...snakeCaseAddress,
				address: composeAddress( snakeCaseAddress ),
				name: composeName( snakeCaseAddress ),
			},
			isVerified,
		};

		requestData.address.is_verified = isVerified;

		const {
			success,
			address: resultAddress,
			...rest
		} = yield apiFetch( {
			path: getUpdateOriginPath(),
			method: 'POST',
			data: requestData,
		} );
		if ( success ) {
			return {
				type: ADD_ORIGIN_ADDRESS,
				payload: {
					...rest,
					address: {
						...camelCaseKeys( resultAddress ),
						name: composeName( resultAddress ),
						address: composeAddress( resultAddress ),
					},
				},
			};
		}
		return {
			type: ADD_ORIGIN_ADDRESS_FAILED,
			payload: {
				error: {
					general: __(
						'Failed to add origin address',
						'woocommerce-shipping'
					),
				},
			},
		};
	} catch ( error ) {
		return {
			type: ADD_ORIGIN_ADDRESS_FAILED,
			payload: {
				error: {
					general:
						( error as Record< string, string > )?.message ?? '',
				},
			},
		};
	}
}

export function* fetchOriginAddresses() {
	const addresses: LocationResponse[] = yield apiFetch( {
		path: getOriginAddressesPath(),
	} );

	let isMainOriginInSyncWithStore: boolean | undefined;
	let formattedStoreAddress: string | undefined;
	let storeAddressDraft: OriginAddress | null | undefined;

	try {
		const syncMeta: {
			is_main_origin_in_sync_with_store: boolean;
			formatted_store_address: string;
			store_address_draft?: LocationResponse | null;
		} = yield apiFetch( {
			path: getStoreAddressSyncPath(),
		} );
		isMainOriginInSyncWithStore =
			syncMeta.is_main_origin_in_sync_with_store;
		formattedStoreAddress = syncMeta.formatted_store_address ?? '';
		storeAddressDraft = syncMeta.store_address_draft
			? normalizeStoreAddressDraft( syncMeta.store_address_draft )
			: null;
	} catch {
		// Older servers without the v2 meta route: keep existing Redux sync fields.
	}

	return {
		type: FETCH_ORIGIN_ADDRESSES,
		payload: {
			addresses: addresses.map( ( addr ) => ( {
				...camelCaseKeys( addr ),
				address: composeAddress( addr ),
				name: composeName( addr ),
			} ) ),
			isMainOriginInSyncWithStore,
			formattedStoreAddress,
			storeAddressDraft,
		},
	};
}

/**
 * Refreshes is_main_origin_in_sync_with_store and formatted_store_address from the REST API.
 */
export function* refreshStoreAddressSyncFromApi() {
	const syncMeta: {
		is_main_origin_in_sync_with_store: boolean;
		formatted_store_address: string;
		store_address_draft?: LocationResponse | null;
	} = yield apiFetch( {
		path: getStoreAddressSyncPath(),
	} );

	return {
		type: SET_STORE_ADDRESS_SYNC_STATUS,
		payload: {
			isMainOriginInSyncWithStore:
				syncMeta.is_main_origin_in_sync_with_store,
			formattedStoreAddress: syncMeta.formatted_store_address ?? '',
			storeAddressDraft: syncMeta.store_address_draft
				? normalizeStoreAddressDraft( syncMeta.store_address_draft )
				: null,
		},
	};
}

export function stateReset(): StateResetAction {
	return {
		type: STATE_RESET,
	};
}

export function setStoreAddressSyncStatus(
	isMainOriginInSyncWithStore: boolean,
	formattedStoreAddress: string,
	storeAddressDraft?: OriginAddress | null
): SetStoreAddressSyncStatusAction {
	return {
		type: SET_STORE_ADDRESS_SYNC_STATUS,
		payload: {
			isMainOriginInSyncWithStore,
			formattedStoreAddress,
			storeAddressDraft,
		},
	};
}

export function setDestinationResidential(
	residential: boolean | undefined
): SetDestinationResidentialAction {
	return {
		type: SET_DESTINATION_RESIDENTIAL,
		payload: { residential },
	};
}