import { useCallback } from "react";
import { toast } from "sonner";
import axios from "axios";

// Logs an error with a flat one-liner header (method/url/status) when it's an axios error,
// then the full error object on the next line for DevTools expansion.
const logError = (err: unknown): void => {
  if (axios.isAxiosError(err)) {
    const method = err.config?.method?.toUpperCase() ?? "?";
    const url = err.config?.url ?? "?";
    const status = err.response?.status ?? "no-response";
    console.error(
      `[API ERROR] ${method} ${url} → ${status}`,
      err.response?.data,
    );
    return;
  }
  console.error("Error caught:", err);
};

// Returns the first non-empty string found in value (string, array, or nested object with message/error/detail). Never throws.
const extractMessage = (value: unknown): string | null => {
  try {
    if (value == null) return null;

    if (typeof value === "string") {
      const trimmed = value.trim();
      return trimmed.length > 0 ? trimmed : null;
    }

    if (Array.isArray(value)) {
      const parts: string[] = [];
      for (const item of value) {
        const part = extractMessage(item);
        if (part) parts.push(part);
      }
      return parts.length > 0 ? parts.join(", ") : null;
    }

    if (typeof value === "object") {
      const obj = value as Record<string, unknown>;
      return (
        extractMessage(obj.message) ??
        extractMessage(obj.error) ??
        extractMessage(obj.detail) ??
        null
      );
    }

    return null;
  } catch (extractError) {
    console.warn("extractMessage: unexpected failure", extractError);
    return null;
  }
};

// Maps an HTTP status code to the i18n key used when no backend message is available.
const statusKeyFor = (status: number | undefined): string => {
  if (status === 400 || status === 422) return "msg.validationError";
  if (status === 401) return "msg.authError";
  if (status === 403) return "msg.permissionError";
  if (status === 404) return "msg.notFoundError";
  if (status === 413) return "msg.fileTooLarge";
  if (status !== undefined && status >= 500 && status < 600) return "msg.serverError";
  return "msg.genericError";
};

// Detects the bulk-upload structured error shape returned by the backend:
//   { message: "duplicate_values_found", errors: { fieldName: "code: value" } }
// Returns { title, description } when matched, null otherwise. Description is
// one "field: value" pair per line, suitable for a Sonner toast `description`.
const extractBulkUploadInfo = (
  data: unknown,
  t: (key: string) => string,
): { title: string; description: string } | null => {
  if (!data || typeof data !== "object") return null;
  const d = data as { message?: unknown; errors?: unknown };
  if (d.message !== "duplicate_values_found") return null;
  if (!d.errors || typeof d.errors !== "object") return null;

  const lines: string[] = [];
  for (const [field, value] of Object.entries(
    d.errors as Record<string, unknown>,
  )) {
    if (typeof value === "string" && value.length > 0) {
      lines.push(`${field}: ${value}`);
    }
  }
  if (lines.length === 0) return null;

  // If `msg.duplicateValuesFound` isn't in translation files yet, t() returns
  // the raw key. Detect that and fall back to a readable English string.
  const translatedTitle = t("msg.duplicateValuesFound");
  const title =
    translatedTitle === "msg.duplicateValuesFound"
      ? "Duplicate values found"
      : translatedTitle;

  return { title, description: lines.join("\n") };
};

// Reads a Blob as JSON. Returns null on any failure.
const parseBlobBody = async (blob: Blob): Promise<unknown> => {
  try {
    const text = await blob.text();
    return JSON.parse(text);
  } catch (parseError) {
    console.warn("useErrorHandler: failed to parse Blob error body", parseError);
    return null;
  }
};

export const useErrorHandler = (
  t: (key: string) => string,
) => {
  const getErrorMessage = useCallback(
    async (err: unknown): Promise<string> => {
      try {
        let status: number | undefined;

        if (typeof err === "object" && err !== null && "response" in err) {
          const response = (err as any).response;
          status = response?.status;
          let data: unknown = response?.data;

          if (data instanceof Blob) {
            data = await parseBlobBody(data);
          }

          // Bulk-upload structured error short-circuit (highest priority).
          const bulk = extractBulkUploadInfo(data, t);
          if (bulk) return bulk.title;

          const fromException =
            extractMessage((data as any)?.exception?.response) ??
            extractMessage((data as any)?.exception);
          if (fromException) return fromException;

          const fromData = extractMessage(data);
          if (fromData) return fromData;
        }

        if (err instanceof Error) {
          const fromError = extractMessage(err.message);
          if (fromError) return fromError;
        }

        return t(statusKeyFor(status));
      } catch (handlerError) {
        console.error(
          "useErrorHandler: unexpected error while extracting message",
          handlerError
        );
        return t("msg.genericError");
      }
    },
    [t]
  );

  const handleError = useCallback(
    async (err: unknown): Promise<string> => {
      logError(err);

      // Bulk-upload short-circuit: emit one toast with title + description,
      // skipping the normal extraction path.
      if (typeof err === "object" && err !== null && "response" in err) {
        let data: unknown = (err as any).response?.data;
        if (data instanceof Blob) {
          data = await parseBlobBody(data);
        }
        const bulk = extractBulkUploadInfo(data, t);
        if (bulk) {
          toast.error(bulk.title, { description: bulk.description });
          return bulk.title;
        }
      }

      const message = await getErrorMessage(err);
      toast.error(message);
      return message;
    },
    [getErrorMessage, t]
  );

  return { getErrorMessage, handleError };
};

export const useClientErrorHandler = (
  t: (key: string) => string,
) => {
  const getErrorMessage = useCallback(
    (err: unknown): string => {
      try {
        let status: number | undefined;

        if (typeof err === "object" && err !== null) {
          const response = (err as any).response;
          status = response?.status;
          const data = response?.data;

          // Bulk-upload structured error short-circuit (highest priority).
          const bulk = extractBulkUploadInfo(data, t);
          if (bulk) return bulk.title;

          const fromException =
            extractMessage(data?.exception?.response) ??
            extractMessage(data?.exception);
          if (fromException) return fromException;

          const fromData = extractMessage(data);
          if (fromData) return fromData;

          const fromError = extractMessage((err as any).message);
          if (fromError) return fromError;
        }

        return t(statusKeyFor(status));
      } catch (handlerError) {
        console.error(
          "useClientErrorHandler: unexpected error while extracting message",
          handlerError
        );
        return t("msg.genericError");
      }
    },
    [t]
  );

  const handleError = useCallback(
    (err: unknown): string => {
      logError(err);

      // Bulk-upload short-circuit: emit one toast with title + description,
      // skipping the normal extraction path.
      if (typeof err === "object" && err !== null) {
        const data = (err as any).response?.data;
        const bulk = extractBulkUploadInfo(data, t);
        if (bulk) {
          toast.error(bulk.title, { description: bulk.description });
          return bulk.title;
        }
      }

      const message = getErrorMessage(err);
      toast.error(message);
      return message;
    },
    [getErrorMessage, t]
  );

  return { getErrorMessage, handleError };
};
