import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { ModalService } from './modal.service';
import { LoggerService } from './logger.service';
import { AuthService } from './auth.service';
import axios, { AxiosError, AxiosResponse } from 'axios';

/**
 * Error response interface matching backend ErrorResponseDto
 */
export interface ErrorResponse {
  error: string;
  message: string;
  status: number;
  timestamp?: string;
  path?: string;
  correlationId?: string;
  errors?: { [key: string]: string }; // Validation errors
  details?: { [key: string]: any }; // Additional error details
  requires2FA?: boolean;
}

/**
 * Global error handler service for centralized error processing
 */
@Injectable({
  providedIn: 'root'
})
export class ErrorHandlerService {
  private isHandlingError = false;

  constructor(
    private modalService: ModalService,
    private logger: LoggerService,
    private authService: AuthService,
    private router: Router
  ) {
    this.setupAxiosInterceptor();
  }

  /**
   * Setup axios interceptor to catch all HTTP errors globally
   */
  private setupAxiosInterceptor(): void {
    // Response interceptor
    axios.interceptors.response.use(
      (response: AxiosResponse) => response,
      (error: AxiosError) => {
        this.handleError(error);
        return Promise.reject(error);
      }
    );
  }

  /**
   * Handle error from axios request
   */
  handleError(error: AxiosError | Error): void {
    // Prevent infinite loops
    if (this.isHandlingError) {
      return;
    }

    this.isHandlingError = true;

    try {
      if (this.isAxiosError(error)) {
        this.handleHttpError(error);
      } else {
        this.handleGenericError(error);
      }
    } catch (handlerError) {
      this.logger.error('Error in error handler:', handlerError);
    } finally {
      this.isHandlingError = false;
    }
  }

  /**
   * Handle HTTP errors from axios
   */
  private handleHttpError(error: AxiosError): void {
    const response = error.response;
    const status = response?.status || 0;
    const errorData: ErrorResponse = response?.data as ErrorResponse || {
      error: 'Request failed',
      message: error.message || 'An unexpected error occurred',
      status
    };

    // Log error with correlation ID if available
    const correlationId = errorData.correlationId || 'N/A';
    this.logger.error(`HTTP Error [${status}] | Correlation ID: ${correlationId}`, {
      error: errorData.error,
      message: errorData.message,
      path: errorData.path,
      url: error.config?.url,
      method: error.config?.method,
      correlationId
    });

    // Handle specific status codes
    switch (status) {
      case 401:
        this.handleUnauthorized(errorData);
        break;
      case 403:
        this.handleForbidden(errorData);
        break;
      case 404:
        this.handleNotFound(errorData, error);
        break;
      case 400:
        this.handleBadRequest(errorData);
        break;
      case 409:
        this.handleConflict(errorData);
        break;
      case 422:
        this.handleValidationError(errorData);
        break;
      case 500:
      case 502:
      case 503:
      case 504:
        this.handleServerError(errorData);
        break;
      default:
        this.handleGenericHttpError(errorData);
    }
  }

  /**
   * Handle 401 Unauthorized errors
   */
  private handleUnauthorized(errorData: ErrorResponse): void {
    // Don't show modal for 2FA requirement - let the component handle it
    const message = errorData.message?.toLowerCase() || '';
    if (errorData.requires2FA || 
        message.includes('2fa') || 
        message.includes('two factor') ||
        message.includes('2-factor')) {
      return;
    }

    // If token is invalid, logout and redirect to login
    if (this.authService.isAuthenticated()) {
      this.authService.logout().then(() => {
        this.router.navigate(['/login']);
      });
    }

    this.showErrorModal(
      'Session Expired',
      errorData.message || 'Your session has expired. Please log in again.',
      'error'
    );
  }

  /**
   * Handle 403 Forbidden errors
   */
  private handleForbidden(errorData: ErrorResponse): void {
    this.showErrorModal(
      'Access Denied',
      errorData.message || 'You do not have permission to perform this action.',
      'error'
    );
  }

  /**
   * Handle 404 Not Found errors
   */
  private handleNotFound(errorData: ErrorResponse, error?: AxiosError): void {
    // Get URL from error path or config
    const url = errorData.path || error?.config?.url || '';
    
    // List of endpoints that may legitimately return 404 when no data exists
    // These are expected 404s and should not show error modals
    const expected404Endpoints = [
      '/api/v3/vital-signs/patient/',
      '/api/v3/lab-results/patient/',
      '/api/v3/medical-records/patient/',
      '/api/v3/prescriptions/patient/',
      '/api/v3/appointments/patient/',
      '/latest', // Any endpoint ending with /latest
      '/patient/', // Any patient-specific endpoint
    ];
    
    // Check if this is an expected 404 (no data exists)
    const isExpected404 = expected404Endpoints.some(endpoint => url.includes(endpoint));
    
    if (isExpected404) {
      // Log but don't show modal for expected 404s
      this.logger.debug('Expected 404 (no data found):', {
        url,
        message: errorData.message,
        correlationId: errorData.correlationId
      });
      return; // Don't show error modal for expected 404s
    }
    
    // Show modal for unexpected 404s (actual resource not found)
    this.showErrorModal(
      'Not Found',
      errorData.message || 'The requested resource was not found.',
      'warning'
    );
  }

  /**
   * Handle 400 Bad Request errors
   */
  private handleBadRequest(errorData: ErrorResponse): void {
    // Don't show modal for 2FA requirement - let the component handle it
    const message = errorData.message?.toLowerCase() || '';
    if (errorData.requires2FA || 
        message.includes('2fa') || 
        message.includes('two factor') ||
        message.includes('2-factor')) {
      return;
    }

    // If validation errors exist, show them
    if (errorData.errors && Object.keys(errorData.errors).length > 0) {
      this.handleValidationError(errorData);
      return;
    }

    this.showErrorModal(
      'Invalid Request',
      errorData.message || 'The request is invalid. Please check your input.',
      'warning'
    );
  }

  /**
   * Handle 409 Conflict errors
   */
  private handleConflict(errorData: ErrorResponse): void {
    this.showErrorModal(
      'Conflict',
      errorData.message || 'The operation conflicts with existing data.',
      'warning'
    );
  }

  /**
   * Handle 422 Validation errors
   */
  private handleValidationError(errorData: ErrorResponse): void {
    let message = errorData.message || 'Validation failed. Please check the following:';
    
    if (errorData.errors && Object.keys(errorData.errors).length > 0) {
      const errorList = Object.entries(errorData.errors)
        .map(([field, msg]) => `• ${field}: ${msg}`)
        .join('\n');
      message = `${message}\n\n${errorList}`;
    }

    this.showErrorModal(
      'Validation Error',
      message,
      'warning'
    );
  }

  /**
   * Handle 5xx Server errors
   */
  private handleServerError(errorData: ErrorResponse): void {
    const correlationId = errorData.correlationId || 'N/A';
    this.showErrorModal(
      'Server Error',
      `${errorData.message || 'An internal server error occurred. Please try again later.'}\n\nCorrelation ID: ${correlationId}`,
      'error'
    );
  }

  /**
   * Handle generic HTTP errors
   */
  private handleGenericHttpError(errorData: ErrorResponse): void {
    this.showErrorModal(
      'Error',
      errorData.message || 'An error occurred while processing your request.',
      'error'
    );
  }

  /**
   * Handle generic errors (non-HTTP)
   */
  private handleGenericError(error: Error): void {
    this.logger.error('Generic error:', error);
    
    // Only show modal for critical errors
    if (error.message && !error.message.includes('Network Error')) {
      this.showErrorModal(
        'Error',
        error.message || 'An unexpected error occurred.',
        'error'
      );
    }
  }

  /**
   * Show error modal to user
   */
  private showErrorModal(title: string, message: string, type: 'error' | 'warning' | 'info' = 'error'): void {
    // Use setTimeout to avoid blocking the error handling flow
    setTimeout(() => {
      this.modalService.alert(message, type, title).catch(err => {
        this.logger.error('Error showing error modal:', err);
      });
    }, 0);
  }

  /**
   * Check if error is an AxiosError
   */
  private isAxiosError(error: any): error is AxiosError {
    return error && error.isAxiosError !== undefined;
  }

  /**
   * Extract user-friendly error message from error
   */
  getErrorMessage(error: AxiosError | Error): string {
    if (this.isAxiosError(error)) {
      const errorData: ErrorResponse = error.response?.data as ErrorResponse || {};
      return errorData.message || errorData.error || error.message || 'An unexpected error occurred';
    }
    return error.message || 'An unexpected error occurred';
  }

  /**
   * Extract validation errors from error response
   */
  getValidationErrors(error: AxiosError): { [key: string]: string } | null {
    if (this.isAxiosError(error)) {
      const errorData: ErrorResponse = error.response?.data as ErrorResponse || {};
      return errorData.errors || null;
    }
    return null;
  }

  /**
   * Check if error requires 2FA
   */
  requires2FA(error: AxiosError): boolean {
    if (this.isAxiosError(error)) {
      const errorData: ErrorResponse = error.response?.data as ErrorResponse || {};
      return errorData.requires2FA === true || 
             errorData.message?.toLowerCase().includes('2fa') === true;
    }
    return false;
  }

  /**
   * Get correlation ID from error response
   */
  getCorrelationId(error: AxiosError): string | null {
    if (this.isAxiosError(error)) {
      const errorData: ErrorResponse = error.response?.data as ErrorResponse || {};
      return errorData.correlationId || null;
    }
    return null;
  }
}

