import { NextResponse } from "next/server";
import { readFile } from "node:fs/promises";
import path from "node:path";
import sharp from "sharp";
import { and, eq, isNull } from "drizzle-orm";
import { revalidatePath } from "next/cache";
import { db } from "@/db";
import { menuItemSbisLinks, menuItems, sbisCatalogItems } from "@/db/schema";
import { adminAuthorized } from "@/lib/admin-auth";
import { recordAudit } from "@/lib/audit";
import { pushBotSbisItem } from "@/lib/bot-api";

const PUSH_FIELDS = ["name", "description", "imageUrl"] as const;
type PushField = (typeof PUSH_FIELDS)[number];

export const runtime = "nodejs";

function fail(error: string, status = 400) { return NextResponse.json({ error }, { status }); }

/** Local WebP uploads are converted: the bot accepts only JPEG/PNG (contract §3а). */
async function localImageJpegBase64(imageUrl: string) {
  if (!imageUrl.startsWith("/uploads/")) throw new Error("в СБИС можно отправить только локальное фото сайта");
  const buffer = await readFile(path.join(process.cwd(), "public", imageUrl));
  const jpeg = await sharp(buffer).jpeg({ quality: 85 }).toBuffer();
  if (jpeg.byteLength > 5 * 1024 * 1024) throw new Error("фото после конвертации больше 5 МБ");
  return jpeg.toString("base64");
}

/**
 * Manual push of editorial fields to SBIS (§6.3): requires an active link and
 * a position in the current price, explicit confirmation, sends only
 * name/description/photo, re-reads the actual result and never silently
 * rolls back local data.
 */
export async function POST(request: Request) {
  if (!(await adminAuthorized(request, true))) return fail("Unauthorized", 401);
  if (!db) return fail("Database is not configured", 503);
  const body = await request.json() as { menuItemId?: unknown; fields?: unknown; confirm?: unknown };
  const menuItemId = Number(body.menuItemId);
  const fields = Array.isArray(body.fields) ? [...new Set(body.fields.filter((field): field is PushField => PUSH_FIELDS.includes(field as PushField)))] : [];
  if (!Number.isInteger(menuItemId) || !fields.length) return fail("Укажите карточку и хотя бы одно поле (name, description, imageUrl)");
  if (body.confirm !== true) return fail("Требуется повторное подтверждение оператора", 409);
  try {
    const [row] = await db.select({ item: menuItems, link: menuItemSbisLinks, sbis: sbisCatalogItems })
      .from(menuItems)
      .innerJoin(menuItemSbisLinks, and(eq(menuItemSbisLinks.menuItemId, menuItems.id), isNull(menuItemSbisLinks.unlinkedAt)))
      .innerJoin(sbisCatalogItems, eq(sbisCatalogItems.sbisId, menuItemSbisLinks.sbisId))
      .where(eq(menuItems.id, menuItemId))
      .limit(1);
    if (!row) return fail("У карточки нет активной связи СБИС", 409);
    if (!row.sbis.inCurrentPrice) return fail("Позиция отсутствует в актуальном прайсе СБИС", 409);

    const payload: { name?: string; description?: string; image_base64?: string } = {};
    const fieldErrors: { fields: string[]; error: string }[] = [];
    if (fields.includes("name")) payload.name = row.item.name.trim().slice(0, 200);
    if (fields.includes("description")) payload.description = (row.item.description || "").slice(0, 10000);
    if (fields.includes("imageUrl")) {
      try {
        if (!row.item.imageUrl) throw new Error("у карточки сайта не задано фото");
        payload.image_base64 = await localImageJpegBase64(row.item.imageUrl);
      } catch (error) {
        fieldErrors.push({ fields: ["image"], error: error instanceof Error ? error.message : "не удалось подготовить фото" });
      }
    }
    if (!Object.keys(payload).length) {
      return NextResponse.json({ ok: false, updated: [], skipped: [], errors: fieldErrors, verified: {} }, { status: 409 });
    }

    let result;
    try {
      result = await pushBotSbisItem(row.link.sbisId, payload);
    } catch (error) {
      const botErrors = (error as { body?: { errors?: { fields: string[]; error: string }[] } }).body?.errors;
      const errors = botErrors?.length ? botErrors : [{ fields: Object.keys(payload), error: "sbis_unavailable" }];
      await db.update(menuItemSbisLinks).set({ lastPushAt: new Date(), lastResult: `push:failed:${fields.join(",")}`, lastError: errors.map((entry) => entry.error).join("; ").slice(0, 500) }).where(eq(menuItemSbisLinks.id, row.link.id));
      await recordAudit("sbis_push", { entityType: "menu_item", entityId: menuItemId, details: { sbis_id: row.link.sbisId, fields, errors } });
      return NextResponse.json({ ok: false, updated: [], skipped: [], errors, verified: {} }, { status: 502 });
    }

    // §6.3.5–6: confirm the actual SBIS state per field; partial success is
    // visible, local data is not rolled back silently.
    const verified: Record<string, boolean | null> = {};
    if (result.actual) {
      if (payload.name !== undefined) verified.name = (result.actual.name || "") === payload.name;
      if (payload.description !== undefined) verified.description = (result.actual.description || "") === payload.description;
      if (payload.image_base64 !== undefined) verified.image = result.actual.image_present === true;
    }
    const errors = [...fieldErrors, ...result.errors];
    await db.update(menuItemSbisLinks).set({ lastPushAt: new Date(), lastResult: `push:${result.updated.join(",")}`, lastError: errors.length ? errors.map((entry) => entry.error).join("; ").slice(0, 500) : null }).where(eq(menuItemSbisLinks.id, row.link.id));
    await recordAudit("sbis_push", { entityType: "menu_item", entityId: menuItemId, details: { sbis_id: row.link.sbisId, fields, updated: result.updated, skipped: result.skipped, verified } });
    revalidatePath("/menu");
    return NextResponse.json({ ok: result.ok && !fieldErrors.length, updated: result.updated, skipped: result.skipped, errors, verified });
  } catch (error) {
    return fail(error instanceof Error ? error.message : "Не удалось выполнить push в СБИС", 503);
  }
}
