type BotResponse = Record<string, unknown>;
let lastCatalog: BotCatalog | null = null;

const baseUrl = () => (process.env.BOT_API_BASE_URL || "https://kiln.spb.ru").replace(/\/$/, "");

async function botRequest<T extends BotResponse>(path: string, init?: RequestInit): Promise<T> {
  const apiKey = process.env.BOT_API_KEY;
  if (!apiKey) throw new Error("BOT_API_KEY is not configured");
  const response = await fetch(`${baseUrl()}${path}`, {
    ...init,
    headers: { accept: "application/json", "content-type": "application/json", "x-api-key": apiKey, ...(init?.headers || {}) },
    cache: "no-store",
    signal: AbortSignal.timeout(25000)
  });
  const body = await response.json().catch(() => ({}));
  if (!response.ok) {
    const error = new Error(String(body.message || body.error || `Bot API returned ${response.status}`));
    Object.assign(error, { status: response.status, body });
    throw error;
  }
  return body as T;
}

export type BotCatalogItem = { sbis_id: string | number; position_id?: string | number; name: string; price: number; description?: string | null; image?: string | null; stock_left?: number | null; available: boolean | null; unavailable_text?: string | null; variant_of?: string | null };
export type BotCatalog = { ok: true; updated_at: string; stoplist?: { ok: boolean; stale?: boolean; updated_at?: string | null }; categories: { id: string | number; name: string; items: BotCatalogItem[] }[] };
export type BotOrderResponse = { ok: true; order_id: string; status: string; pay_url: string | null; track_url: string; tg_link: string | null; total: number; delivery_fee: number; moderation_id?: string | null };
export type BotOrderStatus = { ok: true; order_id: string; status: string; status_ru: string; paid: boolean; pay_url: string | null; can_pay?: boolean; can_cancel?: boolean; can_change_due?: boolean; completed?: boolean; chat_open?: boolean; track_url: string; eta?: string | null; due?: string | null; fulfillment?: "delivery" | "pickup" };
export type BotBookingResponse = { ok: true; rid: string; message: string; booking_url?: string; public_ref?: string };
export type BotInfo = {
  ok: true; name: string; address: string; coordinates: { lat: number; lon: number }; phone: string; tg_link: string;
  hours: { restaurant: { start: string; end: string }; orders: { start: string; end: string; note?: string }; booking: { start: string; end: string } };
  offhours_messages: { order: string; booking: string };
  delivery: { enabled: boolean; accepting_today?: boolean; receive_types?: string[]; cook_time_min?: number; assembly_time_min?: number; highload_cook_add_min?: number; highload_assembly_add_min?: number; highload_delivery_add_min?: number; order_cutoff_min?: number; source?: string; updated_at?: string; error?: string | null };
  pickup?: { enabled: boolean; min_order?: number; delivery_fee?: number };
  zones: { zone: number; id: number; name: string; min_order: number; delivery_fee: number; free_from: number | null; delivery_time_min?: number; thresholds?: { from: number; price: number }[] }[];
  server_time: string;
};
export type BotSbisItemCard = {
  ok: true; sbis_id: number; name: string | null; description: string | null;
  measure_unit: unknown; production_volume: number | null; production_measure: string | null;
  weight: number | null; weight_unit: string | null; weight_status: string;
  composition: { row_id: unknown; name: string | null; sbis_id: string | null; quantity: number | null; net_g: number | null; output: number | null; unit: string | null; depth: number | null; is_contents: boolean }[];
  composition_source: string; price_list: { id: number }; updated_at: string;
  nutrition: { calories_kcal: number | null; protein_g: number | null; fat_g: number | null; carbohydrate_g: number | null; basis: string | null; source: string; source_fields: string[]; calories_status: string };
};
export type BotSbisPrices = { ok: true; default_id: number; prices: { id: number; name: string; is_used?: boolean; is_archived?: boolean }[] };
export type BotDeliveryQuote = { ok: true; eligible?: boolean | null; normalized_address: string; zone: { zone: number; id: number; name: string; min_order: number; delivery_fee: number; free_from: number | null; delivery_time_min?: number; thresholds?: { from: number; price: number }[] } | null; order_subtotal?: number | null; shortfall?: number | null; delivery_fee: number; total_with_delivery: number | null };
export type BotAvailability = { ok: true; source_ok: boolean; stale: boolean; updated_at: string | null; items: { sbis_id: number; available: boolean; enough: boolean; stock_left: number | null; requested_qty: number; shortfall_qty: number }[] };
export type ClientMessage = { id: number; direction: "system" | "client" | "restaurant" | "note"; message_type?: "information" | "technical" | "warning" | "restaurant" | "client"; text: string; created_at: string };
export type BotOrderLine = { sbis_id?: string | number | null; site_item_id?: string | number | null; name?: string | null; quantity?: number | null; unit_price?: number | null; line_total?: number | null; n?: string | null; q?: number | null; p?: number | null; s?: number | null; image_url?: string | null };
export type BotOrderView = BotOrderStatus & {
  ok: true; kind?: "order"; completed?: boolean; chat_open?: boolean; total?: number; delivery_fee?: number;
  total_source?: "submitted_items";
  created_at?: string; due?: string | null; fulfillment?: "delivery" | "pickup"; address?: string | null;
  track_url?: string | null;
  items?: BotOrderLine[]; details_pending?: boolean; courier?: { name?: string | null; phone?: string | null };
  courier_map_url?: string | null; messages?: ClientMessage[];
  pending_change?: { id: number; proposed: { date_time: string }; requested_at: string } | null;
  can_edit_items?: boolean;
};
export type BotBookingView = {
  ok: true; kind: "booking"; booking_id: string; status: string; status_ru: string; name?: string | null;
  date_time: string; persons: number; comment?: string | null; completed: boolean; chat_open: boolean; can_change: boolean;
  pending_change?: { id: number; proposed: { date_time: string; persons: number }; requested_at: string } | null;
  messages: ClientMessage[]; calendar_url?: string | null;
};
export type BotHistory = {
  ok: true; phone: string;
  orders: { order_id: string; reference?: string; pending?: boolean; status: string; status_ru: string; paid: boolean; total: number | null; total_source?: "submitted_items"; delivery_fee: number | null; due: string | null; created_at: string; completed: boolean; public_ref: string; track_url: string; items?: BotOrderLine[]; details_pending?: boolean; can_pay?: boolean; can_cancel?: boolean; can_change_due?: boolean; can_edit_items?: boolean }[];
  bookings: { rid: string; dt: string; persons: number; status: string; status_ru: string; name: string; created_at: string; completed: boolean; public_ref: string }[];
};
export type BotOperatorDashboard = {
  ok: true;
  active: { orders: BotOperatorOrder[]; bookings: BotOperatorBooking[] };
  archive: { orders: BotOperatorOrder[]; bookings: BotOperatorBooking[] };
  orders: BotOperatorOrder[];
  bookings: BotOperatorBooking[];
};
export type BotOperatorOrder = { id: string; reference?: string; order_id?: string | null; status: string; status_ru: string; total: number | null; total_source?: "submitted_items"; paid: boolean; due: string | null; created_at: string; phone: string | null; name: string | null; completed: boolean; pending?: boolean; items?: BotOrderLine[]; details_pending?: boolean; delivery_provider_env?: "test" | "production" | null };
export type BotOperatorBooking = { id: string; status: string; status_ru: string; date_time: string; persons: number; created_at: string; phone: string | null; name: string | null; completed: boolean };
export type BotOperatorEntity = (BotOrderView & { reference?: string; failure_reason?: string | null; delivery_provider_env?: "test" | "production" | null; customer?: { name?: string | null; phone?: string | null; email?: string | null; telegram_registered?: boolean; max_registered?: boolean; access_pin?: string | null; entry_code?: string | null }; operator?: { notes: ClientMessage[]; actions: { action: string; label: string; value?: string }[] } }) | (BotBookingView & { customer?: { name?: string | null; phone?: string | null; contact_name?: string | null; contact_phone?: string | null; email?: string | null; telegram_registered?: boolean; max_registered?: boolean; access_pin?: string | null }; operator?: { notes: ClientMessage[]; actions: { action: string; label: string; value?: string }[] } });
export type BotDostavistaSettings = { ok: true; mode: "test" | "production"; configured: { test: boolean; production: boolean } };
export type BotMiniAppMe = { ok: true; role: "client" | "restaurant"; phone_linked: boolean; phone_masked?: string | null; mode: string };

let infoCache: { value: BotInfo; at: number } | null = null;
const INFO_TTL_MS = 10 * 60 * 1000;

/** GET /api/info with a 10-minute server-side cache (contract §3в). */
export async function getBotInfo() {
  if (infoCache && Date.now() - infoCache.at < INFO_TTL_MS) return infoCache.value;
  const info = await botRequest<BotInfo>("/api/info");
  infoCache = { value: info, at: Date.now() };
  return info;
}

export async function getBotCatalog() {
  try {
    const catalog = await botRequest<BotCatalog>("/api/catalog");
    lastCatalog = catalog;
    return catalog;
  } catch (error) {
    const status = (error as { status?: unknown }).status;
    if (status === 502 && lastCatalog) return lastCatalog;
    throw error;
  }
}
export function createBotOrder(payload: unknown) { return botRequest<BotOrderResponse>("/api/order", { method: "POST", body: JSON.stringify(payload) }); }
export function replaceBotPendingOrder(clientRef: string, payload: unknown) { return botRequest<BotOrderResponse>(`/api/order/${encodeURIComponent(clientRef)}`, { method: "PUT", body: JSON.stringify(payload) }); }
export function getBotOrder(clientRef: string) { return botRequest<BotOrderStatus>(`/api/order/${encodeURIComponent(clientRef)}`); }
export function createBotBooking(payload: unknown) {
  return botRequest<BotBookingResponse>("/api/booking", { method: "POST", body: JSON.stringify(payload) });
}
export function quoteBotDelivery(address: string, orderTotal: number | null, quoteMode: "conditions" | "checkout" = "checkout") {
  return botRequest<BotDeliveryQuote>("/api/delivery/quote", { method: "POST", body: JSON.stringify({ address, ...(orderTotal === null ? {} : { order_total: orderTotal }), quote_mode: quoteMode }) });
}
export function getBotAvailability(items: { sbis_id: number; qty: number }[]) {
  return botRequest<BotAvailability>("/api/catalog/availability", { method: "POST", body: JSON.stringify({ items }) });
}
export function sendBotSmsPin(phone: string, code: string) {
  return botRequest<{ ok: true }>("/api/sms/pin", { method: "POST", body: JSON.stringify({ phone, code }) });
}
/** POST /api/email/send — plain-text letter; PIN is generated by the site (contract §2а). */
export function sendBotEmail(to: string, subject: string, text: string) {
  return botRequest<{ ok: true }>("/api/email/send", { method: "POST", body: JSON.stringify({ to, subject, text }) });
}
export type BotLoginClaim = { ok: true; status: "pending" } | { ok: true; status: "claimed"; messenger: string; messenger_user_id: string; display_name: string | null; phone_confirmed: boolean; phone?: string };
/** GET /api/auth/claim/{nonce} — one-time messenger identity claim (contract §2в). */
export function getBotLoginClaim(nonce: string) {
  return botRequest<BotLoginClaim>(`/api/auth/claim/${encodeURIComponent(nonce)}`);
}
export function getBotHistory(phone: string, customerRef?: string) {
  const query = customerRef ? `?customer_ref=${encodeURIComponent(customerRef)}` : "";
  return botRequest<BotHistory>(`/api/history/${encodeURIComponent(phone)}${query}`);
}
export function getBotHistoryByEmail(email: string) {
  return botRequest<BotHistory>(`/api/history/email/${encodeURIComponent(email)}`);
}
export function setBotCustomerState(customerRef: string, state: "active" | "suspended", phone?: string | null, reasonCode = "operator_action") {
  return botRequest<{ ok: true }>("/api/pdn/customer/state", { method: "POST", body: JSON.stringify({ customer_ref: customerRef, state, phone: phone || undefined, reason_code: reasonCode }) });
}
export function getBotOrderView(reference: string) {
  return botRequest<BotOrderView>(`/api/client/order/${encodeURIComponent(reference)}`);
}
export function createBotOrderPayment(reference: string) {
  return botRequest<{ ok: true; payment_url: string }>(`/api/client/order/${encodeURIComponent(reference)}/payment`, { method: "POST", body: "{}" });
}
export function cancelBotOrder(reference: string) {
  return botRequest<{ ok: true; status: "canceled" }>(`/api/client/order/${encodeURIComponent(reference)}/cancel`, { method: "POST", body: "{}" });
}
export function requestBotOrderChange(reference: string, dateTime: string) {
  return botRequest<{ ok: true; status: "pending" }>(`/api/client/order/${encodeURIComponent(reference)}/change`, { method: "POST", body: JSON.stringify({ date_time: dateTime }) });
}
export function getBotBookingView(reference: string) {
  return botRequest<BotBookingView>(`/api/client/booking/${encodeURIComponent(reference)}`);
}
export function sendBotClientMessage(kind: "order" | "booking", reference: string, message: string) {
  return botRequest<{ ok: true }>(`/api/client/${kind}/${encodeURIComponent(reference)}/message`, { method: "POST", body: JSON.stringify({ text: message }) });
}
export function requestBotBookingChange(reference: string, dateTime: string, persons: number) {
  return botRequest<{ ok: true; status: "pending" }>(`/api/client/booking/${encodeURIComponent(reference)}/change`, { method: "POST", body: JSON.stringify({ date_time: dateTime, persons }) });
}
export function getBotOperatorDashboard(query = "", date = "") {
  const params = new URLSearchParams();
  if (query) params.set("q", query.slice(0, 128));
  if (date) params.set("date", date.slice(0, 10));
  return botRequest<BotOperatorDashboard>(`/api/operator/dashboard${params.toString() ? `?${params}` : ""}`);
}
export function getBotOperatorEntity(kind: "order" | "booking", id: string) {
  return botRequest<BotOperatorEntity>("/api/operator/entity", { method: "POST", body: JSON.stringify({ kind, id }) });
}
export function executeBotOperatorAction(kind: "order" | "booking", id: string, action: string, value = "") {
  return botRequest<{ ok: true; message: string }>("/api/operator/action", { method: "POST", body: JSON.stringify({ kind, id, action, value }) });
}
export function getBotDostavistaSettings() {
  return botRequest<BotDostavistaSettings>("/api/operator/settings/dostavista");
}
export function setBotDostavistaSettings(mode: "test" | "production", operatorId: string) {
  return botRequest<BotDostavistaSettings>("/api/operator/settings/dostavista", { method: "PATCH", body: JSON.stringify({ mode, operator_id: operatorId }) });
}
export function getBotMiniAppMe(cookie: string) {
  return botRequest<BotMiniAppMe>("/api/ma/me", { headers: { cookie } });
}
export function getBotSbisItem(sbisId: string) {
  return botRequest<BotSbisItemCard>(`/api/item/${encodeURIComponent(sbisId)}`);
}
export function getBotSbisPrices() {
  return botRequest<BotSbisPrices>("/api/sbis/prices");
}
export type BotPushResult = {
  ok: boolean; sbis_id: number; updated: string[]; skipped: string[];
  actual: { name: string | null; description: string | null; image_present: boolean } | null;
  errors: { fields: string[]; error: string }[];
};
/** POST /api/item/{sbis_id} — manual push of editorial fields (contract §3а). */
export function pushBotSbisItem(sbisId: string, payload: { name?: string; description?: string; image_base64?: string }) {
  return botRequest<BotPushResult>(`/api/item/${encodeURIComponent(sbisId)}`, { method: "POST", body: JSON.stringify(payload) });
}
