import {
  ArgumentsHost,
  Catch,
  ExceptionFilter,
  HttpException,
  HttpStatus,
  Logger,
} from '@nestjs/common';
import { Request, Response } from 'express';
import { Prisma } from '@prisma/client';
import { ZodError } from 'zod';

interface ErrorResponse {
  statusCode: number;
  error: string;
  message: string;
  details?: unknown;
  path: string;
  timestamp: string;
}

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

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

    const body = this.toErrorResponse(exception, request.url);

    if (body.statusCode >= 500) {
      this.logger.error(
        `${body.error}: ${body.message}`,
        exception instanceof Error ? exception.stack : undefined,
      );
    } else {
      this.logger.warn(`${body.error}: ${body.message} (${request.method} ${request.url})`);
    }

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

  private toErrorResponse(exception: unknown, path: string): ErrorResponse {
    const timestamp = new Date().toISOString();

    // ---- Zod validation ----
    if (exception instanceof ZodError) {
      return {
        statusCode: HttpStatus.BAD_REQUEST,
        error: 'ValidationError',
        message: 'Invalid input',
        ...(process.env.NODE_ENV !== 'production' && { details: exception.errors }),
        path,
        timestamp,
      };
    }

    // ---- HttpException (NestJS) ----
    if (exception instanceof HttpException) {
      const status = exception.getStatus();
      const res = exception.getResponse();
      if (typeof res === 'string') {
        return {
          statusCode: status,
          error: exception.name,
          message: res,
          path,
          timestamp,
        };
      }
      const obj = res as { message?: string | string[]; error?: string };
      return {
        statusCode: status,
        error: obj.error ?? exception.name,
        message: Array.isArray(obj.message) ? obj.message.join('; ') : obj.message ?? exception.message,
        details: obj,
        path,
        timestamp,
      };
    }

    // ---- Prisma known errors ----
    if (exception instanceof Prisma.PrismaClientKnownRequestError) {
      const map: Record<string, { status: number; msg: string }> = {
        P2002: { status: HttpStatus.CONFLICT, msg: 'A record with this value already exists' },
        P2003: { status: HttpStatus.BAD_REQUEST, msg: 'Foreign key constraint failed' },
        P2025: { status: HttpStatus.NOT_FOUND, msg: 'Record not found' },
      };
      const m = map[exception.code] ?? {
        status: HttpStatus.BAD_REQUEST,
        msg: 'Database error',
      };
      return {
        statusCode: m.status,
        error: 'DatabaseError',
        message: m.msg,
        ...(process.env.NODE_ENV !== 'production' && { details: { code: exception.code, meta: exception.meta } }),
        path,
        timestamp,
      };
    }

    // ---- Fallback ----
    const message = exception instanceof Error ? exception.message : 'Unknown error';
    return {
      statusCode: HttpStatus.INTERNAL_SERVER_ERROR,
      error: 'InternalServerError',
      message,
      path,
      timestamp,
    };
  }
}
