import { Component, ReactNode, ErrorInfo as ReactErrorInfo } from 'react';
import { motion } from 'framer-motion';
import { AlertTriangle, RefreshCcw, Bug, Home, WifiOff } from 'lucide-react';
import { Button } from './Button';
import { reportError } from '../../lib/error-handling';
import { classifyError, AppError } from '../../lib/error-types';

// === Types ===

/** Extended error info with additional metadata */
interface ExtendedErrorInfo extends ReactErrorInfo {
  errorName?: string;
  errorMessage?: string;
}

interface ErrorBoundaryProps {
  children: ReactNode;
  fallback?: ReactNode;
  onError?: (error: Error, errorInfo: ReactErrorInfo) => void;
  onReset?: () => void;
  /** Whether to show connection status indicator */
  showConnectionStatus?: boolean;
  /** Custom error title */
  errorTitle?: string;
  /** Custom error message */
  errorMessage?: string;
}

interface ErrorBoundaryState {
  hasError: boolean;
  error: Error | null;
  errorInfo: ExtendedErrorInfo | null;
  appError: AppError | null;
  showDetails: boolean;
}

// === Global Error Types ===

type GlobalErrorType = 'unhandled-rejection' | 'error' | 'websocket' | 'network';

interface GlobalErrorEvent {
  type: GlobalErrorType;
  error: unknown;
  timestamp: Date;
}

// === Global Error Handler Registry ===

const globalErrorListeners = new Set<(event: GlobalErrorEvent) => void>();

export function addGlobalErrorListener(listener: (event: GlobalErrorEvent) => void): () => void {
  globalErrorListeners.add(listener);
  return () => globalErrorListeners.delete(listener);
}

function notifyGlobalErrorListeners(event: GlobalErrorEvent): void {
  globalErrorListeners.forEach(listener => {
    try {
      listener(event);
    } catch (e) {
      console.error('[GlobalErrorHandler] Listener error:', e);
    }
  });
}

// === Setup Global Error Handlers ===

let globalHandlersSetup = false;

export function setupGlobalErrorHandlers(): () => void {
  if (globalHandlersSetup) {
    return () => {};
  }
  globalHandlersSetup = true;

  // Handle unhandled promise rejections
  const handleRejection = (event: PromiseRejectionEvent) => {
    console.error('[GlobalErrorHandler] Unhandled rejection:', event.reason);
    notifyGlobalErrorListeners({
      type: 'unhandled-rejection',
      error: event.reason,
      timestamp: new Date(),
    });
    // Prevent default browser error logging (we handle it ourselves)
    event.preventDefault();
  };

  // Handle uncaught errors
  const handleError = (event: ErrorEvent) => {
    console.error('[GlobalErrorHandler] Uncaught error:', event.error);
    notifyGlobalErrorListeners({
      type: 'error',
      error: event.error,
      timestamp: new Date(),
    });
    // Let the error boundary handle it if possible
  };

  // Handle WebSocket errors globally
  const handleWebSocketError = (event: Event) => {
    if (event.target instanceof WebSocket) {
      console.error('[GlobalErrorHandler] WebSocket error:', event);
      notifyGlobalErrorListeners({
        type: 'websocket',
        error: new Error('WebSocket connection error'),
        timestamp: new Date(),
      });
    }
  };

  window.addEventListener('unhandledrejection', handleRejection);
  window.addEventListener('error', handleError);
  window.addEventListener('error', handleWebSocketError, true); // Capture phase for WebSocket

  return () => {
    window.removeEventListener('unhandledrejection', handleRejection);
    window.removeEventListener('error', handleError);
    window.removeEventListener('error', handleWebSocketError, true);
    globalHandlersSetup = false;
  };
}

/**
 * GlobalErrorBoundary Component
 *
 * Root-level error boundary that catches all React errors and global errors.
 * Displays a user-friendly error screen with recovery options.
 */
export class GlobalErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
  private cleanupGlobalHandlers: (() => void) | null = null;

  constructor(props: ErrorBoundaryProps) {
    super(props);
    this.state = {
      hasError: false,
      error: null,
      errorInfo: null,
      appError: null,
      showDetails: false,
    };
  }

  static getDerivedStateFromError(error: Error): Partial<ErrorBoundaryState> {
    const appError = classifyError(error);
    return {
      hasError: true,
      error,
      appError,
    };
  }

  componentDidMount() {
    // Setup global error handlers
    this.cleanupGlobalHandlers = setupGlobalErrorHandlers();

    // Listen for global errors and update state
    const unsubscribe = addGlobalErrorListener((event) => {
      if (!this.state.hasError) {
        const appError = classifyError(event.error);
        this.setState({
          hasError: true,
          error: event.error instanceof Error ? event.error : new Error(String(event.error)),
          appError,
          errorInfo: null,
        });
      }
    });

    // Store cleanup function
    this.cleanupGlobalHandlers = () => {
      unsubscribe();
    };
  }

  componentWillUnmount() {
    this.cleanupGlobalHandlers?.();
  }

  componentDidCatch(error: Error, errorInfo: ReactErrorInfo) {
    const { onError } = this.props;

    // Classify the error
    const appError = classifyError(error);

    // Update state with extended error info
    const extendedErrorInfo: ExtendedErrorInfo = {
      componentStack: errorInfo.componentStack,
      errorName: error.name || 'Unknown Error',
      errorMessage: error.message || 'An unexpected error occurred',
    };

    this.setState({
      errorInfo: extendedErrorInfo,
      appError,
    });

    // Call optional error handler
    if (onError) {
      onError(error, errorInfo);
    }

    // Report to error tracking
    reportError(error, {
      componentStack: errorInfo.componentStack ?? undefined,
      errorName: error.name,
      errorMessage: error.message,
    });
  }

  handleReset = () => {
    const { onReset } = this.props;

    // Reset error state
    this.setState({
      hasError: false,
      error: null,
      errorInfo: null,
      appError: null,
      showDetails: false,
    });

    // Call optional reset handler
    if (onReset) {
      onReset();
    }
  };

  handleReload = () => {
    window.location.reload();
  };

  handleGoHome = () => {
    window.location.href = '/';
  };

  handleReport = () => {
    const { error, errorInfo } = this.state;
    if (error) {
      reportError(error, {
        componentStack: errorInfo?.componentStack ?? undefined,
        errorName: errorInfo?.errorName || error.name,
        errorMessage: errorInfo?.errorMessage || error.message,
      });
      // Show confirmation
      alert('Error reported. Thank you for your feedback.');
    }
  };

  toggleDetails = () => {
    this.setState(prev => ({ showDetails: !prev.showDetails }));
  };

  render() {
    const { children, fallback, errorTitle, errorMessage } = this.props;
    const { hasError, error, errorInfo, appError, showDetails } = this.state;

    if (hasError && error) {
      // Use custom fallback if provided
      if (fallback) {
        return fallback;
      }

      // Get error display info
      const title = errorTitle || appError?.title || 'Something went wrong';
      const message = errorMessage || appError?.message || error.message || 'An unexpected error occurred';
      const category = appError?.category || 'system';
      const isNetworkError = category === 'network';

      return (
        <div className="min-h-screen flex items-center justify-center bg-gray-50 dark:bg-gray-900 p-4">
          <motion.div
            initial={{ opacity: 0, scale: 0.95 }}
            animate={{ opacity: 1, scale: 1 }}
            transition={{ duration: 0.2 }}
            className="max-w-lg w-full bg-white dark:bg-gray-800 rounded-xl shadow-lg overflow-hidden"
          >
            {/* Error Header */}
            <div className={`p-6 ${isNetworkError ? 'bg-orange-50 dark:bg-orange-900/20' : 'bg-red-50 dark:bg-red-900/20'}`}>
              <div className="flex items-center gap-4">
                <div className={`p-3 rounded-full ${isNetworkError ? 'bg-orange-100 dark:bg-orange-900/40' : 'bg-red-100 dark:bg-red-900/40'}`}>
                  {isNetworkError ? (
                    <WifiOff className="w-8 h-8 text-orange-500" />
                  ) : (
                    <AlertTriangle className="w-8 h-8 text-red-500" />
                  )}
                </div>
                <div>
                  <h2 className="text-lg font-semibold text-gray-900 dark:text-white">
                    {title}
                  </h2>
                  <p className="text-sm text-gray-600 dark:text-gray-400 mt-1">
                    {message}
                  </p>
                </div>
              </div>
            </div>

            {/* Error Details */}
            <div className="p-6">
              {/* Category Badge */}
              {appError && (
                <div className="flex items-center gap-2 mb-4">
                  <span className={`px-2 py-1 text-xs font-medium rounded-full ${
                    category === 'network' ? 'bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-400' :
                    category === 'auth' ? 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400' :
                    category === 'server' ? 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400' :
                    'bg-gray-100 text-gray-700 dark:bg-gray-700 dark:text-gray-300'
                  }`}>
                    {category.charAt(0).toUpperCase() + category.slice(1)} Error
                  </span>
                  {appError.recoverable && (
                    <span className="px-2 py-1 text-xs font-medium rounded-full bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400">
                      Recoverable
                    </span>
                  )}
                </div>
              )}

              {/* Recovery Steps */}
              {appError?.recoverySteps && appError.recoverySteps.length > 0 && (
                <div className="mb-4 p-4 bg-gray-50 dark:bg-gray-700/50 rounded-lg">
                  <h3 className="text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
                    Suggested Actions:
                  </h3>
                  <ul className="space-y-2">
                    {appError.recoverySteps.slice(0, 3).map((step, index) => (
                      <li key={index} className="text-sm text-gray-600 dark:text-gray-400 flex items-start gap-2">
                        <span className="text-gray-400 mt-0.5">{index + 1}.</span>
                        <span>{step.description}</span>
                      </li>
                    ))}
                  </ul>
                </div>
              )}

              {/* Technical Details Toggle */}
              <button
                onClick={this.toggleDetails}
                className="text-sm text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 flex items-center gap-1 mb-4"
              >
                <span>{showDetails ? 'Hide' : 'Show'} technical details</span>
                <motion.span
                  animate={{ rotate: showDetails ? 180 : 0 }}
                  transition={{ duration: 0.2 }}
                >
                  ▼
                </motion.span>
              </button>

              {/* Technical Details */}
              {showDetails && (
                <motion.div
                  initial={{ height: 0, opacity: 0 }}
                  animate={{ height: 'auto', opacity: 1 }}
                  exit={{ height: 0, opacity: 0 }}
                  className="overflow-hidden mb-4"
                >
                  <pre className="p-3 bg-gray-100 dark:bg-gray-700 rounded-lg text-xs text-gray-600 dark:text-gray-400 overflow-x-auto whitespace-pre-wrap break-words max-h-48">
                    {errorInfo?.errorName || error.name}: {errorInfo?.errorMessage || error.message}
                    {errorInfo?.componentStack && `\n\nComponent Stack:${errorInfo.componentStack}`}
                  </pre>
                </motion.div>
              )}

              {/* Actions */}
              <div className="flex flex-col gap-2">
                <div className="flex gap-2">
                  <Button
                    variant="primary"
                    size="sm"
                    onClick={this.handleReset}
                    className="flex-1"
                  >
                    <RefreshCcw className="w-4 h-4 mr-2" />
                    Try Again
                  </Button>
                  <Button
                    variant="secondary"
                    size="sm"
                    onClick={this.handleReload}
                    className="flex-1"
                  >
                    Reload Page
                  </Button>
                </div>
                <div className="flex gap-2">
                  <Button
                    variant="ghost"
                    size="sm"
                    onClick={this.handleReport}
                    className="flex-1"
                  >
                    <Bug className="w-4 h-4 mr-2" />
                    Report Issue
                  </Button>
                  <Button
                    variant="ghost"
                    size="sm"
                    onClick={this.handleGoHome}
                    className="flex-1"
                  >
                    <Home className="w-4 h-4 mr-2" />
                    Go Home
                  </Button>
                </div>
              </div>
            </div>
          </motion.div>
        </div>
      );
    }

    return children;
  }
}

/**
 * ErrorBoundary Component
 *
 * A simpler error boundary for wrapping individual components or sections.
 * Use GlobalErrorBoundary for the root level.
 */
export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
  constructor(props: ErrorBoundaryProps) {
    super(props);
    this.state = {
      hasError: false,
      error: null,
      errorInfo: null,
      appError: null,
      showDetails: false,
    };
  }

  static getDerivedStateFromError(error: Error): Partial<ErrorBoundaryState> {
    const appError = classifyError(error);
    return {
      hasError: true,
      error,
      appError,
    };
  }

  componentDidCatch(error: Error, errorInfo: ReactErrorInfo) {
    const { onError } = this.props;

    // Update state with extended error info
    const extendedErrorInfo: ExtendedErrorInfo = {
      componentStack: errorInfo.componentStack,
      errorName: error.name || 'Unknown Error',
      errorMessage: error.message || 'An unexpected error occurred',
    };

    this.setState({
      errorInfo: extendedErrorInfo,
    });

    // Call optional error handler
    if (onError) {
      onError(error, errorInfo);
    }

    // Report error
    reportError(error, {
      componentStack: errorInfo.componentStack ?? undefined,
      errorName: error.name,
      errorMessage: error.message,
    });
  }

  handleReset = () => {
    const { onReset } = this.props;
    this.setState({
      hasError: false,
      error: null,
      errorInfo: null,
      appError: null,
      showDetails: false,
    });
    if (onReset) {
      onReset();
    }
  };

  render() {
    const { children, fallback } = this.props;
    const { hasError, error, appError } = this.state;

    if (hasError && error) {
      if (fallback) {
        return fallback;
      }

      // Compact error UI for nested boundaries
      return (
        <div className="p-4 bg-red-50 dark:bg-red-900/20 rounded-lg border border-red-200 dark:border-red-800">
          <div className="flex items-start gap-3">
            <AlertTriangle className="w-5 h-5 text-red-500 flex-shrink-0 mt-0.5" />
            <div className="flex-1 min-w-0">
              <h3 className="text-sm font-medium text-red-800 dark:text-red-200">
                {appError?.title || 'Error'}
              </h3>
              <p className="text-sm text-red-600 dark:text-red-400 mt-1">
                {appError?.message || error.message}
              </p>
              <Button
                variant="ghost"
                size="sm"
                onClick={this.handleReset}
                className="mt-2 text-red-600 dark:text-red-400 hover:text-red-800 dark:hover:text-red-200"
              >
                <RefreshCcw className="w-3 h-3 mr-1" />
                Retry
              </Button>
            </div>
          </div>
        </div>
      );
    }

    return children;
  }
}

// === Re-export for convenience ===
export { GlobalErrorBoundary as RootErrorBoundary };
