import type { Request, Response, NextFunction } from 'express';
import type { JsonWebTokenError, TokenExpiredError } from 'jsonwebtoken';
import { ZodError } from 'zod';
import { NODE_ENV } from '@config/env';
import { HttpException } from '@exceptions/http.exception';
import {
  type StandardErrorResponse,
  type ValidationErrorDetail,
  HTTP_ERROR_MESSAGES,
} from '@interfaces/error.interface';
import { logger } from '@utils/logger';

type HttpExceptionWithData = HttpException & { data?: unknown };
type WithStack = { stack?: string };

/** 타입가드들 */
const isZodError = (e: unknown): e is ZodError => {
  return e instanceof ZodError;
};

/** jsonwebtoken은 런타임에 따라 클래스 경계 이슈가 있을 수 있어 name 기반 가드 권장 */
const isTokenExpiredError = (e: unknown): e is TokenExpiredError => {
  return e instanceof Error && (e as { name?: string }).name === 'TokenExpiredError';
};
const isJsonWebTokenError = (e: unknown): e is JsonWebTokenError => {
  return e instanceof Error && (e as { name?: string }).name === 'JsonWebTokenError';
};

const toHttpException = (err: unknown): HttpException => {
  if (err instanceof HttpException) return err;

  if (isZodError(err)) {
    const details: ValidationErrorDetail[] = err.issues.map((issue) => ({
      field: issue.path.join('.'),
      message: issue.message,
      value: (issue as { received?: unknown }).received || undefined,
    }));
    return new HttpException(400, 'Validation failed', details);
  }

  if (isTokenExpiredError(err)) return new HttpException(401, 'Token expired');
  if (isJsonWebTokenError(err)) return new HttpException(401, 'Invalid token');

  const e = err as Error | undefined;
  return new HttpException(500, e?.message || 'Internal Server Error');
};

const extractStack = (err: unknown): string | undefined => {
  if (err && typeof err === 'object' && 'stack' in err) {
    const s = (err as WithStack).stack;
    return typeof s === 'string' ? s : undefined;
  }
  return undefined;
};

export const ErrorMiddleware = (
  error: unknown,
  req: Request,
  res: Response,
  _next: NextFunction,
) => {
  const httpErr = toHttpException(error);
  const status = httpErr.status || 500;
  const message =
    httpErr.message ||
    HTTP_ERROR_MESSAGES[status as keyof typeof HTTP_ERROR_MESSAGES] ||
    'Something went wrong';

  if (res.headersSent) return _next(httpErr);

  const stack = extractStack(httpErr);
  logger.error(
    `[${req.method}] ${req.originalUrl} | ${status} | ${message}${stack ? `\n${stack}` : ''}`,
  );

  // 표준 에러 응답 형식
  const errorResponse: StandardErrorResponse = {
    success: false,
    error: {
      code: status,
      message,
      timestamp: new Date().toISOString(),
      path: req.originalUrl,
    },
  };

  // 추가 세부 정보가 있으면 포함
  const maybeDetails = (httpErr as HttpExceptionWithData).data;
  if (typeof maybeDetails !== 'undefined') {
    errorResponse.error.details = maybeDetails;
  }

  // 개발 환경에서만 스택 트레이스 포함
  if (NODE_ENV === 'development' && stack) {
    errorResponse.error.details = {
      ...(errorResponse.error.details || {}),
      stack,
    };
  }

  res.status(status).json(errorResponse);
};
