/**
 * Ship order page: single API call returns order, quotes, and dropshops.
 */
import { __, sprintf } from '@wordpress/i18n';
import { Section, Spinner } from '@woocommerce/components';
import {
	useMemo,
	useEffect,
	useRef,
	lazy,
	Suspense,
	useState,
} from '@wordpress/element';
import { Button, Flex, Notice } from '@wordpress/components';

import { useWindowSize, useOrderShipData, useParcel2goAuth } from '../shared/hooks';
import {
	getEditOrderUrl,
	getSiteUrl,
	getSettingsPageUrl,
	isUK,
	validateAddresses,
	validateInternationalRequirements,
	getPluginSettingsPageUrl,
} from '../shared/utils';
import {
	setTag,
	addBreadcrumb,
	withSpan,
	captureException,
} from '../shared/sentry';
import { LazyBookingPrereqsNotice, OrderCompleteView, OrderProgress } from '../shared/components';
import {
	AddressBlock,
	OrderProductsCard,
	PackagingSection,
	OrderShipHeader,
	OrderStatusCard,
} from '../features/order-ship';
import { OrderCompleteResponse } from '../types/completeOrder';
import apiFetch from '@wordpress/api-fetch';
import {
	markOrderAsBooked,
	markOrderAsComplete,
} from '../features/order-ship/utils/shippingStatus';

const LazyServiceQuotes = lazy(
	() => import('../features/order-ship/service-quotes')
);

interface OrderShipPageProps {
	orderId: number;
	onBack: () => void;
}

const MAX_P2G_STATUS_POLL_TRIES = 30;
const P2G_STATUS_POLL_INTERVAL_MS = 2000;

export default function OrderShipPage({ orderId, onBack }: OrderShipPageProps) {
	const { isLinked } = useParcel2goAuth();
	const [orderReference, setOrderReference] = useState<number | null>(null);
	const [p2gOrderLineIds, setP2gOrderLineIds] = useState<string[]>([]);
	const [attempts, setAttempts] = useState<number>(0);
	const [meta, setMeta] = useState<Record<string, string | boolean | object>>(
		{}
	);
	const hasMarkedBookedRef = useRef(false);
	const {
		shippingOrder,
		settings,
		quotes,
		dropshops,
		isLoading,
		orderError,
		quotesError,
		dropshopsError,
		fetchShipData,
		fetchQuotesWithPayload,
	} = useOrderShipData(orderId);

	const order = shippingOrder != null ? shippingOrder : null;
	const orderLabel = order?.number ?? String(order?.id ?? orderId);
	const isProcessing =
		order?.rawStatus === 'processing' || order?.status === 'processing';
	const isCompleted = order?.rawStatus === 'completed';
	const isPaid =
		order?.p2g?.shipStatus === 'Paid' || meta.order_status === 'Paid';
	const isBooked =
		order?.p2g?.shipStatus === 'Booked' || meta.order_status === 'Booked';
	const inProgress = isPaid || isBooked;
	const isFulfilled =
		order?.p2g?.shipStatus === 'Fulfilled' ||
		meta.order_status === 'Fulfilled';

	useEffect(() => {
		setTag('page', 'shipping_quotes_page');
		setTag('order_id', String(orderId));
		addBreadcrumb(
			'shipping_quotes_page_loaded',
			{
				orderId: String(orderId),
				shop: getSiteUrl(),
				origin: settings?.shipping,
				destination: shippingOrder?.shipping,
				products: shippingOrder?.items,
			},
			'shipping_quotes_page'
		);
	}, [orderId, settings, shippingOrder]);

	useEffect(() => {
		if (
			!shippingOrder?.p2g?.completeHash ||
			!orderId ||
			!inProgress ||
			isFulfilled
		) {
			return;
		}

		const orderIdParam = shippingOrder?.p2g?.orderId;
		const hashParam = shippingOrder?.p2g?.completeHash ?? '';
		const query = new URLSearchParams({
			orderId: String(orderIdParam),
			hash: hashParam,
		}).toString();
		hasMarkedBookedRef.current = isBooked;
		let tries = 0;
		const interval = setInterval(async () => {
			try {
				const response = (await withSpan(
					'Checking P2G order status',
					'http.client',
					async () =>
						apiFetch({
							path: `/parcel2go-shipping/v1/order/p2g-status?${query}`,
							method: 'GET',
						}) as Promise<OrderCompleteResponse>,
					{ orderId: orderIdParam, hash: hashParam }
				)) as OrderCompleteResponse;

				const items = response.result.order?.items ?? [];
				const orderLineIds = items
					.filter((item) => item.reference === String(orderId))
					.map((item) => String(item.id ?? ''))
					.filter(Boolean);

				if (orderLineIds.length > 0) {
					setP2gOrderLineIds(orderLineIds);
				}
				if (!hasMarkedBookedRef.current) {
					const orderItems = items.filter(
						(item) => item.reference === String(orderId)
					);
					const isOrderBooked =
						orderItems.length > 0 &&
						orderItems.every((item) => item.booked);
					if (isOrderBooked) {
						hasMarkedBookedRef.current = true;
						const labelHashFromP2g =
							response.result.order?.labelHash ?? '';
						await markOrderAsBooked(
							[String(orderId)],
							labelHashFromP2g || undefined,
							orderLineIds
						);
						setMeta((m) => ({
							...m,
							order_status: 'Booked',
							...(orderLineIds.length > 0
								? { p2gOrderLineIds: orderLineIds }
								: {}),
							...(labelHashFromP2g
								? { labelHashSavedThisSession: true }
								: {}),
						}));
					}
				}

				const parcelNumbers = items
					.flatMap((item) =>
						(item.parcels ?? []).flatMap((parcel) =>
							(parcel.references ?? [])
								.filter(
									(reference) =>
										reference.type === 'ParcelNumber'
								)
								.map((reference) => reference.reference)
						)
					)
					.filter(Boolean);

				if (parcelNumbers.length > 0) {
					const result = await markOrderAsComplete(
						[String(orderId)],
						parcelNumbers,
						orderLineIds
					);
					if (result.success) {
						setMeta((m) => ({
							...m,
							order_status: 'Fulfilled',
							...(orderLineIds.length > 0
								? { p2gOrderLineIds: orderLineIds }
								: {}),
						}));
						clearInterval(interval);
					}
				}

				const firstItem = items[0];
				if (firstItem) setOrderReference(firstItem.id ?? null);
			} catch (error) {
				captureException(
					error instanceof Error ? error : new Error(String(error))
				);
			} finally {
				tries++;
				setAttempts((a) => a + 1);
				if (tries >= MAX_P2G_STATUS_POLL_TRIES) {
					clearInterval(interval);
					captureException(new Error('Order status check timed out'));
				}
			}
		}, P2G_STATUS_POLL_INTERVAL_MS);

		return () => {
			clearInterval(interval);
			setAttempts(0);
		};
	}, [
		orderId,
		shippingOrder?.p2g?.completeHash,
		inProgress,
		isBooked,
		isFulfilled,
	]);

	const { width } = useWindowSize();
	const isWide = width >= 783;

	const store = settings?.shipping ?? null;
	const isStoreUK = useMemo(
		() => isUK(store?.country ?? ''),
		[store?.country]
	);

	const totalValue = useMemo(
		() =>
			shippingOrder?.items?.reduce(
				(acc, item) => acc + Number(item.total ?? 0),
				0
			),
		[shippingOrder?.items]
	);

	const customerSelectionText = useMemo(() => {
		const title = shippingOrder?.shipping_method?.title;
		return title
			? sprintf(
					__(
						'Your customer selected %s shipping at checkout. Choose a service below to fulfil this order.',
						'parcel2go-shipping'
					),
					title
				)
			: undefined;
	}, [shippingOrder?.shipping_method?.title]);

	const protectionLabel = useMemo(() => {
		return totalValue
			? sprintf(
					__('Fully protect this £%s item', 'parcel2go-shipping'),
					totalValue.toFixed(2)
				)
			: undefined;
	}, [totalValue]);

	const addressValidation = useMemo(
		() => validateAddresses(store, shippingOrder),
		[store, shippingOrder]
	);

	const internationalMissing = useMemo(
		() =>
			validateInternationalRequirements(
				shippingOrder ?? null,
				shippingOrder?.shipping?.country ?? '',
				isUK,
				{
					defaultTariffCode: settings?.overrides?.defaultTariffCode,
					defaultCountryOfManufacture:
						settings?.overrides?.defaultCountryOfManufacture,
				}
			),
		[settings?.overrides, shippingOrder]
	);

	const hasInternationalIssues =
		internationalMissing.settings.length > 0 ||
		internationalMissing.products.length > 0;
	const showPrereqsNotice =
		!addressValidation.valid || hasInternationalIssues;

	/* Loading state */
	if (isLoading) {
		return (
			<Section>
				<Flex
					justify="center"
					align="center"
					style={{ minHeight: 120, padding: 24 }}
				>
					<Spinner />
				</Flex>
			</Section>
		);
	}

	/* Error / missing data */
	if (orderError || !shippingOrder || !settings) {
		return (
			<Section>
				<div style={{ marginTop: 16 }}>
					<Button variant="secondary" onClick={onBack}>
						{__('Back to orders', 'parcel2go-shipping')}
					</Button>
				</div>
				<Flex
					justify="center"
					align="center"
					style={{ minHeight: 120, padding: 24 }}
				>
					<Notice status="error">
						{orderError ??
							__('Order not found.', 'parcel2go-shipping')}
					</Notice>
				</Flex>
			</Section>
		);
	}

	/* Completed order */
	if (isCompleted || isFulfilled) {
		return (
			<Section>
				<OrderShipHeader
					title={__('Order', 'parcel2go-shipping')}
					orderLabel={orderLabel}
					onBack={onBack}
				/>
				<OrderCompleteView
					order={shippingOrder}
					orderLabel={orderLabel}
					onBack={onBack}
					isFulfilled={isFulfilled}
					p2gOrderLineIds={
						(Array.isArray(meta.p2gOrderLineIds)
							? (meta.p2gOrderLineIds as string[])
							: undefined) ?? p2gOrderLineIds
					}
					labelHashSavedThisSession={
						meta.labelHashSavedThisSession === true
					}
				/>
			</Section>
		);
	}

	/* Non-processing order */
	if (!isProcessing && !inProgress) {
		return (
			<Section>
				<OrderShipHeader
					title={__('Order', 'parcel2go-shipping')}
					orderLabel={orderLabel}
					onBack={onBack}
				/>
				<OrderStatusCard
					icon="&#9888;"
					heading={__(
						'Order is not ready to ship',
						'parcel2go-shipping'
					)}
					message={sprintf(
						__(
							'This order is currently "%s". Only orders with "Processing" status can be shipped via Parcel2Go. Please update the order status in WooCommerce if you need to ship it.',
							'parcel2go-shipping'
						),
						order.status
					)}
					orderId={order.id}
					editLabel={__('Edit order', 'parcel2go-shipping')}
					onBack={onBack}
				/>
			</Section>
		);
	}

	return (
		<Section>
			<OrderShipHeader
				title={__('Ship order', 'parcel2go-shipping')}
				orderLabel={orderLabel}
				onBack={onBack}
			/>
			{showPrereqsNotice && (
				<div style={{ marginBottom: 16 }}>
					<Suspense fallback={null}>
						<LazyBookingPrereqsNotice
							storeMissing={addressValidation.storeMissing}
							customerMissing={addressValidation.customerMissing}
							internationalMissing={internationalMissing}
							orderHasWeight={
								order.items?.every(
									(item) =>
										item.dimensions?.weight &&
										Number(item.dimensions.weight) > 0
								) ?? false
							}
							orderId={order.id}
							settingsUrl={isLinked ? getPluginSettingsPageUrl() : getSettingsPageUrl()}
							getEditOrderUrl={getEditOrderUrl}
						/>
					</Suspense>
				</div>
			)}
			<Flex
				direction={isWide ? 'row' : 'column'}
				gap={4}
				style={{ alignItems: 'stretch' }}
			>
				{store && (
					<AddressBlock
						title={__('Store Address', 'parcel2go-shipping')}
						addressData={store}
						tooltip={__(
							'You can edit this address in Settings.',
							'parcel2go-shipping'
						)}
						editUrl={isLinked ? getPluginSettingsPageUrl() : getSettingsPageUrl()}
						type="storeAddress"
					/>
				)}
				<AddressBlock
					title={__('Shipping Address', 'parcel2go-shipping')}
					addressData={order}
					tooltip={__(
						'You can edit this address in your WooCommerce order settings.',
						'parcel2go-shipping'
					)}
					editUrl={getEditOrderUrl(order.id)}
					type="orderAddress"
				/>
			</Flex>
			{inProgress ? (
				<OrderProgress
					paid={isPaid}
					booked={isBooked}
					fulfilled={isFulfilled}
					attempts={attempts}
					orderReference={orderReference}
					orderLineCount={1}
					bookedOrderLineCount={isFulfilled ? 1 : 0}
				/>
			) : isStoreUK ? (
				<Flex direction="column" gap={6} style={{ marginTop: 24 }}>
					<OrderProductsCard order={order} />
					<PackagingSection
						order={order}
						store={store}
						onFetchQuotes={fetchQuotesWithPayload}
					/>
					<Suspense
						fallback={
							<Flex justify="center" style={{ padding: 24 }}>
								<Spinner />
							</Flex>
						}
					>
						<LazyServiceQuotes
							quotes={quotes}
							dropshops={dropshops}
							order={shippingOrder}
							settings={settings}
							isLoading={false}
							quotesError={quotesError}
							dropshopsError={dropshopsError}
							customerSelectionText={customerSelectionText}
							protectionLabel={protectionLabel}
							totalValue={Number(totalValue)}
							onBookingComplete={fetchShipData}
						/>
					</Suspense>
				</Flex>
			) : (
				<Flex
					justify="center"
					align="center"
					style={{ minHeight: 120, padding: 24 }}
				>
					<Notice status="error" isDismissible={false}>
						{__(
							'Parcel2Go Shipping only supports shops and orders in the United Kingdom.',
							'parcel2go-shipping'
						)}
					</Notice>
				</Flex>
			)}
		</Section>
	);
}
