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

// RFC 9457 Problem Details for HTTP APIs.
// CODING-RULES §05: Zod sempre 422 (nunca 400). 403 vira 404 onde o cliente
// não pode saber da existência (regra aplicada por guards de domínio — aqui
// só repassamos o status que o caller escolheu).

const PROBLEM_CONTENT_TYPE = 'application/problem+json';
const BASE_TYPE_URI = 'https://docs.sar.jcs.com.br/errors';

interface ProblemDetails {
  type: string;
  title: string;
  status: number;
  detail?: string;
  instance: string;
  requestId?: string;
  errors?: ReadonlyArray<{ path: string; message: string; code?: string }>;
  [key: string]: unknown;
}

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

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

    const problem = this.toProblem(exception, req);

    if (problem.status >= 500) {
      this.logger.error(
        { err: exception, requestId: problem.requestId, path: problem.instance },
        problem.title,
      );
    }

    res.setHeader('Content-Type', PROBLEM_CONTENT_TYPE);
    res.status(problem.status).json(problem);
  }

  private toProblem(exception: unknown, req: Request): ProblemDetails {
    const requestId = this.extractRequestId(req);
    const instance = req.originalUrl ?? req.url ?? '/';

    // Zod direto (validação manual ou rethrow de pipe sem nestjs-zod).
    if (exception instanceof ZodError) {
      return this.zodProblem(exception, instance, requestId);
    }

    if (exception instanceof HttpException) {
      return this.httpProblem(exception, instance, requestId);
    }

    return {
      type: `${BASE_TYPE_URI}/internal-server-error`,
      title: 'Internal Server Error',
      status: HttpStatus.INTERNAL_SERVER_ERROR,
      detail: 'Erro interno. Reporte ao suporte com o requestId.',
      instance,
      requestId,
    };
  }

  private zodProblem(err: ZodError, instance: string, requestId?: string): ProblemDetails {
    return {
      type: `${BASE_TYPE_URI}/validation`,
      title: 'Unprocessable Entity',
      status: HttpStatus.UNPROCESSABLE_ENTITY,
      detail: 'Validação dos dados de entrada falhou.',
      instance,
      requestId,
      errors: err.issues.map((i) => ({
        path: i.path.join('.'),
        message: i.message,
        code: i.code,
      })),
    };
  }

  private httpProblem(err: HttpException, instance: string, requestId?: string): ProblemDetails {
    const status = err.getStatus();
    const response = err.getResponse();

    // nestjs-zod lança ZodValidationException (HttpException 400/422) com payload já estruturado.
    if (typeof response === 'object' && response !== null) {
      const r = response as Record<string, unknown>;
      // nestjs-zod 5.x: { statusCode, message, errors: ZodIssue[] }
      if (Array.isArray(r['errors']) && r['errors'].length > 0) {
        const issues = r['errors'] as Array<{ path?: unknown; message?: unknown; code?: unknown }>;
        return {
          type: `${BASE_TYPE_URI}/validation`,
          title: 'Unprocessable Entity',
          status: HttpStatus.UNPROCESSABLE_ENTITY,
          detail: typeof r['message'] === 'string' ? (r['message'] as string) : 'Validação falhou.',
          instance,
          requestId,
          errors: issues.map((i) => ({
            path: Array.isArray(i.path) ? i.path.join('.') : String(i.path ?? ''),
            message: String(i.message ?? ''),
            code: typeof i.code === 'string' ? i.code : undefined,
          })),
        };
      }

      const title = this.titleFor(status);
      return {
        type: `${BASE_TYPE_URI}/${this.slug(title)}`,
        title,
        status,
        detail: typeof r['message'] === 'string' ? (r['message'] as string) : title,
        instance,
        requestId,
      };
    }

    const title = this.titleFor(status);
    return {
      type: `${BASE_TYPE_URI}/${this.slug(title)}`,
      title,
      status,
      detail: typeof response === 'string' ? response : title,
      instance,
      requestId,
    };
  }

  private extractRequestId(req: Request): string | undefined {
    const fromHeader = req.headers['x-request-id'];
    if (typeof fromHeader === 'string') return fromHeader;
    const reqId = (req as Request & { id?: unknown }).id;
    return typeof reqId === 'string' ? reqId : undefined;
  }

  private titleFor(status: number): string {
    switch (status) {
      case 400:
        return 'Bad Request';
      case 401:
        return 'Unauthorized';
      case 403:
        return 'Forbidden';
      case 404:
        return 'Not Found';
      case 409:
        return 'Conflict';
      case 422:
        return 'Unprocessable Entity';
      case 429:
        return 'Too Many Requests';
      default:
        return status >= 500 ? 'Internal Server Error' : 'Error';
    }
  }

  private slug(title: string): string {
    return title.toLowerCase().replace(/\s+/g, '-');
  }
}
