#!/usr/bin/env node
import { createRequire } from "node:module";
import fs from "node:fs";
import path from "node:path";
import process from "node:process";
import { fileURLToPath } from "node:url";

const require = createRequire(import.meta.url);
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");

function loadPlaywright() {
  const home = process.env.HOME || "";
  const candidates = [
    "playwright",
    "playwright-core",
    process.env.PLAYWRIGHT_CORE_MODULE,
    home ? path.join(home, ".agents/skills/playwright/node_modules/playwright-core") : "",
  ].filter(Boolean);
  for (const candidate of candidates) {
    try {
      return require(candidate);
    } catch {
      // Try the next local Playwright installation.
    }
  }
  throw new Error("Playwright module not found");
}

function env(name, fallback = "") {
  const value = process.env[name];
  return value && value.trim() ? value.trim() : fallback;
}

function authHeaders() {
  const explicit = env("DETMIR_PORTAL_SMOKE_AUTH_HEADER");
  if (explicit) return { Authorization: explicit };
  const basic = env("DETMIR_PORTAL_SMOKE_BASIC_AUTH", env("DETMIR_BASIC_AUTH"));
  if (basic) return { Authorization: `Basic ${basic}` };
  return {};
}

function extractDataTabs(html) {
  return [...html.matchAll(/data-tab=["']([^"']+)["']/g)].map((match) => match[1]);
}

function containsText(text, marker) {
  return String(text || "").toLocaleLowerCase("ru-RU").includes(String(marker || "").toLocaleLowerCase("ru-RU"));
}

function securityEventsMode(summary) {
  const s = summary || {};
  if (!summary || s.backend === "disabled" || s.status === "disabled") return "disabled";
  if (s.fallback_used) return "fallback";
  return "available";
}

function expectedSecurityEventsText(mode) {
  if (mode === "disabled") return "Источник событий безопасности отключён";
  if (mode === "fallback") return "События безопасности временно недоступны";
  if (mode === "available") return "События безопасности доступны";
  return "";
}

function forbiddenExecutiveTerms(text) {
  const forbidden = [
    "Trust KPI",
    "Business Risk",
    "Risk Narrative",
    "Security Events",
    "Incident Candidate",
    "Coverage SLA",
    "ERROR",
    "EMPTY",
    "STALE",
    "SECURITY_EVENTS_BACKEND",
    "CLICKHOUSE_*",
    "ClickHouse",
  ];
  return forbidden.filter((term) => containsText(text, term));
}

function assertStaticTabHandlers() {
  const index = fs.readFileSync(path.join(root, "adk-rust/crates/detmir-portal/src/static/index.html"), "utf8");
  const app = fs.readFileSync(path.join(root, "adk-rust/crates/detmir-portal/src/static/app.js"), "utf8");
  const tabs = [...new Set(extractDataTabs(index))];
  const missing = tabs.filter((tab) => !app.includes(`state.tab === "${tab}"`));
  if (missing.length) {
    throw new Error(`data-tab without app.js handler: ${missing.join(", ")}`);
  }
  const loadingTerms = [
    "Загрузка данных",
    "Данные готовы",
    "Данные отсутствуют",
    "Данные устарели",
    "Ошибка получения данных",
    "Получение данных",
    "Расчёт показателей",
    "Формирование главного вывода",
    "Подготовка разделов",
  ];
  const missingLoadingTerms = loadingTerms.filter((term) => !app.includes(term) && !index.includes(term));
  if (missingLoadingTerms.length) {
    throw new Error(`missing loading/localization terms: ${missingLoadingTerms.join(", ")}`);
  }
  return tabs;
}

const expectedTabs = [
  { tab: "operator", label: "Обзор", marker: "Представление руководителя" },
  { tab: "employees", label: "Сотрудники", marker: "Карточки сотрудников" },
  { tab: "departments", label: "Подразделения", marker: "Рейтинг подразделений" },
  { tab: "owner", label: "Риски", marker: "Контроль безопасности" },
  { tab: "incidents", label: "Расследования", marker: "Расследования и доказательная база" },
  { tab: "perimeter", label: "Сетевой периметр", marker: "Состояние доступных сигналов" },
  { tab: "reports", label: "Отчеты", marker: "Срезы отчета" },
  { tab: "settings", label: "Настройки", marker: "Параметры расчета" },
];

let smokeStep = "start";

async function main() {
  const staticTabs = assertStaticTabHandlers();
  const missingExpected = expectedTabs.filter((item) => !staticTabs.includes(item.tab));
  if (missingExpected.length) {
    throw new Error(`expected tabs absent in index.html: ${missingExpected.map((item) => item.tab).join(", ")}`);
  }

  const { chromium } = loadPlaywright();
  const url = env("DETMIR_PORTAL_SMOKE_URL", "http://127.0.0.1:8720/portal/");
  const timeout = Number(env("DETMIR_PORTAL_SMOKE_TIMEOUT_MS", "30000"));
  const browser = await chromium.launch({
    headless: true,
    args: ["--no-sandbox", "--disable-dev-shm-usage"],
  });
  const context = await browser.newContext({
    ignoreHTTPSErrors: true,
    viewport: { width: 1440, height: 950 },
    extraHTTPHeaders: authHeaders(),
  });
  const page = await context.newPage();
  const errors = [];
  const badResponses = [];
  page.on("pageerror", (error) => errors.push(error.message));
  page.on("console", (message) => {
    if (message.type() === "error") errors.push(message.text());
  });
  page.on("response", (response) => {
    if (response.status() >= 400) badResponses.push({ url: response.url(), status: response.status() });
  });

  const results = [];
  const checks = [];
  try {
    smokeStep = "open_portal";
    await page.goto(url, { waitUntil: "domcontentloaded", timeout });
    smokeStep = "wait_tabs";
    await page.waitForSelector(".tab", { timeout });
    smokeStep = "wait_initial_ready";
    await page.waitForFunction(
      () => document.querySelector("#loadingStatus")?.dataset.loadStatus === "READY",
      null,
      { timeout },
    );
    checks.push({
      name: "loading_refresh_status_ready",
      ok:
        (await page.locator("#loadingStatus").count()) === 1
        && (await page.locator("#loadingStatus").evaluate((node) => node.dataset.loadStatus)) === "READY"
        && (await page.locator("#loadingStateText").innerText({ timeout })).includes("Данные готовы")
        && (await page.locator("#loadingStageText").innerText({ timeout })).includes("Данные готовы"),
    });
    smokeStep = "api:security_events_summary";
    const reportsPayload = await page.evaluate(async () => {
      const response = await fetch("api/reports?role=security", {
        cache: "no-store",
        headers: { "X-AWatch-Role": "security" },
      });
      return { ok: response.ok, status: response.status, json: await response.json() };
    });
    const securitySummary = reportsPayload.json?.security_events_summary || null;
    const securityMode = securityEventsMode(securitySummary);
    const expectedSecurityMode = env("DETMIR_PORTAL_SMOKE_SECURITY_EVENTS_EXPECT", "auto").toLowerCase();
    checks.push({
      name: "security_events_api_json",
      ok: reportsPayload.ok && Boolean(securitySummary),
      mode: securityMode,
      status: reportsPayload.status,
    });
    checks.push({
      name: "security_events_expected_mode",
      ok: expectedSecurityMode === "auto" || expectedSecurityMode === securityMode,
      expected: expectedSecurityMode,
      actual: securityMode,
    });
    for (const item of expectedTabs) {
      smokeStep = `tab:${item.tab}`;
      await page.click(`button[data-tab="${item.tab}"]`, { timeout });
      await page.waitForFunction(
        (marker) => document.body && document.body.innerText.includes(marker),
        item.marker,
        { timeout },
      );
      const activeTab = await page.locator(".tab.is-active").innerText({ timeout });
      const bodyText = await page.locator("#content").innerText({ timeout });
      const stillLoading = bodyText.includes("Загрузка") || bodyText.includes("Обновление периода");
      const ok = activeTab.trim() === item.label && bodyText.includes(item.marker) && !stillLoading;
      results.push({ tab: item.tab, label: item.label, marker: item.marker, ok });
      if (item.tab === "operator") {
        const readyBodyText = await page.locator("#content").innerText({ timeout });
        const requiredExecutive = [
          "Риск-нарратив",
          "Достоверность показателей",
          "Полнота данных",
          "Главный риск",
          "Карта рисков",
          "Риски подразделений",
          "Почему такой индекс активности?",
          "Рекомендуемые действия",
          "Пробел в данных",
        ];
        checks.push({
          name: "executive_dashboard_layer",
          ok: requiredExecutive.every((marker) => containsText(readyBodyText, marker)),
          required: requiredExecutive,
        });
        const forbiddenExecutive = forbiddenExecutiveTerms(readyBodyText);
        checks.push({
          name: "executive_view_no_technical_terms",
          ok: forbiddenExecutive.length === 0,
          forbidden: forbiddenExecutive,
        });
        const cardHeadings = await page.$$eval("#content section.card h3, #content h3.section-title", (nodes) =>
          nodes.map((node) => node.textContent.trim()),
        );
        const riskNarrativeIndex = cardHeadings.indexOf("Риск-нарратив");
        const executiveIndex = cardHeadings.indexOf("Сводка руководителя");
        const businessRiskIndex = cardHeadings.indexOf("Риски подразделений");
        const heatmapIndex = cardHeadings.indexOf("Карта рисков");
        checks.push({
          name: "management_block_order",
          ok:
            riskNarrativeIndex >= 0 &&
            executiveIndex > riskNarrativeIndex &&
            businessRiskIndex > executiveIndex &&
            heatmapIndex > businessRiskIndex,
          order: [
            "Риск-нарратив",
            "Сводка руководителя",
            "Риски подразделений",
            "Карта рисков",
          ],
        });
        checks.push({
          name: "role_view_switcher_present",
          ok:
            (await page.locator('[data-view-mode="executive"]').count()) === 1
            && (await page.locator('[data-view-mode="manager"]').count()) === 1
            && (await page.locator('[data-view-mode="security"]').count()) === 1
            && (await page.locator('[data-view-mode="forensics"]').count()) === 1
            && (await page.locator('[data-view-mode="admin"]').count()) === 1,
        });
        const expectedSecurityText = expectedSecurityEventsText(securityMode);
        const executiveSecurityTextOk = securityMode === "available"
          ? containsText(readyBodyText, "События безопасности")
            && containsText(readyBodyText, "Событий за 24 часа")
            && !containsText(readyBodyText, "События безопасности временно недоступны")
            && !containsText(readyBodyText, "Источник событий безопасности отключён")
          : containsText(readyBodyText, expectedSecurityText);
        checks.push({
          name: "security_events_executive_text",
          ok:
            !expectedSecurityText
            || (executiveSecurityTextOk
              && !containsText(readyBodyText, "SECURITY_EVENTS_BACKEND")
              && !containsText(readyBodyText, "CLICKHOUSE_*")
              && !containsText(readyBodyText, "ClickHouse")),
          mode: securityMode,
          expected_text: expectedSecurityText,
        });

        smokeStep = "role:manager";
        await page.click('[data-view-mode="manager"]', { timeout });
        await page.waitForFunction(
          () => document.querySelector("#loadingStatus")?.dataset.loadStatus === "READY"
            && document.body.innerText.includes("ТОП-5 лучших подразделений"),
          null,
          { timeout },
        );
        const managerText = await page.locator("#content").innerText({ timeout });
        checks.push({
          name: "manager_role_view",
          ok: [
            "Представление менеджера",
            "Сводка руководителя",
            "ТОП-5 лучших подразделений",
            "ТОП-5 проблемных подразделений",
            "Heat Map подразделений",
            "Markdown-отчет",
          ].every((marker) => containsText(managerText, marker))
            && !containsText(managerText, "Материалы расследования"),
        });

        smokeStep = "role:security";
        await page.click('[data-view-mode="security"]', { timeout });
        await page.waitForFunction(
          () => document.querySelector("#loadingStatus")?.dataset.loadStatus === "READY"
            && document.body.innerText.includes("Материалы расследования"),
          null,
          { timeout },
        );
        const securityText = await page.locator("#content").innerText({ timeout });
        checks.push({
          name: "security_role_view",
          ok: [
            "Представление безопасности",
            "Требует проверки",
            "Расследования",
            "Аудит",
            "Материалы расследования",
            "События безопасности за 24 часа",
            "Связь рисков и активности",
          ].every((marker) => containsText(securityText, marker)),
        });
        checks.push({
          name: "security_role_hides_technical_health_sources",
          ok:
            !containsText(securityText, "detmir_check")
            && !containsText(securityText, "detmir_status")
            && !containsText(securityText, "command failed")
            && !containsText(securityText, "command returned non-zero"),
        });
        checks.push({
          name: "security_events_security_text",
          ok: !expectedSecurityText || containsText(securityText, expectedSecurityText),
          mode: securityMode,
          expected_text: expectedSecurityText,
        });

        smokeStep = "role:forensics";
        await page.click('[data-view-mode="forensics"]', { timeout });
        await page.waitForFunction(
          () => document.querySelector("#loadingStatus")?.dataset.loadStatus === "READY"
            && document.body.innerText.includes("Timeline событий"),
          null,
          { timeout },
        );
        const forensicsText = await page.locator("#content").innerText({ timeout });
        checks.push({
          name: "forensics_role_view",
          ok: [
            "Представление расследований",
            "Требует проверки",
            "Расследования",
            "Timeline событий",
            "Материалы расследования",
            "Аудит",
          ].every((marker) => containsText(forensicsText, marker))
            && !containsText(forensicsText, "Рейтинг подразделений"),
        });

        smokeStep = "role:admin";
        await page.click('[data-view-mode="admin"]', { timeout });
        await page.waitForFunction(
          () => document.querySelector("#loadingStatus")?.dataset.loadStatus === "READY"
            && document.body.innerText.includes("Телеметрия"),
          null,
          { timeout },
        );
        const operationsText = await page.locator("#content").innerText({ timeout });
        checks.push({
          name: "admin_role_view",
          ok: [
            "Представление администратора",
            "Полнота данных",
            "Качество данных",
            "События безопасности за 24 часа",
            "Качество данных по рабочим местам",
            "Ошибки",
            "Телеметрия",
          ].every((marker) => containsText(operationsText, marker)),
        });
        checks.push({
          name: "security_events_operations_text",
          ok:
            !expectedSecurityText
            || (containsText(operationsText, expectedSecurityText)
              && (securityMode !== "fallback" || containsText(operationsText, "События безопасности временно недоступны"))),
          mode: securityMode,
          expected_text: expectedSecurityText,
        });

        smokeStep = "api:role_gates";
        const apiUrl = (path) => new URL(path, page.url()).toString();
        const [managerSecurity, securityWorkforce, forensicsOk, uebaOk] = await Promise.all([
          context.request.get(apiUrl("api/security"), {
            headers: { ...authHeaders(), "X-AWatch-Role": "manager" },
          }),
          context.request.get(apiUrl("api/workforce"), {
            headers: { ...authHeaders(), "X-AWatch-Role": "security" },
          }),
          context.request.get(apiUrl("api/forensics"), {
            headers: { ...authHeaders(), "X-AWatch-Role": "forensics" },
          }),
          context.request.get(apiUrl("api/ueba"), {
            headers: { ...authHeaders(), "X-AWatch-Role": "security" },
          }),
        ]);
        checks.push({
          name: "server_role_gates",
          ok:
            managerSecurity.status() === 403
            && securityWorkforce.status() === 403
            && forensicsOk.ok()
            && uebaOk.ok(),
          statuses: {
            manager_security: managerSecurity.status(),
            security_workforce: securityWorkforce.status(),
            forensics: forensicsOk.status(),
            ueba: uebaOk.status(),
          },
        });
      }
      if (item.tab === "settings") {
        const requiredSettings = [
          "Период расчета",
          "Рабочий день",
          "Порог внимания",
          "Порог критического риска",
          "Источник правил",
          "Дата последнего пересчета",
        ];
        checks.push({
          name: "settings_readonly_rows",
          ok: requiredSettings.every((marker) => containsText(bodyText, marker)),
          required: requiredSettings,
        });
      }
    }
    smokeStep = "security:investigation_navigation";
    await page.click('button[data-tab="operator"]', { timeout });
    await page.click('[data-view-mode="security"]', { timeout });
    await page.waitForFunction(
      () => document.querySelector("#loadingStatus")?.dataset.loadStatus === "READY"
        && document.body.innerText.includes("Требует проверки"),
      null,
      { timeout },
    );
    const investigationButtons = await page.locator("[data-open-investigation]").count();
    if (investigationButtons > 0) {
      await page.locator("[data-open-investigation]").first().click({ timeout });
      await page.waitForFunction(
        () => document.body && document.body.innerText.includes("Расследования и доказательная база"),
        null,
        { timeout },
      );
      const activeTab = await page.locator(".tab.is-active").innerText({ timeout });
      const incidentText = await page.locator("#content").innerText({ timeout });
      checks.push({
        name: "risk_open_investigation_readonly_navigation",
        ok: activeTab.trim() === "Расследования"
          && containsText(incidentText, "Расследование сформировано автоматически")
          && containsText(incidentText, "Номер")
          && containsText(incidentText, "Риск")
          && containsText(incidentText, "Материалы расследования"),
        buttons: investigationButtons,
      });
    } else {
      checks.push({
        name: "risk_open_investigation_readonly_navigation",
        ok: true,
        buttons: 0,
        skipped: "no risk rows in current report",
      });
    }
  } finally {
    await browser.close();
  }

  const ok = results.every((item) => item.ok) && checks.every((item) => item.ok) && errors.length === 0 && badResponses.length === 0;
  const result = {
    ok,
    generated_at_utc: new Date().toISOString(),
    url,
    static_tabs: staticTabs,
    tabs: results,
    checks,
    errors,
    bad_responses: badResponses,
  };
  process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
  process.exitCode = ok ? 0 : 2;
}

main().catch((error) => {
  process.stdout.write(`${JSON.stringify({ ok: false, step: smokeStep, error: error.message, stack: error.stack }, null, 2)}\n`);
  process.exitCode = 2;
});
