import {
  ArgumentsHost,
  Catch,
  ExceptionFilter,
  HttpException,
  HttpStatus,
  Logger,
} from "@nestjs/common";

interface ErrorResponseBody {
  success: false;
  statusCode: number;
  message: string;
  error?: string;
  timestamp: string;
  path: string;
}

interface PgDriverError {
  code?: string;
  detail?: string;
  column?: string;
  message?: string;
}

const toSentenceCase = (snake: string): string =>
  snake.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());

/**
 * Translate common Postgres constraint failures (surfaced by TypeORM as
 * QueryFailedError, which is NOT an HttpException and would otherwise fall
 * through as an opaque 500) into actionable 400s. Clients rely on the
 * "still referenced" wording to show friendly cannot-delete messages.
 */
const translatePgError = (
  driver: PgDriverError,
): { status: number; message: string } | null => {
  const detail = driver.detail ?? "";
  switch (driver.code) {
    case "23503": {
      const stillReferenced = detail.match(
        /is still referenced from table "(.*?)"/,
      );
      if (stillReferenced) {
        const table = toSentenceCase(stillReferenced[1]);
        return {
          status: HttpStatus.BAD_REQUEST,
          message: `Cannot delete: this record is still referenced by ${table}.`,
        };
      }
      const notPresent = detail.match(/is not present in table "(.*?)"/);
      const entity = toSentenceCase(notPresent?.[1] ?? "referenced entity");
      return {
        status: HttpStatus.BAD_REQUEST,
        message: `The specified ${entity} does not exist.`,
      };
    }
    case "23505": {
      const pair = detail.match(/\((.*?)\)=\((.*?)\)/);
      return {
        status: HttpStatus.BAD_REQUEST,
        message: `Duplicate entry: '${pair?.[2] ?? "value"}' already exists for '${toSentenceCase(pair?.[1] ?? "field")}'.`,
      };
    }
    case "23502":
      return {
        status: HttpStatus.BAD_REQUEST,
        message: `Missing required field: ${toSentenceCase(driver.column ?? "field")}.`,
      };
    case "22P02":
      return {
        status: HttpStatus.BAD_REQUEST,
        message: "Invalid input format (e.g., wrong UUID or number).",
      };
    default:
      return null;
  }
};

@Catch()
export class HttpExceptionFilter implements ExceptionFilter {
  private readonly logger = new Logger(HttpExceptionFilter.name);

  catch(exception: unknown, host: ArgumentsHost): void {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse();
    const request = ctx.getRequest();

    const pgTranslated =
      (exception as Error)?.name === "QueryFailedError"
        ? translatePgError(
            ((exception as { driverError?: PgDriverError }).driverError ??
              {}) as PgDriverError,
          )
        : null;

    const status = pgTranslated
      ? pgTranslated.status
      : exception instanceof HttpException
        ? exception.getStatus()
        : HttpStatus.INTERNAL_SERVER_ERROR;

    const messageRaw = pgTranslated
      ? pgTranslated.message
      : exception instanceof HttpException
        ? exception.getResponse()
        : "Internal server error";

    const message =
      typeof messageRaw === "string"
        ? messageRaw
        : ((messageRaw as { message?: string }).message ?? "Unexpected error");

    const body: ErrorResponseBody = {
      success: false,
      statusCode: status,
      message,
      error: exception instanceof Error ? exception.name : "Error",
      timestamp: new Date().toISOString(),
      path: request.url,
    };

    if (status >= 500) {
      this.logger.error(
        `${request.method} ${request.url} -> ${status}`,
        (exception as Error)?.stack,
      );
    } else {
      this.logger.warn(
        `${request.method} ${request.url} -> ${status} ${message}`,
      );
    }

    response.status(status).json(body);
  }
}
