import { ErrorCode } from '../../constants/errors';
import { GenericError } from './generic';

/**
 * Authentication Error
 * Used for authentication-related errors (login, token, session)
 */
export class AuthenticationError extends GenericError {
	constructor(errorCode: ErrorCode = 'UNAUTHORIZED', additionalData?: unknown) {
		super(errorCode, additionalData);
	}
}

/**
 * Authorization Error
 * Used for authorization/permission-related errors
 */
export class AuthorizationError extends GenericError {
	constructor(errorCode: ErrorCode = 'FORBIDDEN', additionalData?: unknown) {
		super(errorCode, additionalData);
	}
}

/**
 * Validation Error
 * Used for input validation errors
 * Supports field-level validation errors with helper methods
 */
export class ValidationError extends GenericError {
	errors: Array<{ field: string; code: string; message: { vi: string; en: string } }>;

	constructor(fieldErrors?: Array<{ field: string; code: string; message: { vi: string; en: string } }>) {
		super('VALIDATION_ERROR', fieldErrors ? { errors: fieldErrors } : undefined);
		this.errors = fieldErrors || [];
	}

	/**
	 * Add an error to the validation error list
	 * @param field - Field name
	 * @param code - Error code from catalog
	 * @param message - Optional custom message (defaults to catalog message)
	 */
	addError(field: string, code: string, message?: { vi: string; en: string }): void {
		this.errors.push({
			field,
			code,
			message: message || { vi: 'Validation error', en: 'Validation error' },
		});
		// Update additionalData to reflect the new errors
		this.additionalData = { errors: this.errors };
	}

	/**
	 * Check if there's an error for a specific field
	 * @param field - Field name to check
	 * @returns True if field has an error
	 */
	hasFieldError(field: string): boolean {
		return this.errors.some((e) => e.field === field);
	}

	/**
	 * Get all errors for a specific field
	 * @param field - Field name
	 * @returns Array of errors for the field
	 */
	getFieldErrors(field: string): Array<{ code: string; message: { vi: string; en: string } }> {
		return this.errors.filter((e) => e.field === field).map((e) => ({ code: e.code, message: e.message }));
	}

	/**
	 * Check if there are any validation errors
	 * @returns True if there are errors
	 */
	hasErrors(): boolean {
		return this.errors.length > 0;
	}
}

/**
 * Not Found Error
 * Used when a resource is not found
 */
export class NotFoundError extends GenericError {
	constructor(resource: string = 'Resource', additionalData?: unknown) {
		super('NOT_FOUND', additionalData, {
			vi: `Không tìm thấy ${resource}`,
			en: `${resource} not found`,
		});
	}
}

/**
 * Conflict Error
 * Used for conflict errors (duplicate resources, etc.)
 */
export class ConflictError extends GenericError {
	constructor(errorCode: ErrorCode = 'CONFLICT', additionalData?: unknown) {
		super(errorCode, additionalData);
	}
}

/**
 * Database Error
 * Used for database-related errors
 */
export class DatabaseError extends GenericError {
	constructor(errorCode: ErrorCode = 'DATABASE_ERROR', additionalData?: unknown) {
		super(errorCode, additionalData);
	}
}

/**
 * External Service Error
 * Used for external service errors (API calls, third-party services)
 */
export class ExternalServiceError extends GenericError {
	constructor(errorCode: ErrorCode = 'EXTERNAL_SERVICE_ERROR', additionalData?: unknown) {
		super(errorCode, additionalData);
	}
}

/**
 * Circuit Breaker Open Error
 * Used when circuit breaker is open (service unavailable)
 */
export class CircuitBreakerOpenError extends Error {
	constructor() {
		super('Circuit breaker is open');
		this.name = 'CircuitBreakerOpenError';
	}
}

/**
 * Convenience factory functions for common error scenarios
 * Provides IDE autocomplete and documentation for each error type
 */
export const Errors = {
	// Authentication
	/**
	 * Invalid credentials error (wrong username/password)
	 * @param additionalData - Optional additional data to include with the error
	 */
	invalidCredentials: (additionalData?: unknown) => new AuthenticationError('INVALID_CREDENTIALS', additionalData),
	/**
	 * Token expired error (JWT token has expired)
	 * @param additionalData - Optional additional data to include with the error
	 */
	tokenExpired: (additionalData?: unknown) => new AuthenticationError('TOKEN_EXPIRED', additionalData),
	/**
	 * Invalid token error (malformed or invalid JWT token)
	 * @param additionalData - Optional additional data to include with the error
	 */
	tokenInvalid: (additionalData?: unknown) => new AuthenticationError('TOKEN_INVALID', additionalData),
	/**
	 * Account locked error (account has been locked due to security reasons)
	 * @param additionalData - Optional additional data to include with the error
	 */
	accountLocked: (additionalData?: unknown) => new AuthenticationError('ACCOUNT_LOCKED', additionalData),
	/**
	 * Account inactive error (account has not been activated)
	 * @param additionalData - Optional additional data to include with the error
	 */
	accountInactive: (additionalData?: unknown) => new AuthenticationError('ACCOUNT_INACTIVE', additionalData),
	/**
	 * Session expired error (user session has expired)
	 * @param additionalData - Optional additional data to include with the error
	 */
	sessionExpired: (additionalData?: unknown) => new AuthenticationError('SESSION_EXPIRED', additionalData),

	// Authorization
	/**
	 * Forbidden error (user does not have permission to access resource)
	 * @param additionalData - Optional additional data to include with the error
	 */
	forbidden: (additionalData?: unknown) => new AuthorizationError('FORBIDDEN', additionalData),
	/**
	 * Insufficient permissions error (user lacks required permissions)
	 * @param additionalData - Optional additional data to include with the error
	 */
	insufficientPermissions: (additionalData?: unknown) =>
		new AuthorizationError('INSUFFICIENT_PERMISSIONS', additionalData),

	// Validation
	/**
	 * Generic validation error with field-level errors
	 * @param fieldErrors - Array of field errors with field name, code, and message
	 */
	validation: (fieldErrors?: Array<{ field: string; code: string; message: { vi: string; en: string } }>) =>
		new ValidationError(fieldErrors),
	/**
	 * Invalid email format error
	 * @param field - Field name (defaults to 'email')
	 */
	invalidEmail: (field?: string) =>
		new ValidationError([
			{
				field: field || 'email',
				code: 'INVALID_EMAIL_FORMAT',
				message: { vi: 'Email không hợp lệ', en: 'Invalid email' },
			},
		]),
	/**
	 * Invalid UUID format error
	 * @param field - Field name (defaults to 'id')
	 */
	invalidUuid: (field?: string) =>
		new ValidationError([
			{
				field: field || 'id',
				code: 'INVALID_UUID_FORMAT',
				message: { vi: 'ID không hợp lệ', en: 'Invalid ID' },
			},
		]),
	/**
	 * Password too short error
	 * @param field - Field name (defaults to 'password')
	 */
	passwordTooShort: (field?: string) =>
		new ValidationError([
			{
				field: field || 'password',
				code: 'PASSWORD_TOO_SHORT',
				message: { vi: 'Mật khẩu quá ngắn', en: 'Password is too short' },
			},
		]),

	// Not found
	/**
	 * Generic not found error
	 * @param resource - Resource name (defaults to 'Resource')
	 */
	notFound: (resource?: string) => new NotFoundError(resource),
	/**
	 * User not found error
	 * @param additionalData - Optional additional data to include with the error
	 */
	userNotFound: (additionalData?: unknown) => new NotFoundError('Người dùng', additionalData),
	/**
	 * File not found error
	 * @param additionalData - Optional additional data to include with the error
	 */
	fileNotFound: (additionalData?: unknown) => new NotFoundError('File', additionalData),

	// Conflict
	/**
	 * Generic conflict error
	 * @param additionalData - Optional additional data to include with the error
	 */
	conflict: (additionalData?: unknown) => new ConflictError('CONFLICT', additionalData),
	/**
	 * User already exists error
	 * @param additionalData - Optional additional data to include with the error
	 */
	userExists: (additionalData?: unknown) => new ConflictError('USER_EXISTS', additionalData),

	// Database
	/**
	 * Generic database error
	 * @param additionalData - Optional additional data to include with the error
	 */
	databaseError: (additionalData?: unknown) => new DatabaseError('DATABASE_ERROR', additionalData),
	/**
	 * Foreign key constraint error
	 * @param additionalData - Optional additional data to include with the error
	 */
	foreignKeyConstraint: (additionalData?: unknown) => new DatabaseError('FOREIGN_KEY_CONSTRAINT', additionalData),
	/**
	 * Unique constraint error
	 * @param additionalData - Optional additional data to include with the error
	 */
	uniqueConstraint: (additionalData?: unknown) => new DatabaseError('UNIQUE_CONSTRAINT', additionalData),

	// External service
	/**
	 * Circuit breaker open error (service unavailable due to circuit breaker)
	 * @param additionalData - Optional additional data to include with the error
	 */
	circuitBreakerOpen: (additionalData?: unknown) => new ExternalServiceError('CIRCUIT_BREAKER_OPEN', additionalData),
	/**
	 * External service error (third-party service failure)
	 * @param additionalData - Optional additional data to include with the error
	 */
	externalServiceError: (additionalData?: unknown) =>
		new ExternalServiceError('EXTERNAL_SERVICE_ERROR', additionalData),

	// System
	/**
	 * Internal server error (unexpected server error)
	 * @param additionalData - Optional additional data to include with the error
	 */
	internalError: (additionalData?: unknown) => new GenericError('INTERNAL_SERVER_ERROR', additionalData),
} as const;
