/**
 * CheckoutErrorNotice — renders a P2G checkout / validation error as a human-readable
 * Notice with per-issue descriptions and contextual "fix" links.
 *
 * section → where the fix lives:
 *   "origin"         → store collection address  → plugin Settings page
 *   "destination"    → customer shipping address → WooCommerce edit-order page
 *   "parcel"         → item/parcel details       → WooCommerce edit-order page
 *   "billingAddress" → billing address           → WooCommerce edit-order page
 */
import { __ } from '@wordpress/i18n';
import { Notice, Icon } from '@wordpress/components';
import { cautionFilled } from '@wordpress/icons';
import type { CheckoutApiError, CheckoutIssue } from '../../types';
import { getEditOrderUrl, getPluginSettingsPageUrl } from '../utils';

export interface CheckoutErrorNoticeProps {
	error: CheckoutApiError;
	/**
	 * URL for the plugin settings page — used for "origin" issues.
	 * Defaults to getPluginSettingsPageUrl().
	 */
	settingsUrl?: string;
}

// ── Helpers ──────────────────────────────────────────────────────────────────

/** Capitalise the first letter of a field name and split camelCase. */
function humaniseField(field: string): string {
	return field
		.replace(/([a-z])([A-Z])/g, '$1 $2')
		.replace(/^./, (c) => c.toUpperCase());
}

/** Expand compact API field tokens into readable labels. */
function normaliseFieldToken(token: string): string {
	const value = token.toLowerCase();
	const map: Record<string, string> = {
		contactname: 'contact name',
		contentsummary: 'content summary',
		countryofmanufacture: 'country of manufacture',
		exportreason: 'export reason',
		discountcode: 'discount code',
	};
	return map[value] ?? value.replace(/-/g, ' ');
}

type InferredContext = {
	section: string | null;
	field: string | null;
};

/**
 * Infer section/field from documented error type codes.
 * Example: "missing-origin-postcode" -> { section: "origin", field: "Postcode" }
 */
function inferContextFromType(type: string): InferredContext {
	if (!type) return { section: null, field: null };
	const code = type.split('/').pop() ?? '';
	const parts = code.toLowerCase().split('-');
	if (parts.length === 0) return { section: null, field: null };

	const rule = parts[0];
	const knownRules = [
		'invalid',
		'missing',
		'unavailable',
		'ineligible',
		'required',
	];
	if (!knownRules.includes(rule)) return { section: null, field: null };

	const sectionToken = parts[1] ?? null;
	const fieldToken = parts.slice(2).join('-') || null;

	const sectionMap: Record<string, string> = {
		origin: 'origin',
		destination: 'destination',
		billing: 'billingAddress',
		parcel: 'parcel',
		parcelcontent: 'parcel',
		shipment: 'parcel',
		customs: 'customs',
		customer: 'customer',
		service: 'service',
		quote: 'service',
		extra: 'service',
		collection: 'service',
		discountcode: 'service',
	};

	const section = sectionToken ? sectionMap[sectionToken] ?? null : null;
	const field = fieldToken ? humaniseField(normaliseFieldToken(fieldToken)) : null;

	// Special cases where the token itself is the field.
	if (!field && sectionToken === 'service') {
		return { section, field: __('service', 'parcel2go-shipping') };
	}

	return { section, field };
}

/**
 * Infer the field name from the error type code.
 * E.g. "errorcodes/checkout/invalid-requirement" → null (generic)
 *      "errorcodes/validate/invalid-parcelcontent-countryofmanufacture" → "country of manufacture"
 */
function inferFieldFromType(type: string): string | null {
	if (!type) return null;
	// Extract the last part after the final slash
	const parts = type.split('/');
	const code = parts[parts.length - 1];
	// Match "invalid-<context>-<field>" pattern
	const match = code.match(/^invalid-[^-]+-(.+)$/);
	if (!match) return null;
	// "countryofmanufacture" → "country of manufacture"
	return humaniseField(match[1].replace(/([A-Z])/g, ' $1').toLowerCase());
}

/**
 * Infer a field name from human-readable detail text.
 * E.g. "Item '0' had Height Is Required" → "Height"
 *      "... postcode was missing a required value" → "Postcode"
 */
function inferFieldFromDetail(detail: string): string | null {
	if (!detail) return null;

	const hadRequiredMatch = detail.match(
		/\bhad\s+([A-Za-z][A-Za-z\s_-]{1,40}?)\s+Is Required\b/i
	);
	if (hadRequiredMatch?.[1]) {
		return humaniseField(hadRequiredMatch[1].trim().replace(/[_-]+/g, ' '));
	}

	const requiredMatch = detail.match(/\b([A-Za-z][A-Za-z\s_-]{1,40})\s+Is Required\b/i);
	if (requiredMatch?.[1]) {
		return humaniseField(requiredMatch[1].trim().replace(/[_-]+/g, ' '));
	}

	const missingMatch = detail.match(/\bfield\s+([A-Za-z][A-Za-z0-9_-]{1,40})\b/i);
	if (missingMatch?.[1]) {
		return humaniseField(missingMatch[1].trim());
	}

	return null;
}

/**
 * Parse an instance string to extract a human-readable location.
 * E.g. "request[0]:parcel[0]" → "Parcel 1" (1-indexed for users)
 *      "request[0]" → "Request 1"
 */
function parseInstance(instance: string): string | null {
	if (!instance) return null;
	// Prefer the most specific segment (e.g. "parcel[0]" from "request[0]:parcel[0]").
	const lastSegment = instance.split(':').pop() ?? instance;
	const match = lastSegment.match(/(\w+)\[(\d+)\]/);
	if (match) {
		const [, type, index] = match;
		const normalisedType = type.toLowerCase() === 'request' ? 'parcel' : type;
		const humanType =
			normalisedType.charAt(0).toUpperCase() + normalisedType.slice(1);
		return `${humanType} ${parseInt(index, 10) + 1}`; // 1-indexed for users
	}
	return null;
}

/** Map a section name to a short human-readable location label. */
function sectionLabel(section: string): string {
	switch (section) {
		case 'origin':
        case 'billingAddress':
			return __('store address in Settings', 'parcel2go-shipping');
		case 'destination':
			return __('receiver delivery address on the order', 'parcel2go-shipping');
		case 'parcel':
			return __('item details on the order', 'parcel2go-shipping');
		case 'customs':
			return __('customs details on the order', 'parcel2go-shipping');
		case 'customer':
			return __('customer details on the order', 'parcel2go-shipping');
		case 'service':
			return __('service options on the order', 'parcel2go-shipping');
		default:
			return __('related order', 'parcel2go-shipping');
	}
}

interface ResolvedLink {
	href: string;
	label: string;
}

/** Check if a string or number can be parsed as a valid positive order ID. */
function isValidOrderId(ref: string | number | null | undefined): boolean {
	if (!ref) return false;
	const num = typeof ref === 'number' ? ref : parseInt(String(ref), 10);
	return !isNaN(num) && num > 0;
}

function resolveFixLink(
	issue: CheckoutIssue,
	settingsUrl: string,
	effectiveSection: string | null
): ResolvedLink | null {
	const section = effectiveSection ?? '';

	// Explicit section → route based on section
	if (section === 'origin') {
		return {
			href: settingsUrl,
			label: __('Fix in Settings', 'parcel2go-shipping'),
		};
	}

	if (
		['destination', 'parcel', 'billingAddress', 'customs', 'customer', 'service'].includes(section) &&
		issue.itemRef &&
		isValidOrderId(issue.itemRef)
	) {
		const orderId = Number(issue.itemRef);
		return {
			href: getEditOrderUrl(orderId),
			label: __('Fix on order', 'parcel2go-shipping'),
		};
	}

	// No explicit section, but itemRef looks like an order ID → treat as order error
	if (!section && issue.itemRef && isValidOrderId(issue.itemRef)) {
		const orderId = Number(issue.itemRef);
		return {
			href: getEditOrderUrl(orderId),
			label: __('Fix on order', 'parcel2go-shipping'),
		};
	}

	return null;
}

// ── Component ─────────────────────────────────────────────────────────────────

const listStyle: React.CSSProperties = {
	margin: '8px 0 0',
	padding: 0,
	listStyle: 'none',
};

const itemStyle: React.CSSProperties = {
	display: 'flex',
	alignItems: 'flex-start',
	gap: 8,
	marginBottom: 6,
};

const MAX_VISIBLE_GROUPS = 3;

/**
 * Grouping key for deduplication: combines title + field + section
 * to identify identical or similar errors across multiple items/orders.
 */
function getGroupingKey(
	issue: CheckoutIssue,
	fieldLabel: string | null,
	effectiveSection: string | null
): string {
	return [
		issue.title || 'error',
		fieldLabel || 'unknown',
		effectiveSection || 'none',
	].join('::');
}

interface GroupedIssue {
	key: string;
	issue: CheckoutIssue; // representative issue (first in group)
	issues: CheckoutIssue[]; // all issues in this group
	fieldLabel: string | null;
	locationLabel: string | null;
	effectiveSection: string | null;
	fixLink: ResolvedLink | null;
	affectedOrderIds: (string | number)[]; // unique order IDs
}

/**
 * Group issues by their key (title + field + section) to deduplicate
 * and collect all affected order IDs.
 */
function groupIssuesByKey(issues: CheckoutIssue[]): GroupedIssue[] {
	const groupMap = new Map<string, GroupedIssue>();

	issues.forEach((issue) => {
		const inferred = inferContextFromType(issue.type);
		const effectiveSection = issue.section ?? inferred.section;
		const fieldLabel = issue.field
			? humaniseField(issue.field)
			: inferred.field ?? inferFieldFromType(issue.type) ?? inferFieldFromDetail(issue.detail);
		const locationLabel = effectiveSection
			? sectionLabel(effectiveSection)
			: parseInstance(issue.instance);

		const key = getGroupingKey(issue, fieldLabel, effectiveSection);

		if (!groupMap.has(key)) {
			groupMap.set(key, {
				key,
				issue,
				issues: [],
				fieldLabel,
				locationLabel,
				effectiveSection,
				fixLink: null, // Will be resolved below
				affectedOrderIds: [],
			});
		}

		const group = groupMap.get(key)!;
		group.issues.push(issue);

		// Collect unique order IDs
		if (issue.itemRef && isValidOrderId(issue.itemRef)) {
			const orderId = String(issue.itemRef);
			if (!group.affectedOrderIds.includes(orderId)) {
				group.affectedOrderIds.push(orderId);
			}
		}
	});

	// Resolve fix links for each group (using the first issue)
	groupMap.forEach((group) => {
		const settingsUrl = getPluginSettingsPageUrl();
		group.fixLink = resolveFixLink(group.issue, settingsUrl, group.effectiveSection);
	});

	return Array.from(groupMap.values());
}

export default function CheckoutErrorNotice({
	error,
	settingsUrl,
}: CheckoutErrorNoticeProps) {
	const resolvedSettingsUrl = settingsUrl ?? getPluginSettingsPageUrl();

	const title = error.title ?? __('Could not process checkout', 'parcel2go-shipping');
	const detail =
		error.detail ??
		__('There was an issue processing your shipment.', 'parcel2go-shipping');

	const issues = error.issues ?? [];
	const grouped = groupIssuesByKey(issues);
	const visibleGroups = grouped.slice(0, MAX_VISIBLE_GROUPS);
	const hiddenGroupCount = Math.max(0, grouped.length - MAX_VISIBLE_GROUPS);
	const totalHiddenIssues = issues.length - visibleGroups.reduce((sum, g) => sum + g.issues.length, 0);

	return (
		<Notice status="error" isDismissible={false}>
			<strong>{title}</strong>
			<p style={{ margin: '4px 0 0', fontSize: 13 }}>{detail}</p>

			{issues.length > 0 && (
				<ul style={listStyle}>
					{visibleGroups.map((group, i) => {
						const duplicateCount = group.issues.length;
						const multipleOrders = group.affectedOrderIds.length > 1;

						return (
							<li key={group.key} style={itemStyle}>
								<Icon
									icon={cautionFilled}
									size={16}
									style={{ flexShrink: 0, marginTop: 2 }}
								/>
								<span style={{ fontSize: 13, lineHeight: 1.5 }}>
									<strong>{group.issue.title}</strong>
									{group.fieldLabel && group.locationLabel && (
										<>
											{' '}
											{__('Fix', 'parcel2go-shipping')}{' '}
											<em>{group.fieldLabel}</em>{' '}
											{__('in', 'parcel2go-shipping')}{' '}
											{group.locationLabel}
											{duplicateCount > 1 && (
												<>
													{' '}
													<em style={{ fontStyle: 'normal', color: '#555' }}>
														({duplicateCount} {__('occurrences', 'parcel2go-shipping')})
													</em>
												</>
											)}
											.
										</>
									)}
									{!group.fieldLabel && !group.locationLabel && group.issue.detail && (
										<>
											{': '}
											{group.issue.detail}
											{duplicateCount > 1 && (
												<>
													{' '}
													<em style={{ fontStyle: 'normal', color: '#555' }}>
														({duplicateCount} {__('occurrences', 'parcel2go-shipping')})
													</em>
												</>
											)}
										</>
									)}
									{multipleOrders && group.affectedOrderIds.length > 0 && (
										<>
											{' '}
											{__('Affects orders:', 'parcel2go-shipping')}{' '}
											{group.affectedOrderIds.map((orderId, idx) => (
												<span key={orderId}>
													{idx > 0 && ', '}
													<a
														href={getEditOrderUrl(Number(orderId))}
														style={{ whiteSpace: 'nowrap' }}
													>
														#{orderId}
													</a>
												</span>
											))}
										</>
									)}
									{group.fixLink && !multipleOrders && (
										<>
											{' '}
											<a
												href={group.fixLink.href}
												style={{ whiteSpace: 'nowrap' }}
											>
												{group.affectedOrderIds.length === 1
													? `${group.fixLink.label} #${group.affectedOrderIds[0]}`
													: group.fixLink.label}
											</a>
										</>
									)}
								</span>
							</li>
						);
					})}
					{hiddenGroupCount > 0 && (
						<li style={{ ...itemStyle, marginBottom: 0 }}>
							<span style={{ fontSize: 13, color: '#50575e' }}>
								{hiddenGroupCount === 1
									? __('1 more issue found.', 'parcel2go-shipping')
									: `${hiddenGroupCount} ${__('more issues found.', 'parcel2go-shipping')}`}{' '}
								{__('Open the related order to review all details.', 'parcel2go-shipping')}
							</span>
						</li>
					)}
				</ul>
			)}
		</Notice>
	);
}
