import { chromium } from "playwright";

const BASE = "http://localhost:5174/";
const results = [];
const pass = (n, d = "") => { results.push(["PASS", n, d]); console.log("PASS ::", n, d); };
const fail = (n, d = "") => { results.push(["FAIL", n, d]); console.log("FAIL ::", n, d); };

const b = await chromium.launch({ headless: true });
const ctx = await b.newContext({ viewport: { width: 1600, height: 900 } });
const p = await ctx.newPage();

const api = [];
p.on("request", (r) => { if (r.url().includes("/api/")) api.push({ m: r.method(), u: r.url() }); });
const apiResp = [];
p.on("response", (r) => { if (r.url().includes("/api/")) apiResp.push({ m: r.request().method(), u: r.url(), s: r.status() }); });
const consoleErrors = [];
p.on("console", (m) => { if (m.type() === "error") consoleErrors.push(m.text()); });
p.on("pageerror", (e) => consoleErrors.push("PAGEERROR: " + e.message));
// авто-принятие window.confirm (для удаления)
p.on("dialog", (d) => d.accept());

const tab = (name) => p.getByRole("tab", { name, exact: true });

try {
  await p.goto(BASE, { waitUntil: "networkidle", timeout: 30000 });
  await p.waitForTimeout(800);

  // ---- A: 4 вкладки ----
  for (const t of ["Планы", "Карта", "Редактор", "Заявки"]) {
    (await tab(t).count()) > 0 ? pass(`tab:${t}`) : fail(`tab:${t}`, "не найдена");
  }

  // ---- B: Карта — дефолтные слои ----
  await tab("Карта").click();
  await p.waitForTimeout(600);
  const toggles = p.locator(".tgu-map-toolbar__toggles button");
  const n = await toggles.count();
  const labels = [];
  for (let i = 0; i < n; i++) {
    const el = toggles.nth(i);
    labels.push({ t: (await el.innerText()).trim(), active: (await el.getAttribute("class") || "").includes("is-active") });
  }
  console.log("LAYER_TOGGLES", JSON.stringify(labels));
  const wanted = ["Работы плана", "Станции", "Заявки на съёмку"];
  const got = labels.map((l) => l.t);
  JSON.stringify(got) === JSON.stringify(wanted)
    ? pass("map:layers-picker", "ровно planWorks/stations/requests")
    : fail("map:layers-picker", `got=${JSON.stringify(got)}`);
  labels.every((l) => l.active)
    ? pass("map:layers-default-on", "все 3 активны")
    : fail("map:layers-default-on", JSON.stringify(labels));
  const forbidden = got.some((t) => /трек|track|полос|swath|маркер|spacecraft/i.test(t));
  forbidden ? fail("map:no-tracks-swath-markers", "найден лишний слой") : pass("map:no-tracks-swath-markers");

  // ---- C: Заявки — загрузка ----
  await tab("Заявки").click();
  await p.waitForTimeout(400);
  const before = apiResp.length;
  await p.getByRole("button", { name: "Загрузить", exact: true }).click();
  await p.waitForResponse((r) => r.url().includes("/v1/requests/map"), { timeout: 15000 }).catch(() => {});
  await p.waitForTimeout(800);
  const mapCall = apiResp.find((r) => r.u.includes("/v1/requests/map"));
  mapCall && mapCall.s === 200 ? pass("requests:load", `GET map ${mapCall.s}`) : fail("requests:load", JSON.stringify(mapCall));
  const subtitle = await p.locator(".tgu-map-toolbar__subtitle").first().innerText().catch(() => "");
  console.log("REQUESTS_SUBTITLE", subtitle);

  // ---- D: создание + валидации ----
  await p.getByRole("button", { name: "+ Создать заявку", exact: true }).click();
  await p.waitForTimeout(300);
  const form = p.locator(".tgu-requests-form");
  const submitBtn = form.getByRole("button", { name: "Создать заявку", exact: true });
  const alert = form.locator(".tgu-alert--danger");

  // d1: до рисования — кнопка disabled, подсказка про полигон
  (await submitBtn.isDisabled()) ? pass("create:disabled-before-draw") : fail("create:disabled-before-draw");
  console.log("ALERT_INITIAL", await alert.innerText().catch(() => "(нет)"));

  // d2: рисуем полигон 3 кликами по карте
  // :visible — keep-alive держит смонтированными карты сразу нескольких вкладок
  const map = p.locator(".tgu-2d-map:visible");
  const box = await map.boundingBox();
  const pts = [
    { x: box.width * 0.45, y: box.height * 0.40 },
    { x: box.width * 0.60, y: box.height * 0.40 },
    { x: box.width * 0.52, y: box.height * 0.55 }
  ];
  for (const pt of pts) { await map.click({ position: pt }); await p.waitForTimeout(150); }
  const vtext = await form.locator(".tgu-requests-form__geo-status").innerText().catch(() => "");
  console.log("VERTEX_TEXT", vtext);
  /Вершин:\s*3/.test(vtext) ? pass("create:3-vertices") : fail("create:3-vertices", vtext);
  await form.getByRole("button", { name: "Завершить", exact: true }).click();
  await p.waitForTimeout(300);

  // d3: имя пустое -> валидация
  let a = await alert.innerText().catch(() => "");
  /название/i.test(a) && (await submitBtn.isDisabled()) ? pass("validate:empty-name", a) : fail("validate:empty-name", a);

  // заполняем имя
  await form.getByPlaceholder("Название задания").fill("E2E-VERIFY");
  await p.waitForTimeout(200);

  // d4: оба блока выключены
  const opticsCb = form.locator("label.tgu-requests-form__block-toggle", { hasText: "Оптическая" }).getByRole("checkbox");
  await opticsCb.uncheck();
  await p.waitForTimeout(200);
  a = await alert.innerText().catch(() => "");
  /хотя бы один блок/i.test(a) && (await submitBtn.isDisabled()) ? pass("validate:no-optics-no-rsa", a) : fail("validate:no-optics-no-rsa", a);
  await opticsCb.check();
  await p.waitForTimeout(200);

  // d5: from >= to
  const begin = form.locator('input[type="datetime-local"]').nth(0);
  const end = form.locator('input[type="datetime-local"]').nth(1);
  const beginVal = await begin.inputValue();
  await end.fill("2000-01-01T00:00");
  await p.waitForTimeout(200);
  a = await alert.innerText().catch(() => "");
  /раньше конца/i.test(a) && (await submitBtn.isDisabled()) ? pass("validate:from-after-to", a) : fail("validate:from-after-to", a);
  // восстановить корректный конец
  await end.fill("2100-01-01T00:00");
  await p.waitForTimeout(200);

  // d6: submit -> POST 201 + новая заявка
  (await submitBtn.isEnabled()) ? pass("create:enabled-when-valid") : fail("create:enabled-when-valid");
  const postBefore = apiResp.length;
  await submitBtn.click();
  const postResp = await p.waitForResponse((r) => r.url().includes("/v1/requests") && r.request().method() === "POST", { timeout: 15000 }).catch(() => null);
  await p.waitForTimeout(1000);
  if (postResp && (postResp.status() === 201 || postResp.status() === 200)) {
    pass("create:POST", `status ${postResp.status()}`);
    // проверим тело запроса: WKT lon lat + id
    const reqBody = postResp.request().postDataJSON?.() || JSON.parse(postResp.request().postData() || "{}");
    const wktOk = /POLYGON\s*\(\(/.test(reqBody.geometry || "");
    const idOk = /^[0-9a-f-]{36}$/i.test(reqBody.id || "");
    wktOk && idOk ? pass("create:payload", `wkt+uuid ok`) : fail("create:payload", JSON.stringify({ id: reqBody.id, geo: (reqBody.geometry||"").slice(0,30) }));
    console.log("CREATE_WKT", (reqBody.geometry || "").slice(0, 80));
  } else {
    fail("create:POST", postResp ? `status ${postResp.status()}` : "нет ответа");
  }
  const noticeTxt = await p.locator(".tgu-alert--success").innerText().catch(() => "");
  /создана/i.test(noticeTxt) ? pass("create:notice", noticeTxt) : fail("create:notice", noticeTxt || "(нет)");

  // ---- E: детали + удаление ----
  // клик по нарисованному полигону -> панель деталей. Свипаем точки внутри треугольника,
  // т.к. точное соответствие пиксель↔гео зависит от проекции.
  const delBtn = p.getByRole("button", { name: /Удалить заявку/, exact: false });
  const rows = p.locator(".tgu-map-request-row");
  const detailsHeading = p.locator("aside .tgu-panel-heading span", { hasText: "Заявки в точке" });
  const tip = p.locator(".tgu-2d-map__tooltip");
  // hover-свип: ищем пиксель, где появляется тултип нашей заявки (надёжно к проекции/виду)
  const mbox = await map.boundingBox();
  let target = null;
  outer:
  for (let gx = 0.40; gx <= 0.65; gx += 0.02) {
    for (let gy = 0.35; gy <= 0.58; gy += 0.02) {
      await p.mouse.move(mbox.x + mbox.width * gx, mbox.y + mbox.height * gy);
      await p.waitForTimeout(25);
      if ((await tip.count()) > 0 && /E2E-VERIFY/.test(await tip.innerText().catch(() => ""))) {
        target = { x: mbox.width * gx, y: mbox.height * gy };
        break outer;
      }
    }
  }
  let opened = false;
  if (target) {
    await map.click({ position: target });
    await p.waitForTimeout(400);
    if ((await delBtn.count()) > 0 || (await detailsHeading.count()) > 0) opened = true;
  }
  if (opened) {
    // если в точке несколько заявок — выбрать строку, чтобы появилась кнопка удаления
    if ((await delBtn.count()) === 0 && (await rows.count()) > 0) {
      await rows.first().click();
      await p.waitForTimeout(300);
    }
    pass("details:open", `панель деталей открылась (строк в точке: ${await rows.count() || 1})`);

    const initialDel = p.getByRole("button", { name: "Удалить заявку", exact: true });
    const confirmTitle = p.locator(".tgu-requests-confirm__title", { hasText: "Удалить заявку?" });
    const yesBtn = p.getByRole("button", { name: "Да, удалить", exact: true });
    const noBtn = p.getByRole("button", { name: "Нет, не удалять", exact: true });

    // шаг 1: клик «Удалить заявку» -> появляется панель подтверждения с Да/Нет
    await initialDel.first().click();
    await p.waitForTimeout(250);
    (await confirmTitle.count()) > 0 && (await yesBtn.count()) > 0 && (await noBtn.count()) > 0
      ? pass("delete:confirm-panel", "панель «Удалить заявку?» с «Да, удалить»/«Нет, не удалять»")
      : fail("delete:confirm-panel", `title=${await confirmTitle.count()} yes=${await yesBtn.count()} no=${await noBtn.count()}`);

    // «Нет, не удалять» — не шлёт DELETE и закрывает подтверждение
    const delCallsBefore = apiResp.filter((r) => r.m === "DELETE").length;
    await noBtn.click();
    await p.waitForTimeout(300);
    (await confirmTitle.count()) === 0 && apiResp.filter((r) => r.m === "DELETE").length === delCallsBefore
      ? pass("delete:cancel", "«Нет, не удалять» отменяет без DELETE")
      : fail("delete:cancel");

    // «Да, удалить» -> DELETE
    await initialDel.first().click();
    await p.waitForTimeout(200);
    await yesBtn.click();
    const delResp = await p.waitForResponse((r) => r.url().includes("/v1/requests/") && r.request().method() === "DELETE", { timeout: 15000 }).catch(() => null);
    await p.waitForTimeout(800);
    delResp && (delResp.status() === 200 || delResp.status() === 204)
      ? pass("delete:DELETE", `status ${delResp.status()}`)
      : fail("delete:DELETE", delResp ? `status ${delResp.status()}` : "нет DELETE-запроса");
  } else {
    fail("details:open", "панель деталей не открылась (не попал по полигону)");
  }

  // ---- console errors ----
  consoleErrors.length === 0 ? pass("no-console-errors") : fail("no-console-errors", JSON.stringify(consoleErrors.slice(0, 8)));

} catch (e) {
  fail("SCRIPT", e.message);
  console.log(e.stack);
} finally {
  console.log("\n=== API CALLS (последние 25) ===");
  for (const r of apiResp.slice(-25)) console.log(r.s, r.m, r.u.replace("http://localhost:5174", ""));
  const npass = results.filter((r) => r[0] === "PASS").length;
  const nfail = results.filter((r) => r[0] === "FAIL").length;
  console.log(`\n=== SUMMARY: ${npass} PASS / ${nfail} FAIL ===`);
  await b.close();
  process.exit(nfail > 0 ? 1 : 0);
}
