import {
  ArgumentsHost,
  BadRequestException,
  Catch,
  ConflictException,
  InternalServerErrorException,
  Logger,
  PreconditionFailedException,
} from '@nestjs/common';
import { BaseExceptionFilter } from '@nestjs/core';
import {
  EntityMetadataNotFoundError,
  EntityNotFoundError,
  OptimisticLockCanNotBeUsedError,
  QueryFailedError,
} from 'typeorm';

import * as _ from 'lodash';
import { TypeORMError } from 'typeorm/error/TypeORMError';
import { PgExceptionCodes, PgExceptionPrefixes } from './pg-exception-codes';

/**
 * DatabaseExceptionFilter - Global exception filter for database-related errors
 *
 * This filter catches TypeORM database exceptions and maps them to appropriate
 * HTTP exceptions with proper status codes and user-friendly error messages.
 * It extends NestJS's BaseExceptionFilter to maintain consistent error handling.
 *
 * Features:
 * - Maps database errors to appropriate HTTP status codes
 * - Provides user-friendly error messages while logging technical details
 * - Handles PostgreSQL-specific error codes
 * - Maintains security by not exposing database internals to clients
 *
 * @example
 * // Register globally in main.ts
 * app.useGlobalFilters(new DatabaseExceptionFilter());
 *
 * @example
 * // Error mappings:
 * // EntityNotFoundError → 400 Bad Request
 * // UniqueViolation → 400 Bad Request
 * // ForeignKeyViolation → 409 Conflict
 * // All other DB errors → 500 Internal Server Error
 */
@Catch(TypeORMError)
export class DatabaseExceptionFilter extends BaseExceptionFilter {
  private readonly logger = new Logger(DatabaseExceptionFilter.name);

  /**
   * Catches TypeORM exceptions and maps them to HTTP exceptions
   *
   * Analyzes database exceptions and converts them to appropriate
   * HTTP exceptions with meaningful status codes.
   *
   * @param exception - The caught TypeORM error
   * @param host - ArgumentsHost for accessing request/response
   * @returns void (but calls super.catch which sends HTTP response)
   *
   * @note Logs technical details but returns user-friendly errors
   * @note Maintains chain of responsibility with BaseExceptionFilter
   */
  catch(exception: Error, host: ArgumentsHost): any {
    // Log the full technical error for debugging
    this.logger.error(
      `Database exception: ${exception.message}`,
      exception.stack,
    );

    // 1. Entity not found in database
    if (exception instanceof EntityNotFoundError) {
      return super.catch(
        new BadRequestException('Requested resource not found'),
        host,
      );
    }

    // 2. Optimistic locking conflict (stale data update)
    if (exception instanceof OptimisticLockCanNotBeUsedError) {
      return super.catch(
        new PreconditionFailedException(
          'Resource was modified by another user. Please refresh and try again.',
        ),
        host,
      );
    }

    // 3. Entity metadata not found (configuration error)
    if (exception instanceof EntityMetadataNotFoundError) {
      return super.catch(
        new InternalServerErrorException('System configuration error'),
        host,
      );
    }

    // 4. Query execution errors (PostgreSQL-specific)
    if (exception instanceof QueryFailedError) {
      const errorCode = exception.driverError.code as string;

      // Handle data-related errors (invalid input, type mismatches)
      if (errorCode && errorCode.includes(PgExceptionPrefixes.DATA_ERROR)) {
        return super.catch(
          new BadRequestException('Invalid data provided'),
          host,
        );
      }

      // Map specific PostgreSQL error codes to HTTP exceptions
      switch (errorCode) {
        case PgExceptionCodes.NOT_NULL_VIOLATION:
          return super.catch(
            new BadRequestException('Required field cannot be empty'),
            host,
          );

        case PgExceptionCodes.UNIQUE_VIOLATION:
          return super.catch(
            new BadRequestException('Duplicate entry not allowed'),
            host,
          );

        case PgExceptionCodes.FOREIGN_CONSTRAINT_VIOLATION:
        case PgExceptionCodes.INTEGRITY_CONSTRAINT_VIOLATION:
          return super.catch(
            new ConflictException('Referenced resource does not exist'),
            host,
          );

        default:
          if (errorCode) {
            // Unhandled database error - treat as internal server error
            return super.catch(
              new InternalServerErrorException('Database operation failed'),
              host,
            );
          }
      }
    }

    // 5. Generic TypeORM errors
    if (exception instanceof TypeORMError) {
      return super.catch(
        new InternalServerErrorException('Database operation failed'),
        host,
      );
    }

    // 6. Any other errors (fallback to base handling)
    return super.catch(exception, host);
  }
}
