import type { QueryClient } from '@tanstack/react-query';
import { QUERY_KEYS } from '@/lib/query-keys';
import type { WebSocketResponse } from '@/types/websocket';
import type {
	ActionAPIType,
	OrderAPIResponse,
	ServiceSummary,
} from '@/utils/api/types/config/summary';
import { serviceSummaryReducer } from '@/utils/api/types/requests/service-edit';

export type ApprovalsQueryCacheUpdaterParams = {
	/* The React Query client. */
	queryClient: QueryClient;
	/* The WebSocket message to process. */
	msg: WebSocketResponse;
};

/**
 * approvalsQueryCacheUpdater updates React Query caches for the Approvals page
 * in response to WebSocket messages.
 *
 * @param queryClient - The React Query client.
 * @param msg - The WebSocket message to process.
 */
export const approvalsQueryCacheUpdater = ({
	queryClient,
	msg,
}: ApprovalsQueryCacheUpdaterParams) => {
	if (msg.page !== 'APPROVALS') return;

	switch (msg.type) {
		// ORDER
		case 'SERVICE': {
			switch (msg.sub_type) {
				case 'ORDER': {
					if (msg.order === undefined) return;

					// Set the order cache.
					queryClient.setQueryData(QUERY_KEYS.SERVICE.ORDER(), () => ({
						order: msg.order as string[],
					}));

					// Ensure each item in the order has a cache entry (placeholder if needed).
					for (const id of msg.order) {
						queryClient.setQueryData<ServiceSummary>(
							QUERY_KEYS.SERVICE.SUMMARY_ITEM(id),
							(oldData) => oldData ?? { id, loading: true },
						);
					}
					break;
				}
			}
			break;
		}

		// NEW
		// INIT
		// QUERY
		// UPDATED
		// ACTION
		case 'VERSION': {
			const id = msg.service_data?.id;
			if (!id) break;

			queryClient.setQueryData<ServiceSummary>(
				QUERY_KEYS.SERVICE.SUMMARY_ITEM(id),
				(oldData) => serviceSummaryReducer(msg.service_data, oldData),
			);
			break;
		}

		case 'EDIT': {
			const newServiceData = msg.service_data;

			// old ID when editing existing service.
			const oldID = msg.sub_type;
			const newID = newServiceData?.id ?? oldID;
			if (!newID) break;
			const idHasChanged = oldID && oldID !== newID;

			const oldData = oldID
				? queryClient.getQueryData<ServiceSummary>(
						QUERY_KEYS.SERVICE.SUMMARY_ITEM(oldID),
					)
				: null;
			const orderData = queryClient.getQueryData<OrderAPIResponse>(
				QUERY_KEYS.SERVICE.ORDER(),
			) ?? {
				order: [],
			};
			const newOrder = [...orderData.order];

			if (idHasChanged) {
				// Clear caches if another service already has this ID.
				if (newOrder.includes(newID) || !newOrder.includes(oldID)) {
					// ORDER.
					queryClient
						.invalidateQueries({
							exact: true,
							queryKey: QUERY_KEYS.SERVICE.ORDER(),
						})
						.catch((err) => {
							console.error('Failed to invalidate queries', err);
						});
					if (newOrder.includes(newID)) {
						// ACTIONS/EDIT_ITEM.
						queryClient
							.invalidateQueries({
								queryKey: QUERY_KEYS.SERVICE.BASE(newID),
							})
							.catch((err) => {
								console.error('Failed to invalidate queries', err);
							});
						// SUMMARY_ITEM.
						queryClient
							.invalidateQueries({
								exact: true,
								queryKey: QUERY_KEYS.SERVICE.SUMMARY_ITEM(newID),
							})
							.catch((err) => {
								console.error('Failed to invalidate queries', err);
							});
						return;
					}
				}

				// Replace ID in the ORDER array.
				const idx = newOrder.indexOf(oldID);
				// If idx is -1, we will have invalidated above.
				if (idx !== -1) {
					newOrder[idx] = newID;
					queryClient.setQueryData(QUERY_KEYS.SERVICE.ORDER(), () => ({
						order: newOrder,
					}));
				}

				// Remove service actions/edit cache of old service.
				queryClient.removeQueries({
					queryKey: QUERY_KEYS.SERVICE.BASE(oldID),
				});
				// Remove summary cache of old service.
				queryClient.removeQueries({
					exact: true,
					queryKey: QUERY_KEYS.SERVICE.SUMMARY_ITEM(oldID),
				});
			}

			// Upsert summary for newID.
			if (newServiceData) {
				queryClient.setQueryData<ServiceSummary>(
					QUERY_KEYS.SERVICE.SUMMARY_ITEM(newID),
					(_oldData) => {
						// Don't trust cached _oldData if we think `newID` is new.
						const oldDataMerged: ServiceSummary = idHasChanged
							? {
									...oldData,
									id: newID,
								}
							: {
									...oldData,
									..._oldData,
									id: newID,
									status: {
										...oldData?.status,
										..._oldData?.status,
									},
								};

						return serviceSummaryReducer(newServiceData, oldDataMerged);
					},
				);
			}

			// Ensure ID in order.
			if (!newOrder.includes(newID)) {
				queryClient.setQueryData<OrderAPIResponse>(
					QUERY_KEYS.SERVICE.ORDER(),
					(oldData) => ({
						order: [...(oldData?.order ?? []), newID],
					}),
				);
			}

			// Invalidate related caches.
			queryClient
				.invalidateQueries({
					queryKey: QUERY_KEYS.SERVICE.ACTIONS(newID),
				})
				.catch((err) => {
					console.error('Failed to invalidate queries', err);
				});
			queryClient
				.invalidateQueries({
					queryKey: QUERY_KEYS.SERVICE.EDIT_ITEM(newID),
				})
				.catch((err) => {
					console.error('Failed to invalidate queries', err);
				});
			break;
		}

		case 'DELETE': {
			if (!msg.sub_type) break;
			const id = msg.sub_type;

			// Remove from ORDER.
			queryClient.setQueryData<OrderAPIResponse>(
				QUERY_KEYS.SERVICE.ORDER(),
				(oldData) => ({
					order: (oldData?.order ?? []).filter((x) => x !== id),
				}),
			);

			// Remove caches for this service.
			queryClient.removeQueries({
				queryKey: QUERY_KEYS.SERVICE.BASE(id),
			});
			break;
		}

		// EVENT
		case 'COMMAND':
		case 'WEBHOOK': {
			const queryKey = QUERY_KEYS.SERVICE.ACTIONS(msg.service_data?.id);

			// {
			//  "page":"APPROVALS",
			//  "type":"WEBHOOK",
			//  "sub_type":"EVENT",
			//  "service_data":{"id":"release-argus/Argus"},
			//  "webhook_data":{"awx":{"failed":false,"next_runnable":"2025-12-01T01:02:03.12345678Z"}}
			// }
			queryClient.setQueryData<ActionAPIType>(queryKey, (_prevData) => {
				const prevData = _prevData ?? { command: {}, webhook: {} };

				const mergedCommands = prevData.command;
				if (msg.command_data) {
					for (const [id, data] of Object.entries(msg.command_data)) {
						mergedCommands[id] = {
							...mergedCommands[id],
							failed: data.failed,
							next_runnable: data.next_runnable,
						};
					}
				}

				const mergedWebhooks = prevData.webhook;
				if (msg.webhook_data) {
					for (const [id, data] of Object.entries(msg.webhook_data)) {
						mergedWebhooks[id] = {
							...mergedWebhooks[id],
							failed: data.failed,
							next_runnable: data.next_runnable,
						};
					}
				}

				return {
					...prevData,
					command: mergedCommands,
					webhook: mergedWebhooks,
				};
			});
			break;
		}

		default:
			break;
	}
};

export default approvalsQueryCacheUpdater;
