import {
	type AdminRecord,
	field,
	numberValue,
	statusLabel,
	textValue,
} from './admin-data';
import { userFacingError } from './privacy';

export type HistoryTab =
	| 'all'
	| 'completed'
	| 'attention'
	| 'restored'
	| 'notifications';

export interface HistoryEntry {
	id: string;
	source: 'run' | 'notification';
	tabs: HistoryTab[];
	timeValue: string;
	timeLabel: string;
	event: string;
	changeName: string;
	result: string;
	status: string;
	tone: string;
	nextAction: string;
	nextActionBody: string;
	actionLabel: string;
	launchUuid: string;
	restoreAvailable: boolean;
	advanced: { label: string; value: string }[];
	supportRun?: AdminRecord;
}

export const historyTabs: { id: HistoryTab; label: string }[] = [
	{ id: 'all', label: 'All activity' },
	{ id: 'completed', label: 'Completed' },
	{ id: 'attention', label: 'Needs review' },
	{ id: 'restored', label: 'Restored' },
	{ id: 'notifications', label: 'Notifications' },
];

export const buildHistoryEntries = (
	runs: AdminRecord[],
	notifications: AdminRecord[],
	timezone: string
): HistoryEntry[] =>
	[
		...runs.map( ( run, index ) =>
			runHistoryEntry( run, index, timezone )
		),
		...notifications.map( ( notification, index ) =>
			notificationHistoryEntry( notification, index, timezone )
		),
	].sort( compareHistoryEntries );

export const historyEntriesForTab = (
	entries: HistoryEntry[],
	tab: HistoryTab
): HistoryEntry[] =>
	'all' === tab
		? entries
		: entries.filter( ( entry ) => entry.tabs.includes( tab ) );

export const historyEmptyCopy = (
	tab: HistoryTab
): { title: string; body: string } => {
	switch ( tab ) {
		case 'completed':
			return {
				title: 'No completed changes yet.',
				body: 'Completed scheduled changes will appear here after they update the site.',
			};
		case 'attention':
			return {
				title: 'Nothing needs review.',
				body: 'Failures, partial changes, restore issues, and notification problems will appear here.',
			};
		case 'restored':
			return {
				title: 'No restored changes yet.',
				body: 'Restores will appear here after previous values are brought back.',
			};
		case 'notifications':
			return {
				title: 'No notification history yet.',
				body: "Notification results will appear here after JJ's Post and Product Scheduler sends them.",
			};
		default:
			return {
				title: 'No activity yet.',
				body: 'Scheduled changes, safety checks, notifications, and restores will appear here after they happen.',
			};
	}
};

export const formatHistoryTime = (
	value: string,
	timezone: string
): string => {
	if ( ! value ) {
		return 'Time not recorded';
	}

	const date = new Date( normalizedHistoryTime( value ) );

	if ( Number.isNaN( date.getTime() ) ) {
		return value;
	}

	try {
		const label = new Intl.DateTimeFormat( 'en-US', {
			dateStyle: 'medium',
			timeStyle: 'short',
			timeZone: timezone || undefined,
		} ).format( date );

		return timezone ? `${ label } (${ timezone })` : label;
	} catch ( error ) {
		void error;

		return new Intl.DateTimeFormat( 'en-US', {
			dateStyle: 'medium',
			timeStyle: 'short',
		} ).format( date );
	}
};

const runHistoryEntry = (
	run: AdminRecord,
	index: number,
	timezone: string
): HistoryEntry => {
	const status = field( run, 'status', 'unknown' ).toLowerCase();
	const id = field( run, 'uuid', `run-${ index }` );
	const result = field( run, 'summary', runResultFromCounts( run ) );
	const needsAttention = runNeedsAttention( status );
	const restored = [ 'rolled_back', 'restored' ].includes( status );
	const completed = [
		'success',
		'successful',
		'successful_with_warnings',
	].includes( status );
	const restoreAvailable =
		true === run.rollbackAvailable ||
		'true' === field( run, 'rollbackAvailable' );

	return {
		id: `run-${ id }`,
		source: 'run',
		tabs: [
			'all',
			...( completed ? [ 'completed' as HistoryTab ] : [] ),
			...( needsAttention ? [ 'attention' as HistoryTab ] : [] ),
			...( restored ? [ 'restored' as HistoryTab ] : [] ),
		],
		timeValue: firstHistoryTime( run, [
			'completedAt',
			'startedAt',
			'scheduledAt',
		] ),
		timeLabel: formatHistoryTime(
			firstHistoryTime( run, [
				'completedAt',
				'startedAt',
				'scheduledAt',
			] ),
			timezone
		),
		event: runEventLabel( status ),
		changeName: field( run, 'launchName', 'Scheduled change' ),
		result,
		status,
		tone: runTone( needsAttention, completed || restored ),
		nextAction: runNextActionTitle( status, restoreAvailable ),
		nextActionBody: runNextActionBody( status, restoreAvailable ),
		actionLabel: needsAttention ? 'Review issue' : 'View summary',
		launchUuid: field( run, 'launchUuid' ),
		restoreAvailable,
		advanced: [
			{
				label: 'Run UUID',
				value: field( run, 'uuid', 'Unavailable' ),
			},
			{
				label: 'Attempt',
				value: textValue( run.attemptNumber, 'Unavailable' ),
			},
			{
				label: 'Started by',
				value: field( run, 'triggerSource', 'Unavailable' ),
			},
			{
				label: 'Result counts',
				value: runResultCounts( run ),
			},
		],
		supportRun: run,
	};
};

const notificationHistoryEntry = (
	notification: AdminRecord,
	index: number,
	timezone: string
): HistoryEntry => {
	const status = field( notification, 'status', 'sent' ).toLowerCase();
	const failed =
		[ 'failed', 'error', 'undelivered' ].includes( status ) ||
		numberValue( notification.failedCount ) > 0 ||
		Boolean( field( notification, 'error' ) );
	const id = field( notification, 'runUuid', `notification-${ index }` );
	const title = failed ? 'Notification failed' : 'Notification sent';
	const result = failed
		? userFacingError(
				notification.error,
				'The notification could not be delivered.'
		  )
		: field( notification, 'summary', 'Notification sent successfully.' );

	return {
		id: `notification-${ id }-${ index }`,
		source: 'notification',
		tabs: [
			'all',
			'notifications',
			...( failed ? [ 'attention' as HistoryTab ] : [] ),
		],
		timeValue: firstHistoryTime( notification, [
			'createdAt',
			'scheduledAt',
		] ),
		timeLabel: formatHistoryTime(
			firstHistoryTime( notification, [ 'createdAt', 'scheduledAt' ] ),
			timezone
		),
		event: title,
		changeName: field( notification, 'launchName', 'Scheduled change' ),
		result,
		status: failed ? 'failed' : 'successful',
		tone: failed ? 'danger' : 'ready',
		nextAction: failed ? 'Check notification settings' : 'No action needed',
		nextActionBody: failed
			? 'Confirm the recipient and notification settings before relying on the next message.'
			: 'The notification was recorded for this scheduled change.',
		actionLabel: failed ? 'Review issue' : 'View notification',
		launchUuid: field( notification, 'launchUuid' ),
		restoreAvailable: false,
		advanced: [
			{
				label: 'Notification type',
				value: field( notification, 'type', 'notification' ),
			},
			{
				label: 'Channel',
				value: field( notification, 'channel', 'Unknown' ),
			},
			{
				label: 'Delivery status',
				value: field( notification, 'status', 'Unknown' ),
			},
			{
				label: 'Run ID',
				value: field( notification, 'runUuid', 'Unavailable' ),
			},
		],
	};
};

const compareHistoryEntries = ( a: HistoryEntry, b: HistoryEntry ): number => {
	const left = Date.parse( normalizedHistoryTime( a.timeValue ) );
	const right = Date.parse( normalizedHistoryTime( b.timeValue ) );

	return (
		( Number.isNaN( right ) ? 0 : right ) -
		( Number.isNaN( left ) ? 0 : left )
	);
};

const normalizedHistoryTime = ( value: string ): string => {
	if ( ! value ) {
		return '';
	}

	if ( /(?:z|[+-]\d{2}:?\d{2})$/i.test( value ) ) {
		return value;
	}

	return `${ value.replace( ' ', 'T' ) }Z`;
};

const firstHistoryTime = ( row: AdminRecord, keys: string[] ): string => {
	for ( const key of keys ) {
		const value = field( row, key );

		if ( value ) {
			return value;
		}
	}

	return '';
};

const runNeedsAttention = ( status: string ): boolean =>
	[
		'failed',
		'partially_failed',
		'rollback_failed',
		'stale',
		'dead_letter',
	].includes( status );

const runEventLabel = ( status: string ): string => {
	switch ( status ) {
		case 'success':
		case 'successful':
			return 'Completed';
		case 'successful_with_warnings':
			return 'Completed with warnings';
		case 'partially_failed':
			return 'Partially completed';
		case 'failed':
			return 'Failed';
		case 'cancelled':
			return 'Cancelled';
		case 'rolled_back':
		case 'restored':
			return 'Restored';
		case 'rollback_failed':
			return 'Restore failed';
		case 'running':
			return 'Applying';
		default:
			return statusLabel( status );
	}
};

const runTone = ( needsAttention: boolean, ready: boolean ): string => {
	if ( needsAttention ) {
		return 'danger';
	}

	return ready ? 'ready' : 'neutral';
};

const runNextActionTitle = (
	status: string,
	restoreAvailable: boolean
): string => {
	if ( runNeedsAttention( status ) ) {
		return 'Review the issue before retrying';
	}

	if ( restoreAvailable ) {
		return 'Previous values are available';
	}

	if (
		[ 'success', 'successful', 'successful_with_warnings' ].includes(
			status
		)
	) {
		return 'Review what changed';
	}

	return 'View the activity summary';
};

const runNextActionBody = (
	status: string,
	restoreAvailable: boolean
): string => {
	if ( 'partially_failed' === status ) {
		return 'Some changes completed and some did not. Review the affected items before trying again.';
	}

	if ( runNeedsAttention( status ) ) {
		return 'The site was not fully updated by this activity. Check the friendly result first, then open support details only if needed.';
	}

	if ( restoreAvailable ) {
		return 'Previous values were saved for this activity. Review the summary before restoring anything.';
	}

	return 'No follow-up is required unless the result is different from what you expected.';
};

const runResultFromCounts = ( run: AdminRecord ): string => {
	const successful = numberValue( run.successfulOps );
	const failed = numberValue( run.failedOps );

	if ( successful > 0 && failed > 0 ) {
		return `${ successful } changes completed. ${ failed } need review.`;
	}

	if ( failed > 0 ) {
		return 'No changes completed. Review the issue before retrying.';
	}

	const skipped = numberValue( run.skippedOps );

	if ( skipped > 0 ) {
		return `${ skipped } changes were skipped.`;
	}

	if ( successful > 0 ) {
		return `${ successful } changes completed.`;
	}

	return 'No details are available yet.';
};

const runResultCounts = ( run: AdminRecord ): string =>
	`${ numberValue( run.successfulOps ) } completed, ${ numberValue(
		run.failedOps
	) } failed, ${ numberValue( run.skippedOps ) } skipped`;
