import {
  Catch,
  ExceptionFilter,
  ArgumentsHost,
  HttpException,
  Logger,
} from "@nestjs/common";
import type { Request, Response } from "express";
import { ZodError } from "zod";
import { ApplicationError } from "./application-error.js";

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

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

    const traceIdHeader = request.headers["x-request-id"];
    const traceId =
      typeof traceIdHeader === "string" ? traceIdHeader : "unknown";

    let statusCode = 500;
    let body: Record<string, unknown>;

    if (exception instanceof ApplicationError) {
      exception.traceId = traceId;
      statusCode = exception.statusCode;
      body = exception.toJSON();
    } else if (exception instanceof ZodError) {
      statusCode = 400;
      body = {
        success: false,
        error: {
          code: "MSG_VALIDATION_ERROR",
          message: "Validation failed",
          details: exception.flatten(),
          traceId,
        },
      };
    } else if (exception instanceof HttpException) {
      statusCode = exception.getStatus();
      const res = exception.getResponse();
      const message = this.extractHttpMessage(res, exception);
      body = {
        success: false,
        error: {
          code: "HTTP_ERROR",
          message,
          traceId,
        },
      };
    } else {
      this.logger.error(
        `Unhandled exception: ${exception}`,
        exception instanceof Error ? exception.stack : undefined,
      );
      body = {
        success: false,
        error: {
          code: "INTERNAL_ERROR",
          message: "An unexpected error occurred",
          traceId,
        },
      };
    }

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

  private extractHttpMessage(
    res: string | object,
    exception: HttpException,
  ): string {
    if (typeof res === "string") {
      return res;
    }
    if (res && typeof res === "object" && "message" in res) {
      const msg = (res as { message: unknown }).message;
      return typeof msg === "string" ? msg : exception.message;
    }
    return exception.message;
  }
}
