"use client";

import { useEffect, useMemo, useState } from "react";
import { FileSearch, Link2, Link2Off, X } from "lucide-react";
import { CursorTooltip } from "@/components/cursor-tooltip";

type CatalogItem = {
  sbis_id: string;
  name: string;
  price: number;
  available: boolean | null;
  variant_of: string | null;
  categoryId?: string;
  categoryName?: string;
};
type Catalog = {
  updated_at?: string;
  categories: { id: string; name: string; items: CatalogItem[] }[];
};
type LocalItem = {
  id: number;
  name: string;
  slug: string;
  archivedAt?: Date | string | null;
};
type Link = {
  id: number;
  menuItemId: number;
  sbisId: string;
  unlinkedAt: Date | string | null;
  unlinkReason: string | null;
};
type SyncState = {
  lastAttemptAt: Date | string | null;
  lastSuccessAt: Date | string | null;
  lastDurationMs: number | null;
  lastError: string | null;
  lastSnapshotId: string | null;
  sourceVersion: string | null;
  itemsCount: number;
  categoriesCount: number;
} | null;
type AuditEvent = {
  id: number;
  actor: string;
  action: string;
  entityType: string | null;
  entityId: string | null;
  createdAt: Date | string;
};
type SbisItemCard = {
  requestId: string;
  sbisId: number;
  name: string | null;
  weight: number | null;
  weightStatus: string;
  productionVolume: number | null;
  productionMeasure: string | null;
  composition: {
    name: string | null;
    quantity: number | null;
    net_g: number | null;
    output: number | null;
    unit: string | null;
  }[];
  nutrition: {
    calories_kcal: number | null;
    protein_g: number | null;
    fat_g: number | null;
    carbohydrate_g: number | null;
    source: string;
  };
  priceListId: number | null;
  updatedAt: string;
};
type SbisPrices = {
  defaultId: number;
  prices: {
    id: number;
    name: string;
    is_used?: boolean;
    is_archived?: boolean;
  }[];
};

const PULL_FIELDS = [
  { key: "name", label: "Название" },
  { key: "description", label: "Описание" },
  { key: "imageUrl", label: "Фото" },
  { key: "nutrition", label: "Калории и БЖУ" },
] as const;

const PUSH_FIELDS = [
  { key: "name", label: "Название" },
  { key: "description", label: "Описание" },
  { key: "imageUrl", label: "Фото" },
] as const;

function csrfHeaders() {
  const csrf = document.cookie
    .split("; ")
    .find((part) => part.startsWith("kiln-admin-csrf="))
    ?.split("=")[1];
  return csrf
    ? { "x-csrf-token": decodeURIComponent(csrf) }
    : ({} as Record<string, string>);
}

function formatTime(value: Date | string | null | undefined) {
  if (!value) return "нет данных";
  return new Date(value).toLocaleString("ru-RU");
}

export function SbisCatalogPanel({
  localItems,
  links: initialLinks,
  syncState,
  auditEvents = [],
  onChanged,
}: {
  localItems: LocalItem[];
  links: Link[];
  syncState: SyncState;
  auditEvents?: AuditEvent[];
  onChanged?: () => Promise<unknown>;
}) {
  const [catalog, setCatalog] = useState<Catalog | null>(null);
  const [links, setLinks] = useState(
    initialLinks.filter((link) => !link.unlinkedAt),
  );
  const [query, setQuery] = useState("");
  const [selected, setSelected] = useState<string[]>([]);
  const [selectedLocal, setSelectedLocal] = useState<Record<string, string>>(
    {},
  );
  const [manualItemId, setManualItemId] = useState("");
  const [manualSbisId, setManualSbisId] = useState("");
  const [message, setMessage] = useState("");
  const [loading, setLoading] = useState(false);
  const [cardId, setCardId] = useState<string | null>(null);
  const [cardCache, setCardCache] = useState<Record<string, SbisItemCard>>({});
  const [cardError, setCardError] = useState("");
  const [prices, setPrices] = useState<SbisPrices | null>(null);
  useEffect(() => {
    if (!cardId) return;
    const close = (event: KeyboardEvent) => {
      if (event.key === "Escape") setCardId(null);
    };
    window.addEventListener("keydown", close);
    return () => window.removeEventListener("keydown", close);
  }, [cardId]);

  async function loadCard(sbisId: string) {
    setCardId(sbisId);
    setCardError("");
    if (cardCache[sbisId]) return;
    setLoading(true);
    try {
      const response = await fetch(
        `/api/admin/menu/sbis-item?sbisId=${encodeURIComponent(sbisId)}`,
        { cache: "no-store" },
      );
      const body = (await response
        .json()
        .catch(() => ({}))) as Partial<SbisItemCard> & {
        error?: string;
        requestId?: string;
      };
      if (!response.ok)
        throw new Error(
          `${body.error || `Ошибка ${response.status}`}${body.requestId ? ` · код ${body.requestId}` : ""}`,
        );
      if (!Array.isArray(body.composition) || typeof body.sbisId !== "number")
        throw new Error(
          `Некорректный ответ карточки${body.requestId ? ` · код ${body.requestId}` : ""}`,
        );
      setCardCache((current) => ({
        ...current,
        [sbisId]: body as SbisItemCard,
      }));
    } catch (error) {
      setCardError(
        error instanceof Error ? error.message : "Карточка недоступна",
      );
    } finally {
      setLoading(false);
    }
  }

  async function loadPrices() {
    setLoading(true);
    setMessage("");
    try {
      const response = await fetch("/api/admin/menu/sbis-item?prices=1");
      const body = await response.json();
      if (!response.ok) throw new Error(body.error || "Прайсы недоступны");
      setPrices(body);
    } catch (error) {
      setMessage(error instanceof Error ? error.message : "Прайсы недоступны");
    } finally {
      setLoading(false);
    }
  }

  async function load() {
    setLoading(true);
    setMessage("");
    try {
      const response = await fetch("/api/admin/menu/catalog");
      const body = await response.json();
      if (!response.ok) throw new Error(body.error || "Каталог недоступен");
      setCatalog(body);
    } catch (error) {
      setMessage(error instanceof Error ? error.message : "Каталог недоступен");
    } finally {
      setLoading(false);
    }
  }

  const items = useMemo(
    () =>
      (catalog?.categories || [])
        .flatMap((category) =>
          category.items.map((item) => ({
            ...item,
            categoryName: category.name,
          })),
        )
        .filter((item) =>
          `${item.name} ${item.categoryName} ${item.sbis_id}`
            .toLowerCase()
            .includes(query.toLowerCase()),
        ),
    [catalog, query],
  );
  const activeLocalItems = localItems.filter(
    (item) =>
      !item.archivedAt && !links.some((link) => link.menuItemId === item.id),
  );

  async function linkItem(menuItemId: number, sbisId: string) {
    setLoading(true);
    setMessage("");
    try {
      const response = await fetch("/api/admin/menu/link", {
        method: "POST",
        headers: { "content-type": "application/json", ...csrfHeaders() },
        body: JSON.stringify({ menuItemId, sbisId }),
      });
      const body = await response.json();
      if (!response.ok) throw new Error(body.error || "Привязка не выполнена");
      setLinks((current) => [
        ...current,
        {
          id: Date.now(),
          menuItemId,
          sbisId,
          unlinkedAt: null,
          unlinkReason: null,
        },
      ]);
      setMessage(`Позиция ${sbisId} привязана`);
      await onChanged?.();
    } catch (error) {
      setMessage(
        error instanceof Error ? error.message : "Привязка не выполнена",
      );
    } finally {
      setLoading(false);
    }
  }

  async function unlinkItem(menuItemId: number) {
    setLoading(true);
    setMessage("");
    try {
      const response = await fetch("/api/admin/menu/link", {
        method: "DELETE",
        headers: { "content-type": "application/json", ...csrfHeaders() },
        body: JSON.stringify({ menuItemId, reason: "admin" }),
      });
      const body = await response.json();
      if (!response.ok) throw new Error(body.error || "Отвязка не выполнена");
      setLinks((current) =>
        current.filter((link) => link.menuItemId !== menuItemId),
      );
      setMessage("Позиция отвязана");
      await onChanged?.();
    } catch (error) {
      setMessage(
        error instanceof Error ? error.message : "Отвязка не выполнена",
      );
    } finally {
      setLoading(false);
    }
  }

  async function importSelected() {
    setLoading(true);
    try {
      const response = await fetch("/api/admin/menu/catalog", {
        method: "POST",
        headers: { "content-type": "application/json", ...csrfHeaders() },
        body: JSON.stringify({ sbisIds: selected }),
      });
      const body = await response.json();
      if (!response.ok) throw new Error(body.error || "Импорт не выполнен");
      setMessage(`Импортировано позиций: ${body.imported}`);
      setSelected([]);
      await onChanged?.();
    } catch (error) {
      setMessage(error instanceof Error ? error.message : "Импорт не выполнен");
    } finally {
      setLoading(false);
    }
  }

  async function pullFields(menuItemId: number, fields: string[]) {
    setLoading(true);
    setMessage("");
    try {
      const response = await fetch("/api/admin/menu/pull", {
        method: "POST",
        headers: { "content-type": "application/json", ...csrfHeaders() },
        body: JSON.stringify({ menuItemId, fields }),
      });
      const body = await response.json();
      if (!response.ok) throw new Error(body.error || "Pull не выполнен");
      setMessage(`Из СБИС взяты поля: ${fields.join(", ")}`);
    } catch (error) {
      setMessage(error instanceof Error ? error.message : "Pull не выполнен");
    } finally {
      setLoading(false);
    }
  }

  async function pushFields(
    menuItemId: number,
    local: LocalItem,
    fields: string[],
  ) {
    // §6.3: push requires an explicit second confirmation of the payload.
    if (
      !window.confirm(
        `Отправить в СБИС (${local.name}): ${fields.join(", ")}? Изменение запишется в карточку номенклатуры СБИС.`,
      )
    )
      return;
    setLoading(true);
    setMessage("");
    try {
      const response = await fetch("/api/admin/menu/push", {
        method: "POST",
        headers: { "content-type": "application/json", ...csrfHeaders() },
        body: JSON.stringify({ menuItemId, fields, confirm: true }),
      });
      const body = (await response.json()) as {
        updated?: string[];
        errors?: { fields: string[]; error: string }[];
        verified?: Record<string, boolean | null>;
      };
      const parts: string[] = [];
      if (body.updated?.length)
        parts.push(`записано в СБИС: ${body.updated.join(", ")}`);
      const unverified = Object.entries(body.verified || {})
        .filter(([, value]) => value === false)
        .map(([field]) => field);
      if (unverified.length)
        parts.push(`не подтвердилось перечитыванием: ${unverified.join(", ")}`);
      if (body.errors?.length)
        parts.push(
          `ошибки: ${body.errors.map((entry) => `${entry.fields.join("/")} — ${entry.error}`).join("; ")}`,
        );
      if (!parts.length) parts.push("Ничего не изменено");
      setMessage(parts.join(" · "));
      if (!response.ok && !body.updated?.length)
        throw new Error(body.errors?.[0]?.error || "Push не выполнен");
    } catch (error) {
      setMessage(error instanceof Error ? error.message : "Push не выполнен");
    } finally {
      setLoading(false);
    }
  }

  const successAgeMinutes = syncState?.lastSuccessAt
    ? Math.floor(
        (Date.now() - new Date(syncState.lastSuccessAt).getTime()) / 60000,
      )
    : null;
  const stale = successAgeMinutes === null || successAgeMinutes >= 3;
  const card = cardId ? cardCache[cardId] : null;
  const cardLink = cardId
    ? links.find((candidate) => candidate.sbisId === cardId)
    : null;
  const cardLocal = cardLink
    ? localItems.find((candidate) => candidate.id === cardLink.menuItemId)
    : null;

  return (
    <section className="mt-8 card p-5">
      <div className="flex flex-wrap items-start justify-between gap-3">
        <div>
          <p className="eyebrow">Источник каталога</p>
          <h2 className="text-2xl">Каталог СБИС</h2>
          <p className="mt-2 text-sm text-kiln-milk/60">
            Цена, вес и наличие читаются из актуального каталога. Редакционные
            поля остаются на сайте.
          </p>
        </div>
        <div className="flex flex-wrap gap-2">
          <button
            className="button button-secondary"
            onClick={loadPrices}
            disabled={loading}
          >
            {loading ? "Загрузка…" : "Прайсы СБИС"}
          </button>
          <button
            className="button button-secondary"
            onClick={load}
            disabled={loading}
          >
            {loading ? "Загрузка…" : "Загрузить каталог"}
          </button>
        </div>
      </div>
      {prices ? (
        <p className="mt-3 text-xs text-kiln-milk/60">
          Прайс по умолчанию (SBIS_PRICE_ID бота): {prices.defaultId}
          {prices.prices.find((price) => price.id === prices.defaultId)?.name
            ? ` — ${prices.prices.find((price) => price.id === prices.defaultId)!.name}`
            : ""}{" "}
          · доступно прайсов: {prices.prices.length}. Переключение выполняется
          настройкой бота с последующим обновлением каталога.
        </p>
      ) : null}
      <div className="mt-4 grid gap-2 rounded border border-white/10 p-3 text-sm">
        <div className="flex flex-wrap items-center gap-2">
          <span
            className={
              stale
                ? "font-semibold text-kiln-flame"
                : "font-semibold text-kiln-milk"
            }
          >
            {stale ? "Данные устарели" : "Каталог свежий"}
          </span>
          {successAgeMinutes !== null ? (
            <span className="text-kiln-milk/60">
              · снимок {successAgeMinutes} мин назад
            </span>
          ) : (
            <span className="text-kiln-milk/60">· успешных снимков нет</span>
          )}
          {syncState ? (
            <span className="text-kiln-milk/60">
              · позиций: {syncState.itemsCount} · категорий:{" "}
              {syncState.categoriesCount} · снимок{" "}
              {(syncState.lastSnapshotId || "").slice(0, 8) || "—"}
            </span>
          ) : null}
        </div>
        <div className="text-xs text-kiln-milk/55">
          Последняя попытка: {formatTime(syncState?.lastAttemptAt)} · последний
          успех: {formatTime(syncState?.lastSuccessAt)}
          {syncState?.lastDurationMs !== null &&
          syncState?.lastDurationMs !== undefined
            ? ` · ${syncState.lastDurationMs} мс`
            : ""}
        </div>
        {syncState?.lastError ? (
          <div className="text-xs text-kiln-flame">
            Ошибка синхронизации: {syncState.lastError}
          </div>
        ) : null}
      </div>
      <div className="mt-5 grid gap-3 rounded border border-white/10 p-3">
        <p className="text-sm font-semibold">Ручная привязка по `sbis_id`</p>
        <div className="grid gap-3 md:grid-cols-[1fr_1fr_auto]">
          <select
            className="input"
            value={manualItemId}
            onChange={(event) => setManualItemId(event.target.value)}
          >
            <option value="">Карточка сайта</option>
            {activeLocalItems.map((item) => (
              <option key={item.id} value={item.id}>
                {item.name} · #{item.id}
              </option>
            ))}
          </select>
          <input
            className="input"
            value={manualSbisId}
            onChange={(event) => setManualSbisId(event.target.value)}
            placeholder="sbis_id из актуального каталога"
          />
          <button
            className="button button-primary"
            disabled={loading || !manualItemId || !manualSbisId}
            onClick={() => linkItem(Number(manualItemId), manualSbisId.trim())}
          >
            Привязать
          </button>
        </div>
      </div>
      {catalog ? (
        <>
          <div className="mt-4 flex flex-wrap gap-3">
            <input
              className="input min-w-64 flex-1"
              placeholder="Поиск по названию, категории или ID"
              value={query}
              onChange={(event) => setQuery(event.target.value)}
            />
            <button
              className="button button-primary"
              disabled={loading || !selected.length}
              onClick={importSelected}
            >
              Добавить выбранные ({selected.length})
            </button>
          </div>
          <div className="mt-4 max-h-[36rem] overflow-auto rounded border border-white/10">
            {items.map((item) => {
              const link = links.find(
                (candidate) => candidate.sbisId === item.sbis_id,
              );
              const local = link
                ? localItems.find(
                    (candidate) => candidate.id === link.menuItemId,
                  )
                : null;
              const checked = selected.includes(item.sbis_id);
              const toggle = () =>
                setSelected((current) =>
                  current.includes(item.sbis_id)
                    ? current.filter((id) => id !== item.sbis_id)
                    : [...current, item.sbis_id],
                );
              return (
                <div
                  key={item.sbis_id}
                  className="grid grid-cols-[auto_minmax(0,1fr)_minmax(8rem,0.8fr)_auto_auto] items-center gap-2 border-b border-white/10 p-2 last:border-0"
                >
                  <input
                    type="checkbox"
                    checked={checked}
                    onChange={toggle}
                    aria-label={`Выбрать ${item.name}`}
                  />
                  <CursorTooltip
                    text={`${item.name} · ${item.categoryName} · СБИС ${item.sbis_id} · ${item.price} ₽`}
                    className="min-w-0"
                  >
                    <button
                      type="button"
                      className="block w-full truncate text-left text-sm"
                      onClick={toggle}
                      aria-pressed={checked}
                    >
                      {item.name}
                    </button>
                  </CursorTooltip>
                  {link ? (
                    <CursorTooltip
                      text={`Привязано к ${local?.name || `карточке #${link.menuItemId}`}`}
                      className="min-w-0"
                    >
                      <span className="block truncate rounded bg-kiln-flame/10 px-2 py-2 text-xs">
                        {local?.name || `#${link.menuItemId}`}
                      </span>
                    </CursorTooltip>
                  ) : (
                    <select
                      className="input min-w-0 w-full text-sm"
                      aria-label={`Карточка сайта для ${item.name}`}
                      value={selectedLocal[item.sbis_id] || ""}
                      onChange={(event) =>
                        setSelectedLocal((current) => ({
                          ...current,
                          [item.sbis_id]: event.target.value,
                        }))
                      }
                    >
                      <option value="">Карточка сайта</option>
                      {activeLocalItems.map((candidate) => (
                        <option key={candidate.id} value={candidate.id}>
                          {candidate.name} · #{candidate.id}
                        </option>
                      ))}
                    </select>
                  )}
                  <CursorTooltip
                    text={
                      link
                        ? "Отвязать от карточки сайта"
                        : "Привязать к позиции из СБИС"
                    }
                  >
                    <button
                      type="button"
                      className="icon-button"
                      aria-label={
                        link
                          ? "Отвязать от карточки сайта"
                          : "Привязать к позиции из СБИС"
                      }
                      disabled={
                        loading || (!link && !selectedLocal[item.sbis_id])
                      }
                      onClick={() =>
                        link
                          ? unlinkItem(link.menuItemId)
                          : linkItem(
                              Number(selectedLocal[item.sbis_id]),
                              item.sbis_id,
                            )
                      }
                    >
                      {link ? <Link2Off size={17} /> : <Link2 size={17} />}
                    </button>
                  </CursorTooltip>
                  <CursorTooltip text="Открыть карточку СБИС">
                    <button
                      type="button"
                      className="icon-button"
                      aria-label={`Открыть карточку СБИС ${item.name}`}
                      onClick={() => void loadCard(item.sbis_id)}
                    >
                      <FileSearch size={17} />
                    </button>
                  </CursorTooltip>
                </div>
              );
            })}
          </div>
        </>
      ) : null}
      {auditEvents.length ? (
        <div className="mt-5 rounded border border-white/10 p-3">
          <p className="text-sm font-semibold">
            Журнал административных операций
          </p>
          <div className="mt-2 grid max-h-56 gap-1 overflow-auto text-xs text-kiln-milk/60">
            {auditEvents.map((event) => (
              <span key={event.id}>
                {new Date(event.createdAt).toLocaleString("ru-RU")} ·{" "}
                {event.actor} · {event.action}
                {event.entityType
                  ? ` · ${event.entityType}${event.entityId ? ` #${event.entityId}` : ""}`
                  : ""}
              </span>
            ))}
          </div>
        </div>
      ) : null}
      {message ? (
        <p className="mt-3 text-sm text-kiln-flame">{message}</p>
      ) : null}
      {cardId ? (
        <div
          className="auth-modal-backdrop"
          role="presentation"
          onMouseDown={(event) =>
            event.target === event.currentTarget && setCardId(null)
          }
        >
          <section
            className="card max-h-[90dvh] w-full max-w-4xl overflow-y-auto p-6"
            role="dialog"
            aria-modal="true"
            aria-labelledby="sbis-card-title"
          >
            <div className="flex items-start justify-between gap-3">
              <div>
                <p className="eyebrow">Карточка СБИС</p>
                <h2 id="sbis-card-title" className="text-2xl">
                  {card?.name || `Позиция ${cardId}`}
                </h2>
              </div>
              <button
                type="button"
                className="icon-button shrink-0"
                aria-label="Закрыть карточку СБИС"
                onClick={() => setCardId(null)}
              >
                <X size={18} />
              </button>
            </div>
            {cardError ? (
              <div className="mt-5 rounded border border-kiln-flame/30 bg-kiln-flame/10 p-4">
                <p>{cardError}</p>
                <button
                  className="button button-secondary mt-3"
                  onClick={() => {
                    setCardCache((current) => {
                      const next = { ...current };
                      delete next[cardId];
                      return next;
                    });
                    void loadCard(cardId);
                  }}
                >
                  Повторить
                </button>
              </div>
            ) : !card ? (
              <p className="mt-5 muted">Загружаем карточку…</p>
            ) : (
              <div className="mt-5 grid gap-4 text-sm">
                <div className="flex flex-wrap gap-4">
                  <span>
                    Вес:{" "}
                    {card.weight !== null ? `${card.weight} г` : "нет выхода"}
                  </span>
                  {card.productionVolume !== null ? (
                    <span>
                      Объём: {card.productionVolume}{" "}
                      {card.productionMeasure || ""}
                    </span>
                  ) : null}
                  <span>Прайс: {card.priceListId ?? "—"}</span>
                </div>
                <div className="grid grid-cols-2 gap-2 rounded border border-white/10 p-3 sm:grid-cols-4">
                  <span>Ккал: {card.nutrition.calories_kcal ?? "—"}</span>
                  <span>Белки: {card.nutrition.protein_g ?? "—"}</span>
                  <span>Жиры: {card.nutrition.fat_g ?? "—"}</span>
                  <span>Углеводы: {card.nutrition.carbohydrate_g ?? "—"}</span>
                </div>
                {card.composition.length ? (
                  <div className="overflow-x-auto">
                    <table className="w-full text-left">
                      <thead>
                        <tr>
                          <th className="pr-3">Состав</th>
                          <th className="pr-3">Кол-во</th>
                          <th className="pr-3">Нетто</th>
                          <th>Выход</th>
                        </tr>
                      </thead>
                      <tbody>
                        {card.composition.map((row, index) => (
                          <tr key={index} className="border-t border-white/10">
                            <td className="py-2 pr-3">{row.name ?? "—"}</td>
                            <td className="pr-3">{row.quantity ?? "—"}</td>
                            <td className="pr-3">{row.net_g ?? "—"}</td>
                            <td>{row.output ?? "—"}</td>
                          </tr>
                        ))}
                      </tbody>
                    </table>
                  </div>
                ) : (
                  <p className="muted">Состав в СБИС не задан.</p>
                )}
                {cardLink ? (
                  <div className="grid gap-3 border-t border-white/10 pt-4">
                    <p>
                      Привязано к карточке сайта:{" "}
                      <strong>
                        {cardLocal?.name || `#${cardLink.menuItemId}`}
                      </strong>
                    </p>
                    <div className="flex flex-wrap gap-2">
                      {PULL_FIELDS.map((field) => (
                        <button
                          key={field.key}
                          className="button button-secondary"
                          disabled={loading}
                          onClick={() =>
                            pullFields(cardLink.menuItemId, [field.key])
                          }
                        >
                          ↧ {field.label}
                        </button>
                      ))}
                      {PUSH_FIELDS.map((field) =>
                        cardLocal ? (
                          <button
                            key={field.key}
                            className="button button-secondary"
                            disabled={loading}
                            onClick={() =>
                              pushFields(cardLink.menuItemId, cardLocal, [
                                field.key,
                              ])
                            }
                          >
                            ↑ {field.label}
                          </button>
                        ) : null,
                      )}
                    </div>
                  </div>
                ) : null}
                <p className="text-xs text-kiln-milk/45">
                  Код запроса: {card.requestId}
                </p>
              </div>
            )}
          </section>
        </div>
      ) : null}
    </section>
  );
}
