import { createApiError } from "./error.util.js";
import { Logger } from "./logger.util.js";

/**
 * Standard error codes for consistent handling
 */
export enum ErrorCode {
	NOT_FOUND = "NOT_FOUND",
	INVALID_CURSOR = "INVALID_CURSOR",
	ACCESS_DENIED = "ACCESS_DENIED",
	VALIDATION_ERROR = "VALIDATION_ERROR",
	UNEXPECTED_ERROR = "UNEXPECTED_ERROR",
	NETWORK_ERROR = "NETWORK_ERROR",
	RATE_LIMIT_ERROR = "RATE_LIMIT_ERROR",
	PRIVATE_IP_ERROR = "PRIVATE_IP_ERROR",
	RESERVED_RANGE_ERROR = "RESERVED_RANGE_ERROR",
}

/**
 * Context information for error handling
 */
export interface ErrorContext {
	/**
	 * Source of the error (e.g., file path and function)
	 */
	source?: string;

	/**
	 * Type of entity being processed (e.g., 'IP Address', 'User')
	 */
	entityType?: string;

	/**
	 * Identifier of the entity being processed
	 */
	entityId?: string | Record<string, string>;

	/**
	 * Operation being performed (e.g., 'retrieving', 'searching')
	 */
	operation?: string;

	/**
	 * Additional information for debugging
	 */
	additionalInfo?: Record<string, unknown>;
}

/**
 * Helper function to create a consistent error context object
 * @param entityType Type of entity being processed
 * @param operation Operation being performed
 * @param source Source of the error (typically file path and function)
 * @param entityId Optional identifier of the entity
 * @param additionalInfo Optional additional information for debugging
 * @returns A formatted ErrorContext object
 */
export function buildErrorContext(
	entityType: string,
	operation: string,
	source: string,
	entityId?: string | Record<string, string>,
	additionalInfo?: Record<string, unknown>,
): ErrorContext {
	return {
		entityType,
		operation,
		source,
		...(entityId && { entityId }),
		...(additionalInfo && { additionalInfo }),
	};
}

/**
 * Detect specific error types from raw errors
 * @param error The error to analyze
 * @param context Context information for better error detection
 * @returns Object containing the error code and status code
 */
export function detectErrorType(
	error: unknown,
	context: ErrorContext = {},
): { code: ErrorCode; statusCode: number } {
	const methodLogger = Logger.forContext(
		"utils/error-handler.util.ts",
		"detectErrorType",
	);
	methodLogger.debug("Detecting error type", { error, context });

	const errorMessage = error instanceof Error ? error.message : String(error);
	const statusCode =
		error instanceof Error && "statusCode" in error
			? (error as { statusCode: number }).statusCode
			: undefined;

	// Network error detection
	if (
		errorMessage.includes("network error") ||
		errorMessage.includes("fetch failed") ||
		errorMessage.includes("ECONNREFUSED") ||
		errorMessage.includes("ENOTFOUND") ||
		errorMessage.includes("Failed to fetch") ||
		errorMessage.includes("Network request failed")
	) {
		return { code: ErrorCode.NETWORK_ERROR, statusCode: 500 };
	}

	// Rate limiting detection
	if (
		errorMessage.includes("rate limit") ||
		errorMessage.includes("too many requests") ||
		statusCode === 429
	) {
		return { code: ErrorCode.RATE_LIMIT_ERROR, statusCode: 429 };
	}

	// ip-api.com specific error detection
	if (
		errorMessage.includes("private range") ||
		errorMessage.includes("private IP")
	) {
		return { code: ErrorCode.PRIVATE_IP_ERROR, statusCode: 400 };
	}

	if (errorMessage.includes("reserved range")) {
		return { code: ErrorCode.RESERVED_RANGE_ERROR, statusCode: 400 };
	}

	// Check for ip-api.com status="fail" in originalError
	if (
		error instanceof Error &&
		"originalError" in error &&
		error.originalError &&
		typeof error.originalError === "object"
	) {
		const originalError = error.originalError as Record<string, unknown>;

		if (originalError.status === "fail") {
			const apiMessage = originalError.message
				? String(originalError.message)
				: "";

			if (apiMessage.includes("private")) {
				return { code: ErrorCode.PRIVATE_IP_ERROR, statusCode: 400 };
			}

			if (apiMessage.includes("reserved")) {
				return {
					code: ErrorCode.RESERVED_RANGE_ERROR,
					statusCode: 400,
				};
			}

			return { code: ErrorCode.VALIDATION_ERROR, statusCode: 400 };
		}
	}

	// Not Found detection
	if (
		errorMessage.includes("not found") ||
		errorMessage.includes("does not exist") ||
		statusCode === 404
	) {
		return { code: ErrorCode.NOT_FOUND, statusCode: 404 };
	}

	// Access Denied detection
	if (
		errorMessage.includes("access") ||
		errorMessage.includes("permission") ||
		errorMessage.includes("authorize") ||
		errorMessage.includes("authentication") ||
		statusCode === 401 ||
		statusCode === 403
	) {
		return { code: ErrorCode.ACCESS_DENIED, statusCode: statusCode || 403 };
	}

	// Invalid Cursor detection
	if (
		(errorMessage.includes("cursor") ||
			errorMessage.includes("startAt") ||
			errorMessage.includes("page")) &&
		(errorMessage.includes("invalid") || errorMessage.includes("not valid"))
	) {
		return { code: ErrorCode.INVALID_CURSOR, statusCode: 400 };
	}

	// Validation Error detection
	if (
		errorMessage.includes("validation") ||
		errorMessage.includes("invalid") ||
		errorMessage.includes("required") ||
		statusCode === 400 ||
		statusCode === 422
	) {
		return {
			code: ErrorCode.VALIDATION_ERROR,
			statusCode: statusCode || 400,
		};
	}

	// Default to unexpected error
	return {
		code: ErrorCode.UNEXPECTED_ERROR,
		statusCode: statusCode || 500,
	};
}

/**
 * Create user-friendly error messages based on error type and context
 * @param code The error code
 * @param context Context information for better error messages
 * @param originalMessage The original error message
 * @returns User-friendly error message
 */
export function createUserFriendlyErrorMessage(
	code: ErrorCode,
	context: ErrorContext = {},
	originalMessage?: string,
): string {
	const methodLogger = Logger.forContext(
		"utils/error-handler.util.ts",
		"createUserFriendlyErrorMessage",
	);
	const { entityType, entityId, operation } = context;

	// Format entity ID for display
	let entityIdStr = "";
	if (entityId) {
		if (typeof entityId === "string") {
			entityIdStr = entityId;
		} else {
			// Handle object entityId
			entityIdStr = Object.values(entityId).join("/");
		}
	}

	// Determine entity display name
	const entity = entityType
		? `${entityType}${entityIdStr ? ` ${entityIdStr}` : ""}`
		: "Resource";

	let message = "";

	switch (code) {
		case ErrorCode.NOT_FOUND:
			message = `${entity} not found${entityIdStr ? `: ${entityIdStr}` : ""}. Verify the ID is correct and that you have access to this ${entityType?.toLowerCase() || "resource"}.`;
			break;

		case ErrorCode.ACCESS_DENIED:
			message = `Access denied for ${entity.toLowerCase()}${entityIdStr ? ` ${entityIdStr}` : ""}. Verify your credentials and permissions.`;
			break;

		case ErrorCode.INVALID_CURSOR:
			message =
				"Invalid pagination cursor. Use the exact cursor string returned from previous results.";
			break;

		case ErrorCode.VALIDATION_ERROR:
			message =
				originalMessage ||
				`Invalid data provided for ${operation || "operation"} ${entity.toLowerCase()}.`;
			break;

		case ErrorCode.NETWORK_ERROR:
			message = `Network error while ${operation || "connecting to"} the service. Please check your internet connection and try again.`;
			break;

		case ErrorCode.RATE_LIMIT_ERROR:
			message =
				"Rate limit exceeded. Please wait a moment and try again, or reduce the frequency of requests.";
			break;

		case ErrorCode.PRIVATE_IP_ERROR:
			message =
				"Private IP addresses are not supported. Please provide a public IP address.";
			break;

		case ErrorCode.RESERVED_RANGE_ERROR:
			message =
				"Reserved range IP addresses are not supported. Please provide a public IP address.";
			break;

		default:
			message = `An unexpected error occurred while ${operation || "processing"} ${entity.toLowerCase()}.`;
	}

	// Include original message details if available and appropriate
	if (
		originalMessage &&
		code !== ErrorCode.NOT_FOUND &&
		code !== ErrorCode.ACCESS_DENIED
	) {
		message += ` Error details: ${originalMessage}`;
	}

	methodLogger.debug(`Created user-friendly message: ${message}`, {
		code,
		context,
	});
	return message;
}

/**
 * Handle controller errors consistently
 * @param error The error to handle
 * @param context Context information for better error messages
 * @returns Never returns, always throws an error
 */
export function handleControllerError(
	error: unknown,
	context: ErrorContext = {},
): never {
	const methodLogger = Logger.forContext(
		"utils/error-handler.util.ts",
		"handleControllerError",
	);

	// Extract error details
	const errorMessage = error instanceof Error ? error.message : String(error);
	const statusCode =
		error instanceof Error && "statusCode" in error
			? (error as { statusCode: number }).statusCode
			: undefined;

	// Detect error type using utility
	const { code, statusCode: detectedStatus } = detectErrorType(error, context);

	// Combine detected status with explicit status
	const finalStatusCode = statusCode || detectedStatus;

	// Format entity information for logging
	const { entityType, entityId, operation } = context;
	const entity = entityType || "resource";
	const entityIdStr = entityId
		? typeof entityId === "string"
			? entityId
			: JSON.stringify(entityId)
		: "";
	const actionStr = operation || "processing";

	// Log detailed error information
	methodLogger.error(
		`Error ${actionStr} ${entity}${entityIdStr ? `: ${entityIdStr}` : ""}: ${errorMessage}`,
		error,
	);

	// Create user-friendly error message for the response
	const message =
		code === ErrorCode.VALIDATION_ERROR
			? errorMessage
			: createUserFriendlyErrorMessage(code, context, errorMessage);

	// Throw an appropriate API error with the user-friendly message
	throw createApiError(message, finalStatusCode, error);
}
