import { Request, Response, NextFunction } from "express";
import { Prisma } from "@prisma/client";
import { ZodError } from "zod";
import { AppError } from "../errors/app-error";
import { sendError } from "../utils/api-response";
import { logger } from "../utils/logger";
import { env } from "../../config/env";
import { formatZodError } from "../utils/zod-error-formatter";

export function errorMiddleware(
  err: unknown,
  req: Request,
  res: Response,
  _next: NextFunction,
) {
  (res as any).err = err;
  // 1. AppError (Operational / Custom HttpErrors)
  if (err instanceof AppError) {
    return sendError(
      res,
      err.message,
      err.statusCode,
      err.errorCode,
      err.details,
    );
  }

  // 2. Shape thrown from validate.middleware.ts ({ statusCode, message, details })
  if (
    typeof err === "object" &&
    err !== null &&
    "statusCode" in err &&
    "message" in err
  ) {
    const e = err as {
      statusCode: number;
      message: string;
      details?: unknown;
      errorCode?: string;
    };
    return sendError(
      res,
      e.message,
      e.statusCode,
      e.errorCode || "VALIDATION_ERROR",
      e.details,
    );
  }

  // 3. ZodError direct thrown anywhere
  if (err instanceof ZodError) {
    return sendError(
      res,
      "Validation failed",
      400,
      "VALIDATION_ERROR",
      formatZodError(err),
    );
  }

  // 4. Prisma Known Database Errors
  if (err instanceof Prisma.PrismaClientKnownRequestError) {
    // P2002: Unique constraint failed
    if (err.code === "P2002") {
      let fields: string[] = [];

      // 1. Standard Prisma (PostgreSQL/SQLite): meta.target = ["phone"]
      if (Array.isArray(err.meta?.target) && (err.meta.target as string[]).length > 0) {
        fields = err.meta.target as string[];
      }

      // 2. MariaDB driver adapter: parse from meta.driverAdapterError (nested)
      if (fields.length === 0) {
        const metaAny = err.meta as any;
        const index: string =
          metaAny?.driverAdapterError?.cause?.constraint?.index ||
          metaAny?.cause?.constraint?.index ||
          "";
        const match = index.match(/^(?:\w+_)?(.+?)_(?:key|unique)$/i);
        if (match) fields = [match[1]];
      }

      // 3. Last resort: parse field name from the error message string
      if (fields.length === 0) {
        const msgMatch = err.message.match(/constraint[`\s'"]+([\w]+_(?:phone|email|name)[\w]*)_key/i);
        if (msgMatch) {
          const colMatch = msgMatch[1].match(/_(phone|email|name)$/);
          if (colMatch) fields = [colMatch[1]];
        }
        // Even simpler: scan message for known unique field names
        if (fields.length === 0) {
          const lower = err.message.toLowerCase();
          if (lower.includes("_phone_") || lower.includes("phone_key")) fields = ["phone"];
          else if (lower.includes("_email_") || lower.includes("email_key")) fields = ["email"];
        }
      }

      const fieldErrors: Record<string, string> = {};
      fields.forEach((f) => {
        fieldErrors[f] = `${f.charAt(0).toUpperCase() + f.slice(1)} is already registered.`;
      });

      const message =
        fields.length > 0
          ? `${fields.map((f) => f.charAt(0).toUpperCase() + f.slice(1)).join(", ")} is already registered.`
          : "A record with this value already exists.";

      return sendError(res, message, 409, "CONFLICT",
        Object.keys(fieldErrors).length > 0 ? { fieldErrors } : undefined
      );
    }
    // P2025: Record not found
    if (err.code === "P2025") {
      return sendError(res, "Requested resource not found.", 404, "NOT_FOUND");
    }
    // P2003: Foreign key constraint failed
    if (err.code === "P2003") {
      return sendError(
        res,
        "Related record does not exist or cannot be referenced.",
        400,
        "FOREIGN_KEY_VIOLATION",
        { field: err.meta?.field_name },
      );
    }
    // Fallback for other Prisma errors
    logger.error(
      { err, path: req.path, code: err.code },
      "Prisma database error",
    );
    return sendError(res, "Database operation failed.", 400, "DATABASE_ERROR");
  }

  // 5. Prisma Validation Error
  if (err instanceof Prisma.PrismaClientValidationError) {
    logger.warn({ err, path: req.path }, "Prisma validation error");
    return sendError(
      res,
      "Invalid database request structure or parameter type.",
      400,
      "VALIDATION_ERROR",
    );
  }

  // 6. Express Malformed JSON / SyntaxError from express.json()
  if (err instanceof SyntaxError && "body" in err) {
    return sendError(
      res,
      "Malformed JSON payload in request body.",
      400,
      "MALFORMED_JSON",
    );
  }

  // 7. JWT Errors from jsonwebtoken
  if (typeof err === "object" && err !== null && "name" in err) {
    const e = err as { name: string; message: string };
    if (e.name === "TokenExpiredError") {
      return sendError(res, "Session token has expired.", 401, "TOKEN_EXPIRED");
    }
    if (e.name === "JsonWebTokenError") {
      return sendError(
        res,
        "Invalid authentication token.",
        401,
        "INVALID_TOKEN",
      );
    }
  }

  // 8. Unhandled / Fatal Error
  const isProd = env.NODE_ENV === "production";
  logger.error(
    { err, path: req.path, method: req.method },
    "Unhandled internal server error",
  );

  const message = isProd
    ? "Internal server error"
    : err instanceof Error
      ? err.message
      : "Unknown error";
  const details =
    !isProd && err instanceof Error ? { stack: err.stack } : undefined;

  return sendError(res, message, 500, "INTERNAL_SERVER_ERROR", details);
}
