/**
 * HANA Query API Client - Error Classes
 * 
 * Custom error classes for different types of client errors.
 */

import type { 
  ClientErrorType, 
  ClientErrorDetails, 
  ProblemDetails, 
  RequestContext 
} from './types.ts';

/**
 * Base client error class
 */
export class HanaQueryClientError extends Error {
  public readonly type: ClientErrorType;
  public readonly statusCode?: number;
  public readonly originalError?: Error;
  public readonly context?: RequestContext;
  public readonly problemDetails?: ProblemDetails;

  constructor(details: ClientErrorDetails) {
    super(details.message);
    
    this.name = 'HanaQueryClientError';
    this.type = details.type;
    this.statusCode = details.statusCode;
    this.originalError = details.originalError;
    this.context = details.context;
    this.problemDetails = details.problemDetails;

    // Maintains proper stack trace for where our error was thrown (only available on V8)
    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, HanaQueryClientError);
    }
  }

  /**
   * Convert error to JSON for logging/serialization
   */
  toJSON(): Record<string, any> {
    return {
      name: this.name,
      message: this.message,
      type: this.type,
      statusCode: this.statusCode,
      context: this.context,
      problemDetails: this.problemDetails,
      stack: this.stack
    };
  }

  /**
   * Get a human-readable description of the error
   */
  getDescription(): string {
    const parts: string[] = [this.message];

    if (this.statusCode) {
      parts.push(`(HTTP ${this.statusCode})`);
    }

    if (this.context) {
      const duration = this.context.duration ? `${this.context.duration}ms` : 'unknown';
      parts.push(`[${this.context.method} ${this.context.url}] - ${duration}`);
    }

    return parts.join(' ');
  }
}

/**
 * Network-related errors (connection failures, timeouts, etc.)
 */
export class NetworkError extends HanaQueryClientError {
  constructor(message: string, originalError?: Error, context?: RequestContext) {
    super({
      type: 'network_error',
      message: `Network error: ${message}`,
      originalError,
      context
    });
    this.name = 'NetworkError';
  }
}

/**
 * Request timeout errors
 */
export class TimeoutError extends HanaQueryClientError {
  constructor(timeout: number, context?: RequestContext) {
    super({
      type: 'timeout_error',
      message: `Request timed out after ${timeout}ms`,
      context
    });
    this.name = 'TimeoutError';
  }
}

/**
 * Validation errors (invalid parameters, etc.)
 */
export class ValidationError extends HanaQueryClientError {
  constructor(message: string, issues?: string[], context?: RequestContext) {
    const fullMessage = issues && issues.length > 0
      ? `${message}: ${issues.join(', ')}`
      : message;

    super({
      type: 'validation_error',
      message: `Validation error: ${fullMessage}`,
      statusCode: 400,
      context
    });
    this.name = 'ValidationError';
  }
}

/**
 * Authorization errors (401, 403)
 */
export class AuthorizationError extends HanaQueryClientError {
  constructor(message: string, statusCode: number, context?: RequestContext) {
    super({
      type: 'authorization_error',
      message: `Authorization error: ${message}`,
      statusCode,
      context
    });
    this.name = 'AuthorizationError';
  }
}

/**
 * Not found errors (404)
 */
export class NotFoundError extends HanaQueryClientError {
  constructor(resource: string, context?: RequestContext) {
    super({
      type: 'not_found_error',
      message: `Resource not found: ${resource}`,
      statusCode: 404,
      context
    });
    this.name = 'NotFoundError';
  }
}

/**
 * Server errors (5xx)
 */
export class ServerError extends HanaQueryClientError {
  constructor(message: string, statusCode: number, problemDetails?: ProblemDetails, context?: RequestContext) {
    super({
      type: 'server_error',
      message: `Server error: ${message}`,
      statusCode,
      problemDetails,
      context
    });
    this.name = 'ServerError';
  }
}

/**
 * Unknown/unexpected errors
 */
export class UnknownError extends HanaQueryClientError {
  constructor(message: string, originalError?: Error, context?: RequestContext) {
    super({
      type: 'unknown_error',
      message: `Unknown error: ${message}`,
      originalError,
      context
    });
    this.name = 'UnknownError';
  }
}

/**
 * Create appropriate error from HTTP response
 */
export function createErrorFromResponse(
  response: Response,
  responseData: any,
  context?: RequestContext
): HanaQueryClientError {
  const { status, statusText } = response;

  // Handle API error responses with problem details
  if (responseData && typeof responseData === 'object' && responseData.problem) {
    const problem = responseData.problem as ProblemDetails;
    
    switch (status) {
      case 400:
        return new ValidationError(
          problem.detail || 'Bad request',
          problem.issues,
          context
        );
      
      case 401:
      case 403:
        return new AuthorizationError(
          problem.detail || statusText || 'Unauthorized',
          status,
          context
        );
      
      case 404:
        return new NotFoundError(
          problem.detail || statusText || 'Not found',
          context
        );
      
      case 500:
      case 502:
      case 503:
      case 504:
        return new ServerError(
          problem.detail || statusText || 'Server error',
          status,
          problem,
          context
        );
      
      default:
        return new UnknownError(
          problem.detail || `HTTP ${status}: ${statusText}`,
          undefined,
          context
        );
    }
  }

  // Handle HTTP errors without API problem details
  switch (status) {
    case 400:
      return new ValidationError('Bad request', undefined, context);
    
    case 401:
    case 403:
      return new AuthorizationError(
        statusText || 'Unauthorized',
        status,
        context
      );
    
    case 404:
      return new NotFoundError(statusText || 'Not found', context);
    
    case 500:
    case 502:
    case 503:
    case 504:
      return new ServerError(
        statusText || 'Server error',
        status,
        undefined,
        context
      );
    
    default:
      return new UnknownError(
        `HTTP ${status}: ${statusText}`,
        undefined,
        context
      );
  }
}

/**
 * Create error from network/fetch failure
 */
export function createNetworkError(
  error: Error,
  context?: RequestContext
): HanaQueryClientError {
  const message = error.message.toLowerCase();

  // Check for timeout
  if (message.includes('timeout') || error.name === 'TimeoutError') {
    return new TimeoutError(context?.duration || 0, context);
  }

  // Check for network issues
  if (
    message.includes('network') ||
    message.includes('fetch') ||
    message.includes('connection') ||
    error.name === 'TypeError' ||
    error.name === 'AbortError'
  ) {
    return new NetworkError(error.message, error, context);
  }

  // Unknown error
  return new UnknownError(error.message, error, context);
}

/**
 * Check if an error is retryable
 */
export function isRetryableError(error: HanaQueryClientError): boolean {
  switch (error.type) {
    case 'network_error':
    case 'timeout_error':
      return true;
    
    case 'server_error':
      // Retry 5xx errors except for 501 (Not Implemented)
      return error.statusCode !== 501;
    
    case 'authorization_error':
    case 'validation_error':
    case 'not_found_error':
      return false;
    
    case 'unknown_error':
    default:
      return false;
  }
}

/**
 * Get retry delay for an error (exponential backoff)
 */
export function getRetryDelay(
  attempt: number,
  baseDelay: number,
  maxDelay: number,
  backoffMultiplier: number = 2
): number {
  const delay = baseDelay * Math.pow(backoffMultiplier, attempt - 1);
  return Math.min(delay, maxDelay);
}

/**
 * Type guards for specific error types
 */
export function isNetworkError(error: any): error is NetworkError {
  return error instanceof NetworkError || error?.type === 'network_error';
}

export function isTimeoutError(error: any): error is TimeoutError {
  return error instanceof TimeoutError || error?.type === 'timeout_error';
}

export function isValidationError(error: any): error is ValidationError {
  return error instanceof ValidationError || error?.type === 'validation_error';
}

export function isAuthorizationError(error: any): error is AuthorizationError {
  return error instanceof AuthorizationError || error?.type === 'authorization_error';
}

export function isNotFoundError(error: any): error is NotFoundError {
  return error instanceof NotFoundError || error?.type === 'not_found_error';
}

export function isServerError(error: any): error is ServerError {
  return error instanceof ServerError || error?.type === 'server_error';
}

export function isHanaQueryClientError(error: any): error is HanaQueryClientError {
  return error instanceof HanaQueryClientError || 
         (error && typeof error.type === 'string' && error.type.endsWith('_error'));
}