import { z } from 'zod';
import { isEmptyArray } from '@/utils';
import {
	isWebHookType,
	type WebHook,
	type WebHookMap,
	type WebHookType,
} from '@/utils/api/types/config/webhook';
import { buildSuperRefine } from '@/utils/api/types/config-edit/shared/builder--super-refine';
import {
	headersSchema,
	headersSchemaWithValidation,
} from '@/utils/api/types/config-edit/shared/header/preprocess.ts';
import { safeParse } from '@/utils/api/types/config-edit/shared/safeparse';
import { superRefineNameUnique } from '@/utils/api/types/config-edit/shared/unique-name--super-refine';
import { applyDefaultsRecursive } from '@/utils/api/types/config-edit/util';
import {
	validateArrayFieldWithSchemas,
	validateListUniqueKeys,
	validateNumberInRange,
	validateNumberString,
	validateRequired,
} from '@/utils/api/types/config-edit/validators';
import {
	type WebHookSchema,
	webhookSchema,
	webhookSchemaDefault,
} from '@/utils/api/types/config-edit/webhook/schemas';

/**
 * Builds a schema for a webhook.
 *
 * @param defaults - Default values for a webhook.
 * @param mains - The 'main' webhook objects that may be referenced.
 */
const buildWebHookSchema = (
	defaults: WebHookSchema,
	mains: Record<string, WebHookSchema>,
) => {
	return buildSuperRefine(webhookSchema, mains, defaults, [
		{ path: ['desired_status_code'], validator: validateNumberString },
		{ path: ['max_tries'], validator: validateNumberString },
		{
			path: ['max_tries'],
			validator: validateNumberInRange({ max: 255, min: 0 }),
		},
		{ path: ['secret'], validator: validateRequired },
		{ path: ['url'], validator: validateRequired },
		{
			kind: 'array',
			path: ['headers'],
			props: [
				{
					defaultSchema: headersSchema,
					schema: headersSchemaWithValidation,
				},
			],
			validator: [validateArrayFieldWithSchemas, validateListUniqueKeys],
		},
	]);
};

/**
 * Builds a schema for an array of webhooks.
 *
 * @param data - The current data from the API.
 * @param defaultItems - The default webhooks to use.
 * @param mains - The 'main' webhook objects that may be referenced.
 * @param defaults - Default values for a webhook.
 * @param hardDefaults - Hard defaults for a webhook.
 */
export const buildWebHooksSchemaWithFallbacks = (
	data?: WebHook[],
	defaultItems?: string[],
	mains?: WebHookMap,
	defaults?: WebHook,
	hardDefaults?: WebHook,
) => {
	const path = 'webhook';
	const combinedDefaults = applyDefaultsRecursive<WebHook>(
		defaults ?? null,
		hardDefaults,
	);
	const defaultType = isWebHookType(combinedDefaults.type)
		? combinedDefaults.type
		: undefined;

	const dataDefaulted = (data ?? []).map((item) => {
		const main = mains?.[item.name];
		const nameLower = item.name.toLowerCase();
		const itemType =
			main?.type ?? (isWebHookType(nameLower) ? nameLower : defaultType);
		const itemHeaders = (item?.headers ?? []).map((h, i) => ({
			...h,
			old_index: i,
		}));

		return safeParse({
			data: {
				...item,
				headers: itemHeaders,
				old_index: item.name,
				type: itemType,
			},
			fallback: {
				desired_status_code: '',
				max_tries: '',
				type: defaultType as WebHookType,
			},
			path: `${path} (defaults-${item.name})`,
			schema: webhookSchema,
		});
	});
	const schemaDataTypeDefaults = safeParse({
		data: combinedDefaults,
		fallback: {
			desired_status_code: '',
			max_tries: '',
			type: defaultType as WebHookType,
		},
		path: `${path} (defaults)`,
		schema: webhookSchemaDefault,
	});

	// Default schema data.
	const schemaDataDefaults: WebHookSchema[] = (defaultItems ?? []).map(
		(name) => {
			const main = mains?.[name];
			const nameLower = name.toLowerCase();
			const itemType =
				main?.type ?? (isWebHookType(nameLower) ? nameLower : defaultType);
			// headers.
			const headers = isEmptyArray(main?.headers)
				? schemaDataTypeDefaults.headers
				: main?.headers;
			const headersHollow = headers?.map(() => ({
				key: '',
				value: '',
			}));

			const data = {
				headers: headersHollow,
				name: name,
				old_index: null,
				type: itemType,
			};
			return safeParse({
				data: data,
				fallback: {
					desired_status_code: '',
					max_tries: '',
					type: itemType as WebHookType,
				},
				path: `${path} (defaults)`,
				schema: webhookSchema,
			});
		},
	);

	// Defaults for each type.
	const schemaDataTypeDefaultsHollow = {
		headers: schemaDataTypeDefaults.headers.map(() => ({
			key: '',
			value: '',
		})),
	};

	// Defaults for each main.
	const schemaDataMains = Object.entries(mains ?? {}).reduce<
		Record<string, WebHookSchema>
	>((acc, [name, main]) => {
		acc[name] = safeParse({
			data: applyDefaultsRecursive({ ...main, name: name }, combinedDefaults),
			fallback: {
				desired_status_code: '',
				max_tries: '',
				name: name,
				type: (main.type ?? defaultType) as WebHookType,
			},
			path: `${path} (mains-${name}`,
			schema: webhookSchema,
		});
		return acc;
	}, {});

	// Schemas.
	const schema = buildWebHookSchema(schemaDataTypeDefaults, schemaDataMains);
	const schemaFinal = z
		.array(schema)
		.default(schemaDataDefaults)
		.superRefine(superRefineNameUnique);

	// Initial schema data.
	let schemaData: WebHookSchema[];
	if (data) {
		schemaData = safeParse({
			data: dataDefaulted,
			fallback: schemaDataDefaults,
			path: path,
			schema: schemaFinal,
		});
	} else {
		schemaData = schemaDataDefaults;
	}

	return {
		schema: schemaFinal,
		schemaData: schemaData,
		schemaDataDefaults: schemaDataDefaults,
		schemaDataMains: schemaDataMains,
		schemaDataTypeDefaults: schemaDataTypeDefaults,
		schemaDataTypeDefaultsHollow: schemaDataTypeDefaultsHollow,
	};
};
