/**
 * Centralized Error Handling
 * Standardized error classes and handlers for the FF ERP system
 */

import { NextResponse } from 'next/server';

// ============================================
// ERROR CODES
// ============================================

export const ErrorCodes = {
  // Authentication & Authorization (1xxx)
  AUTHENTICATION_REQUIRED: 'AUTH_1001',
  INVALID_CREDENTIALS: 'AUTH_1002',
  TOKEN_EXPIRED: 'AUTH_1003',
  INSUFFICIENT_PERMISSIONS: 'AUTH_1004',
  ACCOUNT_LOCKED: 'AUTH_1005',
  ACCOUNT_DISABLED: 'AUTH_1006',
  
  // Validation (2xxx)
  VALIDATION_ERROR: 'VAL_2001',
  INVALID_INPUT: 'VAL_2002',
  MISSING_REQUIRED_FIELD: 'VAL_2003',
  INVALID_FORMAT: 'VAL_2004',
  
  // Resource (3xxx)
  RESOURCE_NOT_FOUND: 'RES_3001',
  RESOURCE_ALREADY_EXISTS: 'RES_3002',
  RESOURCE_CONFLICT: 'RES_3003',
  RESOURCE_LOCKED: 'RES_3004',
  
  // Business Logic (4xxx)
  BUSINESS_RULE_VIOLATION: 'BUS_4001',
  INVALID_STATE_TRANSITION: 'BUS_4002',
  OPERATION_NOT_ALLOWED: 'BUS_4003',
  QUOTA_EXCEEDED: 'BUS_4004',
  WORKFLOW_ERROR: 'BUS_4005',
  
  // External Services (5xxx)
  EXTERNAL_SERVICE_ERROR: 'EXT_5001',
  DATABASE_ERROR: 'EXT_5002',
  API_TIMEOUT: 'EXT_5003',
  RATE_LIMITED: 'EXT_5004',
  
  // System (9xxx)
  INTERNAL_ERROR: 'SYS_9001',
  NOT_IMPLEMENTED: 'SYS_9002',
  SERVICE_UNAVAILABLE: 'SYS_9003',
  CONFIGURATION_ERROR: 'SYS_9004',
} as const;

export type ErrorCode = typeof ErrorCodes[keyof typeof ErrorCodes];

// ============================================
// APP ERROR CLASS
// ============================================

export interface AppErrorOptions {
  message: string;
  code: ErrorCode;
  statusCode?: number;
  details?: unknown;
  cause?: Error;
  isOperational?: boolean;
}

/**
 * Application Error - Base class for all application errors
 * 
 * @example
 * throw new AppError({
 *   message: 'Product not found',
 *   code: ErrorCodes.RESOURCE_NOT_FOUND,
 *   statusCode: 404,
 *   details: { productId: '123' }
 * });
 */
export class AppError extends Error {
  public readonly code: ErrorCode;
  public readonly statusCode: number;
  public readonly details?: unknown;
  public readonly isOperational: boolean;
  public readonly timestamp: Date;
  public readonly originalCause?: Error;

  constructor(options: AppErrorOptions) {
    super(options.message);
    this.name = 'AppError';
    this.code = options.code;
    this.statusCode = options.statusCode || 500;
    this.details = options.details;
    this.isOperational = options.isOperational ?? true;
    this.timestamp = new Date();
    this.originalCause = options.cause;

    // Maintains proper stack trace
    Error.captureStackTrace?.(this, this.constructor);
  }

  toJSON() {
    const result: Record<string, unknown> = {
      success: false,
      error: {
        message: this.message,
        code: this.code,
        timestamp: this.timestamp.toISOString(),
      },
    };
    
    if (this.details) {
      (result.error as Record<string, unknown>).details = this.details;
    }
    
    return result;
  }

  toResponse(): NextResponse {
    return NextResponse.json(this.toJSON(), { status: this.statusCode });
  }
}

// ============================================
// SPECIALIZED ERROR CLASSES
// ============================================

export class ValidationError extends AppError {
  constructor(message: string, details?: unknown) {
    super({
      message,
      code: ErrorCodes.VALIDATION_ERROR,
      statusCode: 400,
      details,
    });
    this.name = 'ValidationError';
  }
}

export class NotFoundError extends AppError {
  constructor(resource: string, identifier?: string | Record<string, unknown>) {
    super({
      message: identifier
        ? `${resource} not found: ${typeof identifier === 'string' ? identifier : JSON.stringify(identifier)}`
        : `${resource} not found`,
      code: ErrorCodes.RESOURCE_NOT_FOUND,
      statusCode: 404,
      details: { resource, identifier },
    });
    this.name = 'NotFoundError';
  }
}

export class UnauthorizedError extends AppError {
  constructor(message = 'Authentication required') {
    super({
      message,
      code: ErrorCodes.AUTHENTICATION_REQUIRED,
      statusCode: 401,
    });
    this.name = 'UnauthorizedError';
  }
}

export class ForbiddenError extends AppError {
  constructor(message = 'Insufficient permissions', details?: unknown) {
    super({
      message,
      code: ErrorCodes.INSUFFICIENT_PERMISSIONS,
      statusCode: 403,
      details,
    });
    this.name = 'ForbiddenError';
  }
}

export class ConflictError extends AppError {
  constructor(message: string, details?: unknown) {
    super({
      message,
      code: ErrorCodes.RESOURCE_CONFLICT,
      statusCode: 409,
      details,
    });
    this.name = 'ConflictError';
  }
}

export class BusinessRuleError extends AppError {
  constructor(message: string, details?: unknown) {
    super({
      message,
      code: ErrorCodes.BUSINESS_RULE_VIOLATION,
      statusCode: 422,
      details,
    });
    this.name = 'BusinessRuleError';
  }
}

export class ExternalServiceError extends AppError {
  constructor(service: string, message: string, cause?: Error) {
    super({
      message: `External service error (${service}): ${message}`,
      code: ErrorCodes.EXTERNAL_SERVICE_ERROR,
      statusCode: 502,
      details: { service },
      cause,
    });
    this.name = 'ExternalServiceError';
  }
}

// ============================================
// ERROR HANDLING UTILITIES
// ============================================

/**
 * Wraps an async API handler with standardized error handling
 */
export function withErrorHandler<T>(
  handler: () => Promise<T>
): Promise<T | NextResponse> {
  return handler().catch((error: unknown) => {
    return handleError(error);
  });
}

/**
 * Converts any error to an appropriate AppError response
 */
export function handleError(error: unknown): NextResponse {
  // Already an AppError
  if (error instanceof AppError) {
    logError(error);
    return error.toResponse();
  }

  // Prisma errors
  if (isPrismaError(error)) {
    const appError = handlePrismaError(error);
    logError(appError);
    return appError.toResponse();
  }

  // Standard Error
  if (error instanceof Error) {
    const appError = new AppError({
      message: 'An unexpected error occurred',
      code: ErrorCodes.INTERNAL_ERROR,
      statusCode: 500,
      cause: error,
      isOperational: false,
    });
    logError(appError, error);
    return appError.toResponse();
  }

  // Unknown error
  const appError = new AppError({
    message: 'An unexpected error occurred',
    code: ErrorCodes.INTERNAL_ERROR,
    statusCode: 500,
    isOperational: false,
  });
  logError(appError);
  return appError.toResponse();
}

/**
 * Checks if an error is a Prisma error
 */
function isPrismaError(error: unknown): error is { code: string; meta?: unknown } {
  return (
    typeof error === 'object' &&
    error !== null &&
    'code' in error &&
    typeof (error as any).code === 'string' &&
    (error as any).code.startsWith('P')
  );
}

/**
 * Converts Prisma errors to AppErrors
 */
function handlePrismaError(error: { code: string; meta?: unknown }): AppError {
  switch (error.code) {
    case 'P2002': // Unique constraint violation
      return new ConflictError('A record with this value already exists', error.meta);
    case 'P2025': // Record not found
      return new NotFoundError('Record');
    case 'P2003': // Foreign key constraint
      return new ValidationError('Invalid reference to related record', error.meta);
    case 'P2016': // Query interpretation error
      return new ValidationError('Invalid query parameters', error.meta);
    default:
      return new AppError({
        message: 'Database operation failed',
        code: ErrorCodes.DATABASE_ERROR,
        statusCode: 500,
        details: { prismaCode: error.code },
      });
  }
}

/**
 * Logs an error with appropriate severity
 */
function logError(appError: AppError, originalError?: Error): void {
  const logData: Record<string, unknown> = {
    code: appError.code,
    message: appError.message,
    statusCode: appError.statusCode,
    details: appError.details,
    timestamp: appError.timestamp,
    stack: appError.stack,
  };
  
  if (originalError) {
    logData.originalStack = originalError.stack;
  }

  if (appError.isOperational) {
    console.warn('[AppError]', logData);
  } else {
    console.error('[AppError] Non-operational error:', logData);
  }
}

/**
 * Type guard to check if something is an AppError
 */
export function isAppError(error: unknown): error is AppError {
  return error instanceof AppError;
}

/**
 * Creates a successful JSON response
 */
export function successResponse<T>(data: T, statusCode = 200): NextResponse {
  return NextResponse.json({ success: true, ...data }, { status: statusCode });
}
