import {
  clearAuthSession,
  readAuthSession,
  setAuthSession,
} from "./auth-storage";
import { getApiUrl } from "./runtime-api";

let refreshPromise: Promise<string | null> | null = null;

interface FetchOptions extends RequestInit {
  token?: string;
  skipAuthRefresh?: boolean;
  /** ms antes de abortar. Default 20000 -- sin esto, fetch() nunca falla ni
   * resuelve por si solo con una conexion muy lenta (medido en campo: menos
   * de 1 kB/s), asi que las pantallas quedaban con el spinner para siempre
   * en vez de caer al fallback offline que ya existe. Pasar 0 para desactivar. */
  timeoutMs?: number;
}

const DEFAULT_FETCH_TIMEOUT_MS = 20000;

async function fetchWithTimeout(url: string, init: RequestInit, timeoutMs: number): Promise<Response> {
  if (timeoutMs <= 0 || init.signal) {
    return fetch(url, init);
  }

  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort(), timeoutMs);
  try {
    return await fetch(url, { ...init, signal: controller.signal });
  } catch (err: any) {
    if (err?.name === "AbortError") {
      const timeoutError: any = new Error(
        "La conexión es muy lenta y no respondió a tiempo. Intenta de nuevo.",
      );
      timeoutError.isTimeout = true;
      throw timeoutError;
    }
    throw err;
  } finally {
    clearTimeout(timer);
  }
}

async function parseErrorResponse(response: Response) {
  const text = await response.text().catch(() => "");

  if (text) {
    try {
      const parsed = JSON.parse(text);
      const message = Array.isArray(parsed?.message)
        ? parsed.message.join(" ")
        : parsed?.message;
      return {
        ...parsed,
        message: message || parsed?.error,
      };
    } catch {
      if (response.status === 404) {
        return {
          message:
            "No se pudo conectar con el sistema. Revisá tu conexión a internet e intentá de nuevo.",
        };
      }

      return {
        message:
          "El sistema respondió algo inesperado. Intentá de nuevo; si sigue igual, avisá a soporte.",
      };
    }
  }

  return { message: "No se pudo conectar con el servidor." };
}

async function refreshAccessToken(): Promise<string | null> {
  if (refreshPromise) {
    return refreshPromise;
  }

  refreshPromise = (async () => {
    const { refreshToken, user } = readAuthSession();

    if (!refreshToken) {
      clearAuthSession({ redirectToLogin: true });
      return null;
    }

    const response = await fetch(`${getApiUrl()}/auth/refresh`, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ refreshToken }),
    });

    if (!response.ok) {
      clearAuthSession({ redirectToLogin: true });
      return null;
    }

    const data = await response.json();
    setAuthSession({
      accessToken: data.accessToken,
      refreshToken: data.refreshToken || refreshToken,
      user,
    });

    return data.accessToken || null;
  })().finally(() => {
    refreshPromise = null;
  });

  return refreshPromise;
}

export async function apiFetch<T>(
  endpoint: string,
  options: FetchOptions = {},
): Promise<T> {
  const { token, skipAuthRefresh = false, timeoutMs, ...fetchOptions } = options;
  const isFormData =
    typeof FormData !== "undefined" && fetchOptions.body instanceof FormData;

  const headers: HeadersInit = {
    ...(isFormData ? {} : { "Content-Type": "application/json" }),
    ...options.headers,
  };

  if (token) {
    (headers as Record<string, string>)["Authorization"] = `Bearer ${token}`;
  } else {
    const storedToken = readAuthSession().accessToken;
    if (storedToken) {
      (headers as Record<string, string>)["Authorization"] =
        `Bearer ${storedToken}`;
    }
  }

  const response = await fetchWithTimeout(
    `${getApiUrl()}${endpoint}`,
    { ...fetchOptions, headers },
    timeoutMs ?? DEFAULT_FETCH_TIMEOUT_MS,
  );

  if (
    response.status === 401 &&
    !skipAuthRefresh &&
    endpoint !== "/auth/login" &&
    endpoint !== "/auth/refresh"
  ) {
    const renewedToken = await refreshAccessToken();

    if (renewedToken) {
      return apiFetch<T>(endpoint, {
        ...options,
        token: renewedToken,
        skipAuthRefresh: true,
      });
    }

    const expiredError: any = new Error(
      "Sesión expirada. Inicia sesión de nuevo.",
    );
    expiredError.status = 401;
    throw expiredError;
  }

  if (!response.ok) {
    const parsedError = await parseErrorResponse(response);
    const error: any = new Error(
      parsedError.message || `HTTP error ${response.status}`,
    );
    error.status = response.status;
    throw error;
  }

  if (response.status === 204) {
    return {} as T;
  }

  const text = await response.text();
  if (!text) {
    return {} as T;
  }

  try {
    return JSON.parse(text);
  } catch {
    throw new Error(
      "El servidor respondió con un formato no válido. Intenta actualizar la página.",
    );
  }
}

export async function apiFetchBlob(
  endpoint: string,
  options: FetchOptions = {},
): Promise<Blob> {
  const { token, skipAuthRefresh = false, timeoutMs, ...fetchOptions } = options;

  const headers: HeadersInit = {
    ...options.headers,
  };

  if (token) {
    (headers as Record<string, string>)["Authorization"] = `Bearer ${token}`;
  } else {
    const storedToken = readAuthSession().accessToken;
    if (storedToken) {
      (headers as Record<string, string>)["Authorization"] =
        `Bearer ${storedToken}`;
    }
  }

  // Timeout mas largo que apiFetch: son descargas de archivo (PDFs), no
  // llamadas cortas de datos -- pueden legitimamente tardar mas en una red
  // lenta, pero tampoco deben quedar colgadas para siempre.
  const response = await fetchWithTimeout(
    `${getApiUrl()}${endpoint}`,
    { ...fetchOptions, headers },
    timeoutMs ?? DEFAULT_FETCH_TIMEOUT_MS * 3,
  );

  if (
    response.status === 401 &&
    !skipAuthRefresh &&
    endpoint !== "/auth/login" &&
    endpoint !== "/auth/refresh"
  ) {
    const renewedToken = await refreshAccessToken();

    if (renewedToken) {
      return apiFetchBlob(endpoint, {
        ...options,
        token: renewedToken,
        skipAuthRefresh: true,
      });
    }

    const expiredError: any = new Error(
      "Sesión expirada. Inicia sesión de nuevo.",
    );
    expiredError.status = 401;
    throw expiredError;
  }

  if (!response.ok) {
    const parsedError = await parseErrorResponse(response);
    const error: any = new Error(
      parsedError.message || `HTTP error ${response.status}`,
    );
    error.status = response.status;
    throw error;
  }

  return response.blob();
}

export async function downloadApiFile(endpoint: string, filename: string) {
  const blob = await apiFetchBlob(endpoint);
  const url = URL.createObjectURL(blob);
  const link = document.createElement("a");
  link.href = url;
  link.download = filename;
  document.body.appendChild(link);
  link.click();
  link.remove();
  URL.revokeObjectURL(url);
}

export const authApi = {
  login: (email: string, password: string) =>
    apiFetch<{ accessToken: string; refreshToken: string; user: any }>(
      "/auth/login",
      {
        method: "POST",
        body: JSON.stringify({ username: email, password }),
      },
    ),

  refresh: (refreshToken: string) =>
    apiFetch<{ accessToken: string }>("/auth/refresh", {
      method: "POST",
      body: JSON.stringify({ refreshToken }),
    }),

  me: () => apiFetch<any>("/auth/me"),

  tiendas: () => apiFetch<any[]>("/auth/tiendas"),
  createTienda: (data: { nombre: string; codigo?: string; direccion?: string; telefonoImpresion?: string }) =>
    apiFetch<any>("/auth/tiendas", {
      method: "POST",
      body: JSON.stringify(data),
    }),
  updateTienda: (id: number, data: any) =>
    apiFetch<any>(`/auth/tiendas/${id}`, {
      method: "PUT",
      body: JSON.stringify(data),
    }),
  updateTiendaFacturacionPos: (
    id: number,
    data: {
      habilitada: boolean;
      url?: string;
      username?: string;
      password?: string;
      computadora?: string;
      precioDecimales?: number;
      precioIncluyeIva?: boolean;
      pluProductosTaller?: string;
      pluServiciosTaller?: string;
    },
  ) =>
    apiFetch<{ tiendaId: number; habilitada: boolean }>(`/auth/tiendas/${id}/facturacion-pos`, {
      method: "PUT",
      body: JSON.stringify(data),
    }),

  tenantsDisponibles: () =>
    apiFetch<
      Array<{
        tenantId: number;
        codigo: string;
        nombre: string;
        entorno: "demo" | "produccion";
        via: "tenant" | "plataforma";
        current: boolean;
      }>
    >("/auth/tenants-disponibles"),

  switchTenant: (tenantCodigo: string) =>
    apiFetch<{ accessToken: string; refreshToken: string; user: any }>(
      "/auth/switch-tenant",
      {
        method: "POST",
        body: JSON.stringify({ tenantCodigo }),
      },
    ),

  users: () => apiFetch<any[]>("/auth/users"),
  createUser: (data: any) =>
    apiFetch<any>("/auth/users", {
      method: "POST",
      body: JSON.stringify(data),
    }),
  updateUser: (id: number, data: any) =>
    apiFetch<any>(`/auth/users/${id}`, {
      method: "PUT",
      body: JSON.stringify(data),
    }),
  setUserActive: (id: number, value: boolean) =>
    apiFetch<any>(`/auth/users/${id}/activo?value=${value}`, {
      method: "PATCH",
    }),
};

export type TenantEstado = "onboarding" | "activo" | "suspendido";
export type TenantEntorno = "demo" | "produccion";

export type TenantResumen = {
  id: number;
  codigo: string;
  nombre: string;
  estado: TenantEstado;
  entorno: TenantEntorno;
  dbHost: string;
  dbPort: number;
  dbName: string;
  migracionesPendientes: string[];
  desviaciones: { grave: number; advertencia: number; informativo: number };
  acceso: { ok: true } | { ok: false; error: string };
};

export type TogglesOperativos = {
  permiteTiendas: boolean;
  permiteProductosPropios: boolean;
  facturaHabilitada: boolean;
};

export type ParametroOperativoAdmin = {
  clave: string;
  grupo: string;
  seccion: string | null;
  etiqueta: string;
  descripcion: string;
  valor: boolean;
  valorPorDefecto: boolean;
};

export type ColumnDeviation = {
  tabla: string;
  columna: string;
  severidad: "grave" | "advertencia" | "informativo";
  tipoEsperado: string;
  tipoReal: string;
  alterSugerido: string;
};

export type TenantEstadoDetallado = {
  tenant: {
    id: number;
    codigo: string;
    nombre: string;
    estado: TenantEstado;
    entorno: TenantEntorno;
    dbHost: string;
    dbPort: number;
    dbName: string;
    dbUser: string;
  };
  acceso: { ok: true } | { ok: false; error: string };
  migracionesPendientes: string[];
  desviaciones: ColumnDeviation[];
};

export const tenantsAdminApi = {
  list: () => apiFetch<TenantResumen[]>("/admin/tenants"),

  getEstado: (id: number) => apiFetch<TenantEstadoDetallado>(`/admin/tenants/${id}`),

  getResumenOperativo: (id: number) =>
    apiFetch<{
      tiendas: number | null;
      tiendasConFacturacionPos: number | null;
      productos: number | null;
      toggles: TogglesOperativos;
    }>(`/admin/tenants/${id}/resumen-operativo`),

  actualizarTogglesOperativos: (id: number, data: Partial<TogglesOperativos>) =>
    apiFetch<TogglesOperativos>(`/admin/tenants/${id}/toggles-operativos`, {
      method: "PATCH",
      body: JSON.stringify(data),
    }),

  getParametrosOperativos: (id: number) =>
    apiFetch<{ parametros: ParametroOperativoAdmin[] }>(`/admin/tenants/${id}/parametros-operativos`),

  actualizarParametrosOperativos: (id: number, valores: Record<string, boolean>) =>
    apiFetch<{ parametros: ParametroOperativoAdmin[] }>(`/admin/tenants/${id}/parametros-operativos`, {
      method: "PUT",
      body: JSON.stringify({ valores }),
    }),

  probarConexion: (data: {
    dbHost: string;
    dbPort: number;
    dbName: string;
    dbUser: string;
    dbPassword: string;
  }) =>
    apiFetch<{ ok: true } | { ok: false; error: string }>(
      "/admin/tenants/probar-conexion",
      { method: "POST", body: JSON.stringify(data) },
    ),

  registrar: (data: {
    codigo: string;
    nombre: string;
    entorno: TenantEntorno;
    dbHost: string;
    dbPort: number;
    dbName: string;
    dbUser: string;
    dbPassword: string;
  }) => apiFetch<TenantResumen>("/admin/tenants", { method: "POST", body: JSON.stringify(data) }),

  omitirDesviacion: (id: number, deviation: ColumnDeviation) =>
    apiFetch<any>(`/admin/tenants/${id}/omitir-desviacion`, {
      method: "POST",
      body: JSON.stringify(deviation),
    }),

  aprovisionarAcceso: (
    id: number,
    data: { email: string; nombre: string; username: string; password: string },
  ) =>
    apiFetch<{
      identidadId: number;
      usuarioLocalId: number;
      membershipId: number;
      email: string;
      username: string;
    }>(`/admin/tenants/${id}/aprovisionar-acceso`, {
      method: "POST",
      body: JSON.stringify(data),
    }),

  activar: (id: number) =>
    apiFetch<{ id: number; estado: TenantEstado }>(`/admin/tenants/${id}/activar`, {
      method: "POST",
    }),

  reconciliar: (id: number) =>
    apiFetch<{
      jobId: number;
      estado: "completo" | "incompleto";
      aplicadas: string[];
      falloEnMigracion: string | null;
      errorMensaje: string | null;
    }>(`/admin/tenants/${id}/reconciliar`, { method: "POST" }),

  // Re-corre el up() de TODAS las migraciones sin mirar la tabla `migrations`
  // del tenant -- para cuando esa tabla dice que todo esta aplicado pero el
  // esquema real no lo refleja (reconciliar() normal no lo detecta).
  reconciliarForzado: (id: number) =>
    apiFetch<{
      jobId: number;
      estado: "completo" | "incompleto";
      aplicadas: string[];
      falloEnMigracion: string | null;
      errorMensaje: string | null;
    }>(`/admin/tenants/${id}/reconciliar-forzado`, { method: "POST" }),

  progresoReconciliacion: (id: number) =>
    apiFetch<{ aplicadas: number; total: number }>(`/admin/tenants/${id}/reconciliar-progreso`),
};

export const otsApi = {
  list: (params?: Record<string, any>) => {
    const query = params ? `?${new URLSearchParams(params as any)}` : "";
    return apiFetch<any>(`/ots${query}`);
  },

  get: (id: number) => apiFetch<any>(`/ots/${id}`),

  getEventos: (id: number) => apiFetch<any[]>(`/ots/${id}/eventos`),

  getFacturacionPos: (id: number) => apiFetch<any | null>(`/ots/${id}/facturacion-pos`),

  reintentarFacturacionPos: (id: number) =>
    apiFetch<any>(`/ots/${id}/facturacion-pos/reintentar`, { method: "POST" }),

  confirmarPago: (id: number) =>
    apiFetch<any>(`/ots/${id}/confirmar-pago`, { method: "POST" }),

  create: (data: any) =>
    apiFetch<any>("/ots", {
      method: "POST",
      body: JSON.stringify(data),
    }),

  update: (id: number, data: any) =>
    apiFetch<any>(`/ots/${id}`, {
      method: "PUT",
      body: JSON.stringify(data),
    }),

  changeEstado: (
    id: number,
    estado: string,
    fechaSalida?: string,
    entrega?: {
      kmSalida?: number;
      nivelGasolinaSalida?: number;
      recomendacionesEntrega?: string;
      entregadoPor?: string;
      firmaEntrega?: string;
    },
  ) =>
    apiFetch<any>(`/ots/${id}/estado`, {
      method: "PATCH",
      body: JSON.stringify({ estado, fechaSalida, ...entrega }),
    }),

  addDano: (otId: number, data: any) =>
    apiFetch<any>(`/ots/${otId}/danos`, {
      method: "POST",
      body: JSON.stringify(data),
    }),

  removeDano: (otId: number, danoId: number) =>
    apiFetch<void>(`/ots/${otId}/danos/${danoId}`, {
      method: "DELETE",
    }),

  addFoto: (otId: number, data: any) =>
    apiFetch<any>(`/ots/${otId}/fotos`, {
      method: "POST",
      body: JSON.stringify(data),
    }),

  removeFoto: (otId: number, fotoId: number) =>
    apiFetch<void>(`/ots/${otId}/fotos/${fotoId}`, {
      method: "DELETE",
    }),

  saveFirmas: (otId: number, data: any) =>
    apiFetch<any>(`/ots/${otId}/firmas`, {
      method: "POST",
      body: JSON.stringify(data),
    }),

  savePdfEscaner: (otId: number, pdfUrl: string) =>
    apiFetch<any>(`/ots/${otId}/pdf`, {
      method: "POST",
      body: JSON.stringify({ pdfUrl }),
    }),
};
export const clientesApi = {
  list: (params?: Record<string, any>) => {
    const query = params ? `?${new URLSearchParams(params as any)}` : "";
    return apiFetch<any>(`/clientes${query}`);
  },

  search: (q: string) =>
    apiFetch<any[]>(`/clientes/search?q=${encodeURIComponent(q)}`),

  get: (tarjeta: string) => apiFetch<any>(`/clientes/${tarjeta}`),

  create: (data: any) =>
    apiFetch<any>("/clientes", {
      method: "POST",
      body: JSON.stringify(data),
    }),

  update: (tarjeta: string, data: any) =>
    apiFetch<any>(`/clientes/${tarjeta}`, {
      method: "PUT",
      body: JSON.stringify(data),
    }),
};

export const vehiculosApi = {
  list: (clienteId?: string) => {
    const query = clienteId ? `?clienteId=${clienteId}` : "";
    return apiFetch<any[]>(`/vehiculos${query}`);
  },

  search: (q: string) =>
    apiFetch<any[]>(`/vehiculos/search?q=${encodeURIComponent(q)}`),

  getByPlaca: (placa: string) => apiFetch<any>(`/vehiculos/placa/${placa}`),

  create: (data: any) =>
    apiFetch<any>("/vehiculos", {
      method: "POST",
      body: JSON.stringify(data),
    }),

  update: (id: number, data: any) =>
    apiFetch<any>(`/vehiculos/${id}`, {
      method: "PUT",
      body: JSON.stringify(data),
    }),
};

export const productosApi = {
  search: (
    q: string,
    limit = 20,
    options?: { esServicio?: boolean; soloTaller?: boolean },
  ) => {
    const params = new URLSearchParams({ q, limit: String(limit) });
    if (options?.esServicio !== undefined) {
      params.set("esServicio", String(options.esServicio));
    }
    if (options?.soloTaller !== undefined) {
      params.set("soloTaller", String(options.soloTaller));
    }
    return apiFetch<any[]>(`/productos/search?${params.toString()}`);
  },

  get: (plu: string) => apiFetch<any>(`/productos/${plu}`),

  getTop: (
    limit = 50,
    options?: { esServicio?: boolean; soloTaller?: boolean },
  ) => {
    const params = new URLSearchParams({ limit: String(limit) });
    if (options?.esServicio !== undefined) {
      params.set("esServicio", String(options.esServicio));
    }
    if (options?.soloTaller !== undefined) {
      params.set("soloTaller", String(options.soloTaller));
    }
    return apiFetch<any[]>(`/productos/top?${params.toString()}`);
  },

  create: (data: any) =>
    apiFetch<any>("/productos", {
      method: "POST",
      body: JSON.stringify(data),
    }),

  update: (plu: string, data: any) =>
    apiFetch<any>(`/productos/${plu}`, {
      method: "PUT",
      body: JSON.stringify(data),
    }),

  delete: (plu: string) =>
    apiFetch<void>(`/productos/${plu}`, {
      method: "DELETE",
    }),
};
export const mecanicosApi = {
  list: (filters?: { activo?: boolean }) => {
    const params = new URLSearchParams();
    if (filters?.activo !== undefined) {
      params.set("activo", String(filters.activo));
    }
    const query = params.toString();
    return apiFetch<any[]>(`/mecanicos${query ? `?${query}` : ""}`);
  },

  create: (data: any) =>
    apiFetch<any>("/mecanicos", {
      method: "POST",
      body: JSON.stringify(data),
    }),

  update: (id: number, data: any) =>
    apiFetch<any>(`/mecanicos/${id}`, {
      method: "PUT",
      body: JSON.stringify(data),
    }),

  setActive: (id: number, value: boolean) =>
    apiFetch<any>(`/mecanicos/${id}/activo?value=${value}`, {
      method: "PATCH",
    }),
};
export const cotizacionesApi = {
  getByOt: (otId: number) => apiFetch<any>(`/cotizaciones/ot/${otId}`),

  createPublicLink: (cotId: number) =>
    apiFetch<any>(`/cotizaciones/${cotId}/public-link`, {
      method: "POST",
    }),

  versions: (cotId: number) =>
    apiFetch<any[]>(`/cotizaciones/${cotId}/versiones`),

  create: (data: any) =>
    apiFetch<any>("/cotizaciones", {
      method: "POST",
      body: JSON.stringify(data),
    }),

  addLinea: (cotId: number, data: any) =>
    apiFetch<any>(`/cotizaciones/${cotId}/lineas`, {
      method: "POST",
      body: JSON.stringify(data),
    }),

  updateLinea: (cotId: number, lineaId: number, data: any) =>
    apiFetch<any>(`/cotizaciones/${cotId}/lineas/${lineaId}`, {
      method: "PUT",
      body: JSON.stringify(data),
    }),

  getLock: (cotId: number) => apiFetch<any>(`/cotizaciones/${cotId}/lock`),

  acquireLock: (cotId: number) =>
    apiFetch<any>(`/cotizaciones/${cotId}/lock`, {
      method: "POST",
    }),

  renewLock: (cotId: number, lockToken: string) =>
    apiFetch<any>(`/cotizaciones/${cotId}/lock`, {
      method: "PATCH",
      body: JSON.stringify({ lockToken }),
    }),

  releaseLock: (cotId: number, lockToken: string) =>
    apiFetch<any>(`/cotizaciones/${cotId}/lock`, {
      method: "DELETE",
      body: JSON.stringify({ lockToken }),
    }),

  removeLinea: (cotId: number, lineaId: number, data?: any) =>
    apiFetch<void>(`/cotizaciones/${cotId}/lineas/${lineaId}`, {
      method: "DELETE",
      body: data ? JSON.stringify(data) : undefined,
    }),

  aprobar: (cotId: number, data?: any) =>
    apiFetch<any>(`/cotizaciones/${cotId}/aprobar`, {
      method: "PATCH",
      body: data ? JSON.stringify(data) : undefined,
    }),
};

export const publicCotizacionesApi = {
  get: (token: string) =>
    apiFetch<any>(`/public/cotizaciones/${encodeURIComponent(token)}`, {
      skipAuthRefresh: true,
    }),
  decide: (token: string, data: any) =>
    apiFetch<any>(
      `/public/cotizaciones/${encodeURIComponent(token)}/decision`,
      {
        method: "POST",
        body: JSON.stringify(data),
        skipAuthRefresh: true,
      },
    ),
};

export const whatsappApi = {
  sendCotizacion: (cotizacionId: number) =>
    apiFetch<any>(`/whatsapp/cotizaciones/${cotizacionId}/enviar`, {
      method: "POST",
    }),
  sendEstadoOt: (otId: number) =>
    apiFetch<any>(`/whatsapp/ots/${otId}/estado`, {
      method: "POST",
    }),
};

export const publicOtsApi = {
  getStatus: (token: string) =>
    apiFetch<any>(`/public/ots/${encodeURIComponent(token)}/status`, {
      skipAuthRefresh: true,
    }),
};

export const catalogosApi = {
  tiposVehiculo: (filters?: { activo?: boolean }) => {
    const params = new URLSearchParams();
    if (filters?.activo !== undefined)
      params.set("activo", String(filters.activo));
    const query = params.toString();
    return apiFetch<any[]>(
      `/catalogos/tipos-vehiculo${query ? `?${query}` : ""}`,
    );
  },
  createTipoVehiculo: (data: any) =>
    apiFetch<any>("/catalogos/tipos-vehiculo", {
      method: "POST",
      body: JSON.stringify(data),
    }),
  updateTipoVehiculo: (id: number, data: any) =>
    apiFetch<any>(`/catalogos/tipos-vehiculo/${id}`, {
      method: "PUT",
      body: JSON.stringify(data),
    }),
  inactivateTipoVehiculo: (id: number) =>
    apiFetch<any>(`/catalogos/tipos-vehiculo/${id}/inactivar`, {
      method: "PATCH",
    }),
  activateTipoVehiculo: (id: number) =>
    apiFetch<any>(`/catalogos/tipos-vehiculo/${id}/activar`, {
      method: "PATCH",
    }),

  cosasTrae: (filters?: { activo?: boolean }) => {
    const params = new URLSearchParams();
    if (filters?.activo !== undefined)
      params.set("activo", String(filters.activo));
    const query = params.toString();
    return apiFetch<any[]>(`/catalogos/cosas-trae${query ? `?${query}` : ""}`);
  },
  createCosaTrae: (data: any) =>
    apiFetch<any>("/catalogos/cosas-trae", {
      method: "POST",
      body: JSON.stringify(data),
    }),
  updateCosaTrae: (id: number, data: any) =>
    apiFetch<any>(`/catalogos/cosas-trae/${id}`, {
      method: "PUT",
      body: JSON.stringify(data),
    }),
  inactivateCosaTrae: (id: number) =>
    apiFetch<any>(`/catalogos/cosas-trae/${id}/inactivar`, {
      method: "PATCH",
    }),
  activateCosaTrae: (id: number) =>
    apiFetch<any>(`/catalogos/cosas-trae/${id}/activar`, {
      method: "PATCH",
    }),

  tiposDano: (filters?: { activo?: boolean }) => {
    const params = new URLSearchParams();
    if (filters?.activo !== undefined)
      params.set("activo", String(filters.activo));
    const query = params.toString();
    return apiFetch<any[]>(`/catalogos/tipos-dano${query ? `?${query}` : ""}`);
  },
  createTipoDano: (data: any) =>
    apiFetch<any>("/catalogos/tipos-dano", {
      method: "POST",
      body: JSON.stringify(data),
    }),
  updateTipoDano: (id: number, data: any) =>
    apiFetch<any>(`/catalogos/tipos-dano/${id}`, {
      method: "PUT",
      body: JSON.stringify(data),
    }),
  inactivateTipoDano: (id: number) =>
    apiFetch<any>(`/catalogos/tipos-dano/${id}/inactivar`, {
      method: "PATCH",
    }),
  activateTipoDano: (id: number) =>
    apiFetch<any>(`/catalogos/tipos-dano/${id}/activar`, {
      method: "PATCH",
    }),

  vehicular: (
    tipo: "marca" | "modelo" | "motor" | "cilindraje",
    filters?: Record<string, any>,
  ) => {
    const params = new URLSearchParams({ tipo, ...(filters || {}) } as any);
    return apiFetch<any[]>(`/catalogos/vehicular?${params.toString()}`);
  },
  createVehicular: (data: any) =>
    apiFetch<any>("/catalogos/vehicular", {
      method: "POST",
      body: JSON.stringify(data),
    }),
  updateVehicular: (id: number, data: any) =>
    apiFetch<any>(`/catalogos/vehicular/${id}`, {
      method: "PUT",
      body: JSON.stringify(data),
    }),
  inactivateVehicular: (id: number) =>
    apiFetch<any>(`/catalogos/vehicular/${id}/inactivar`, {
      method: "PATCH",
    }),
  activateVehicular: (id: number) =>
    apiFetch<any>(`/catalogos/vehicular/${id}/activar`, {
      method: "PATCH",
    }),
  importVehicularExcel: (file: File) => {
    const formData = new FormData();
    formData.append("file", file);
    return apiFetch<any>("/catalogos/vehicular/importar-excel", {
      method: "POST",
      body: formData,
    });
  },
  downloadFichaVehicularTemplate: () =>
    downloadApiFile(
      "/catalogos/plantillas/ficha-vehicular",
      "plantilla-ficha-vehicular.xlsx",
    ),
};

export const historialApi = {
  search: (q: string) =>
    apiFetch<any>(`/historial/search?q=${encodeURIComponent(q)}`),
  byCliente: (tarjeta: string) =>
    apiFetch<any>(`/historial/cliente/${encodeURIComponent(tarjeta)}`),
  byPlaca: (placa: string) =>
    apiFetch<any>(`/historial/placa/${encodeURIComponent(placa)}`),
};

export type ResumenReportes = {
  totalOts: number;
  totalClientes: number;
  totalVehiculos: number;
  totalCotizado: number;
  totalCotizadoAprobado: number;
  ticketPromedio: number;
  cotizacionesPendientes: number;
  porEstado: Record<string, number>;
  topClientes: Array<{
    tarjeta: string;
    nombre: string | null;
    recepciones: number;
    totalCotizado: number;
  }>;
  topVehiculos: Array<{
    placa: string;
    marca: string | null;
    modelo: string | null;
    visitas: number;
  }>;
  recientes: Array<{
    numeroOt: string | null;
    estado: string;
    fechaEntrada: string | null;
    placa: string | null;
    clienteTarjeta: string | null;
    clienteNombre: string | null;
    total: number;
  }>;
};

export const reportesApi = {
  resumen: () => apiFetch<ResumenReportes>("/reportes/resumen"),
};

export const syncApi = {
  push: (events: any[], deviceId?: string) =>
    apiFetch<any>("/sync/push", {
      method: "POST",
      body: JSON.stringify({ events, deviceId }),
    }),

  pull: (since: number) => apiFetch<any>(`/sync/pull?since=${since}`),
};

export interface UploadStatusSnapshot {
  attempt: number;
  totalAttempts: number;
  phase: "starting" | "uploading" | "retrying" | "processing" | "done";
  message: string;
}

interface UploadFileOptions {
  maxRetries?: number;
  timeoutMs?: number;
  onStatusChange?: (status: UploadStatusSnapshot) => void;
}

function wait(ms: number) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

function isRetryableUploadError(error: any): boolean {
  const status = Number(error?.status);
  if (status === 408 || status === 425 || status === 429) {
    return true;
  }

  if (Number.isFinite(status) && status >= 500) {
    return true;
  }

  const code = String(error?.code || "").toUpperCase();
  if (code === "NETWORK" || code === "TIMEOUT") {
    return true;
  }

  const message = String(error?.message || "");
  return /network|timeout|failed to fetch|connection|conexion/i.test(message);
}

// Mensajes tecnicos crudos que Express/Nest devuelven en ingles cuando el
// error nunca pasa por nuestro propio codigo (ej. el body-parser rechaza el
// request antes de llegar al controller) -- sin esto, ese texto crudo llega
// tal cual a la pantalla del usuario en vez de un mensaje entendible.
const RAW_ERROR_TRANSLATIONS: Array<{ match: RegExp; friendly: string }> = [
  {
    match: /request entity too large|payload too large/i,
    friendly: "El archivo es demasiado grande para subirlo.",
  },
];

function getFriendlyUploadError(parsed: any, fallback: string): string {
  const rawMessage = parsed?.message;

  if (Array.isArray(rawMessage)) {
    return rawMessage.filter(Boolean).join(" ");
  }

  if (typeof rawMessage === "string" && rawMessage.trim()) {
    const translated = RAW_ERROR_TRANSLATIONS.find((entry) => entry.match.test(rawMessage));
    return translated ? translated.friendly : rawMessage;
  }

  if (typeof parsed?.error === "string" && parsed.error.trim()) {
    const translated = RAW_ERROR_TRANSLATIONS.find((entry) => entry.match.test(parsed.error));
    return translated ? translated.friendly : parsed.error;
  }

  return fallback;
}

function buildUploadLabel(type: "foto" | "firma" | "pdf"): string {
  if (type === "pdf") return "documento";
  if (type === "firma") return "firma";
  return "archivo";
}

function validateUploadFile(file: File, type: "foto" | "firma" | "pdf") {
  if (type === "pdf") {
    const isPdf =
      file.type === "application/pdf" ||
      file.name.toLowerCase().endsWith(".pdf");
    if (!isPdf) {
      throw new Error("El archivo debe ser PDF.");
    }
    if (file.size > 15 * 1024 * 1024) {
      throw new Error(
        "El PDF es demasiado grande. El tamaño máximo permitido es 15 MB.",
      );
    }
    return;
  }

  if (!file.type.startsWith("image/")) {
    throw new Error(
      type === "firma"
        ? "La firma debe ser una imagen."
        : "La foto debe ser una imagen.",
    );
  }

  const maxSize = type === "firma" ? 3 * 1024 * 1024 : 8 * 1024 * 1024;
  if (file.size > maxSize) {
    throw new Error(
      type === "firma"
        ? "La firma es demasiado grande. Intenta capturarla de nuevo."
        : "La foto es demasiado grande. Intenta con una imagen mas liviana.",
    );
  }
}

export async function uploadFile(
  file: File,
  type: "foto" | "firma" | "pdf",
): Promise<{ url: string }> {
  return uploadFileWithProgress(file, type);
}

export async function uploadFileWithProgress(
  file: File,
  type: "foto" | "firma" | "pdf",
  onProgress?: (progress: number) => void,
  options: UploadFileOptions = {},
): Promise<{ url: string }> {
  const { maxRetries = 2, timeoutMs = 120000, onStatusChange } = options;

  validateUploadFile(file, type);

  let token = readAuthSession().accessToken;
  let authRefreshed = false;
  const totalAttempts = Math.max(1, maxRetries + 1);
  const uploadLabel = buildUploadLabel(type);

  const emitStatus = (status: UploadStatusSnapshot) => {
    onStatusChange?.(status);
  };

  const sendOnce = (activeToken: string | null, attempt: number) =>
    new Promise<{ url: string }>((resolve, reject) => {
      const xhr = new XMLHttpRequest();
      xhr.open("POST", `${getApiUrl()}/uploads/${type}`);
      xhr.timeout = timeoutMs;

      if (activeToken) {
        xhr.setRequestHeader("Authorization", `Bearer ${activeToken}`);
      }

      emitStatus({
        attempt,
        totalAttempts,
        phase: attempt === 1 ? "starting" : "uploading",
        message:
          attempt === 1
            ? `Preparando ${uploadLabel}...`
            : `Reanudando subida (${attempt}/${totalAttempts})`,
      });

      xhr.upload.onprogress = (event) => {
        if (!event.lengthComputable) return;
        const progress = Math.min(
          100,
          Math.round((event.loaded / event.total) * 100),
        );
        onProgress?.(progress);
        emitStatus({
          attempt,
          totalAttempts,
          phase: "uploading",
          message: `Subiendo ${uploadLabel} (${attempt}/${totalAttempts})`,
        });
      };

      xhr.onerror = () => {
        const error: any = new Error("Error de conexión durante la carga");
        error.code = "NETWORK";
        reject(error);
      };

      xhr.ontimeout = () => {
        const error: any = new Error("La carga excedio el tiempo de espera");
        error.code = "TIMEOUT";
        reject(error);
      };

      xhr.onload = () => {
        if (xhr.status >= 200 && xhr.status < 300) {
          emitStatus({
            attempt,
            totalAttempts,
            phase: "processing",
            message: "Guardando respuesta del servidor...",
          });

          try {
            resolve(JSON.parse(xhr.responseText || "{}"));
          } catch {
            reject(new Error("Respuesta invalida del servidor"));
          }
          return;
        }

        if (xhr.status === 401) {
          const error: any = new Error("unauthorized");
          error.status = 401;
          reject(error);
          return;
        }

        try {
          const parsed = JSON.parse(xhr.responseText || "{}");
          const error: any = new Error(
            getFriendlyUploadError(
              parsed,
              `No se pudo subir el ${uploadLabel}.`,
            ),
          );
          error.status = xhr.status;
          reject(error);
        } catch {
          const error: any = new Error(`No se pudo subir el ${uploadLabel}.`);
          error.status = xhr.status;
          reject(error);
        }
      };

      const formData = new FormData();
      formData.append("file", file);
      xhr.send(formData);
    });

  for (let attempt = 1; attempt <= totalAttempts; attempt += 1) {
    try {
      const result = await sendOnce(token, attempt);
      onProgress?.(100);
      emitStatus({
        attempt,
        totalAttempts,
        phase: "done",
        message: `${uploadLabel.charAt(0).toUpperCase()}${uploadLabel.slice(1)} cargado correctamente`,
      });
      return result;
    } catch (error: any) {
      if (error?.status === 401 && !authRefreshed) {
        token = await refreshAccessToken();
        authRefreshed = true;
        if (!token) {
          throw new Error("Sesión expirada. Inicia sesión de nuevo.");
        }
        attempt -= 1;
        continue;
      }

      const canRetry = attempt < totalAttempts && isRetryableUploadError(error);
      if (!canRetry) {
        throw error;
      }

      emitStatus({
        attempt,
        totalAttempts,
        phase: "retrying",
        message: `Reintentando carga (${attempt + 1}/${totalAttempts})...`,
      });
      await wait(Math.min(5000, 900 * attempt));
    }
  }

  throw new Error("No se pudo completar la carga.");
}

export type CatalogoParametroEntrada = {
  ambito: "tenant" | "tienda";
  tipo: "boolean" | "string" | "number" | "json";
  grupo: string;
  etiqueta: string;
  descripcion: string;
  sensible: boolean;
  default?: boolean | string | number;
  requeridoSi?: string;
  comportamientoAnteFallo?: "bloquear" | "permitir";
  seccion?: string;
};

export type ValorParaUI = boolean | string | number | { configurado: boolean };

export type ResumenConfiguracion = {
  tenant: Record<string, ValorParaUI>;
  tiendas: Array<{ tiendaId: number; nombre: string; parametros: Record<string, ValorParaUI> }>;
};

export type FlagsConfiguracion = {
  usaFacturacion: boolean;
  permiteTiendas: boolean;
  permiteProductosPropios: boolean;
  usaWhatsapp: boolean;
  usaFotos: boolean;
  usaDanos: boolean;
  usaPdfEscaner: boolean;
  usaCosasTrae: boolean;
  usaMecanicos: boolean;
  usaFirmaCliente: boolean;
  usaFirmaRecepcion: boolean;
  exigeFechaSalida: boolean;
  unaOtPorVehiculo: boolean;
  bloqueaVentaBajoCosto: boolean;
  exigeMotivoCambio: boolean;
  usaLinkPublicoCotizacion: boolean;
  usaEstadoPublico: boolean;
  usaHistorial: boolean;
  usaReportes: boolean;
  usaCargasMasivas: boolean;
  usaBitacora: boolean;
  imprimeRecepcion: boolean;
  imprimeCotizacion: boolean;
  imprimeEntrega: boolean;
  imprimeOrdenInterna: boolean;
  exigePagoParaEntrega: boolean;
};

export const configuracionApi = {
  catalogo: () => apiFetch<Record<string, CatalogoParametroEntrada>>("/taller-parametros/catalogo"),

  resumen: () => apiFetch<ResumenConfiguracion>("/taller-parametros"),

  flags: () => apiFetch<FlagsConfiguracion>("/taller-parametros/flags"),

  actualizarTenant: (data: Record<string, boolean | string | undefined>) =>
    apiFetch<{ ok: true }>("/taller-parametros/tenant", {
      method: "PUT",
      body: JSON.stringify(data),
    }),

  probarConexionPos: (
    tiendaId: number,
    data: { url: string; username: string; password?: string; computadora: string },
  ) =>
    apiFetch<{ ok: boolean; detalle?: string }>(
      `/taller-parametros/tiendas/${tiendaId}/facturacion-pos/probar`,
      { method: "POST", body: JSON.stringify(data) },
    ),
};
