import { Injectable, inject, isDevMode } from '@angular/core';
import { NotificationService } from './notification.service';

/** Estructura de error de Apollo (sin importar ApolloError deprecado) */
interface GraphQLErrorLike {
  message: string;
  extensions?: Record<string, unknown>;
  path?: readonly (string | number)[];
}

interface ApolloErrorLike {
  graphQLErrors?: readonly GraphQLErrorLike[];
  networkError?: { status?: number; message?: string } | null;
  message: string;
}

/** Códigos de error del backend GraphQL */
type ErrorCode =
  | 'MAX_ENROLLMENTS'
  | 'SAME_PROFESSOR'
  | 'STUDENT_NOT_FOUND'
  | 'SUBJECT_NOT_FOUND'
  | 'ENROLLMENT_NOT_FOUND'
  | 'DUPLICATE_ENROLLMENT'
  | 'VALIDATION_ERROR'
  | 'INVALID_ARGUMENT'
  | 'NETWORK_ERROR'
  | 'SERVER_ERROR'
  | 'UNKNOWN';

interface ErrorInfo {
  /** Mensaje amigable para el usuario */
  userMessage: string;
  /** Mensaje técnico para desarrolladores */
  devMessage: string;
  /** Sugerencia de acción para el usuario */
  suggestion?: string;
  /** Código de error */
  code: ErrorCode;
}

/** Mapeo de códigos de error a mensajes amigables */
const ERROR_MESSAGES: Record<ErrorCode, { user: string; suggestion?: string }> = {
  MAX_ENROLLMENTS: {
    user: 'Has alcanzado el límite máximo de 3 materias inscritas',
    suggestion: 'Debes cancelar una inscripción antes de agregar otra materia',
  },
  SAME_PROFESSOR: {
    user: 'Ya tienes una materia con este profesor',
    suggestion: 'No puedes inscribir dos materias del mismo profesor',
  },
  STUDENT_NOT_FOUND: {
    user: 'El estudiante no existe en el sistema',
    suggestion: 'Verifica que el estudiante esté registrado',
  },
  SUBJECT_NOT_FOUND: {
    user: 'La materia seleccionada no existe',
    suggestion: 'Recarga la página e intenta nuevamente',
  },
  ENROLLMENT_NOT_FOUND: {
    user: 'La inscripción no fue encontrada',
    suggestion: 'Es posible que ya haya sido cancelada',
  },
  DUPLICATE_ENROLLMENT: {
    user: 'Ya estás inscrito en esta materia',
  },
  VALIDATION_ERROR: {
    user: 'Los datos ingresados no son válidos',
    suggestion: 'Revisa los campos marcados en rojo',
  },
  INVALID_ARGUMENT: {
    user: 'Datos inválidos enviados al servidor',
  },
  NETWORK_ERROR: {
    user: 'No se pudo conectar con el servidor',
    suggestion: 'Verifica tu conexión a internet y que el servidor esté activo',
  },
  SERVER_ERROR: {
    user: 'Error interno del servidor',
    suggestion: 'Intenta nuevamente en unos momentos',
  },
  UNKNOWN: {
    user: 'Ocurrió un error inesperado',
    suggestion: 'Si el problema persiste, contacta al administrador',
  },
};

@Injectable({ providedIn: 'root' })
export class ErrorHandlerService {
  private notification = inject(NotificationService);

  /**
   * Procesa un error y retorna información estructurada
   */
  parseError(error: unknown): ErrorInfo {
    // Error de Apollo/GraphQL
    if (this.isApolloError(error)) {
      return this.parseApolloError(error);
    }

    // Error HTTP genérico
    if (this.isHttpError(error)) {
      return this.parseHttpError(error);
    }

    // Error desconocido
    return this.createErrorInfo('UNKNOWN', String(error));
  }

  /**
   * Procesa el error, muestra notificación y loguea para desarrolladores
   */
  handle(error: unknown, context?: string): ErrorInfo {
    const errorInfo = this.parseError(error);

    // Notificación al usuario
    const message = errorInfo.suggestion
      ? `${errorInfo.userMessage}. ${errorInfo.suggestion}`
      : errorInfo.userMessage;
    this.notification.error(message);

    // Logging detallado para desarrolladores
    this.logForDevelopers(errorInfo, error, context);

    return errorInfo;
  }

  /**
   * Solo loguea para desarrolladores sin mostrar notificación
   */
  logError(error: unknown, context?: string): void {
    const errorInfo = this.parseError(error);
    this.logForDevelopers(errorInfo, error, context);
  }

  private isApolloError(error: unknown): error is ApolloErrorLike {
    return typeof error === 'object' && error !== null &&
      ('graphQLErrors' in error || 'networkError' in error);
  }

  private isHttpError(error: unknown): error is { status: number; message: string } {
    return typeof error === 'object' && error !== null && 'status' in error;
  }

  private parseApolloError(error: ApolloErrorLike): ErrorInfo {
    // Error de red (servidor no disponible)
    if (error.networkError) {
      const netError = error.networkError as { status?: number; message?: string };
      const devMessage = this.formatNetworkError(netError);
      return this.createErrorInfo('NETWORK_ERROR', devMessage);
    }

    // Errores de GraphQL
    if (error.graphQLErrors?.length) {
      const gqlError = error.graphQLErrors[0];
      const code = (gqlError.extensions?.['code'] as ErrorCode) || 'UNKNOWN';
      const devMessage = this.formatGraphQLError(gqlError);

      // Errores de validación pueden tener múltiples campos
      if (code === 'VALIDATION_ERROR' && gqlError.extensions?.['errors']) {
        const validationErrors = gqlError.extensions['errors'] as Array<{
          PropertyName: string;
          ErrorMessage: string;
        }>;
        const details = validationErrors
          .map(e => `${e.PropertyName}: ${e.ErrorMessage}`)
          .join(', ');
        return this.createErrorInfo(code, details);
      }

      return this.createErrorInfo(code, devMessage);
    }

    return this.createErrorInfo('UNKNOWN', error.message);
  }

  private parseHttpError(error: { status: number; message: string }): ErrorInfo {
    if (error.status === 0) {
      return this.createErrorInfo('NETWORK_ERROR', 'Sin respuesta del servidor (status 0)');
    }
    if (error.status >= 500) {
      return this.createErrorInfo('SERVER_ERROR', `HTTP ${error.status}: ${error.message}`);
    }
    return this.createErrorInfo('UNKNOWN', `HTTP ${error.status}: ${error.message}`);
  }

  private createErrorInfo(code: ErrorCode, devMessage: string): ErrorInfo {
    const config = ERROR_MESSAGES[code] || ERROR_MESSAGES.UNKNOWN;
    return {
      code,
      userMessage: config.user,
      suggestion: config.suggestion,
      devMessage,
    };
  }

  private formatNetworkError(error: { status?: number; message?: string }): string {
    if (error.status === 0 || !error.status) {
      return 'NETWORK_ERROR: No se pudo establecer conexión con el servidor. ' +
        'Posibles causas: servidor apagado, URL incorrecta, CORS bloqueado, o sin internet.';
    }
    return `NETWORK_ERROR: HTTP ${error.status} - ${error.message || 'Sin mensaje'}`;
  }

  private formatGraphQLError(error: { message: string; path?: readonly (string | number)[] }): string {
    const path = error.path?.join('.') || 'root';
    return `GraphQL Error en "${path}": ${error.message}`;
  }

  private logForDevelopers(info: ErrorInfo, originalError: unknown, context?: string): void {
    if (!isDevMode()) return;

    const timestamp = new Date().toISOString();
    const contextStr = context ? ` [${context}]` : '';

    console.group(`🚨 Error${contextStr} - ${timestamp}`);
    console.log('%c Código:', 'font-weight: bold', info.code);
    console.log('%c Usuario verá:', 'font-weight: bold; color: #e74c3c', info.userMessage);
    if (info.suggestion) {
      console.log('%c Sugerencia:', 'font-weight: bold; color: #3498db', info.suggestion);
    }
    console.log('%c Detalle técnico:', 'font-weight: bold; color: #95a5a6', info.devMessage);
    console.log('%c Error original:', 'font-weight: bold', originalError);
    console.groupEnd();
  }
}
