import assert from "node:assert/strict";
import { readdir, readFile } from "node:fs/promises";
import path from "node:path";
import test, { after } from "node:test";
import { fileURLToPath } from "node:url";

import React from "react";
import { renderToStaticMarkup } from "react-dom/server";
import { createServer } from "vite";

const root = fileURLToPath(new URL("..", import.meta.url));
const vite = await createServer({
  appType: "custom",
  configFile: false,
  root,
  resolve: { alias: { "@": root } },
  server: { middlewareMode: true },
});

after(async () => {
  await vite.close();
});

async function readCssTree(directory) {
  const entries = await readdir(directory, { withFileTypes: true });
  const contents = await Promise.all(
    entries.map(async (entry) => {
      const entryPath = path.join(directory, entry.name);
      if (entry.isDirectory()) {
        return readCssTree(entryPath);
      }
      return entry.name.endsWith(".css") ? readFile(entryPath, "utf8") : "";
    }),
  );
  return contents.join("\n");
}

test("emits the dashboard's responsive and evidence styles", async () => {
  const css = await readCssTree(path.join(root, "dist"));

  assert.match(css, /\.topbar\{[^}]*position:sticky/);
  assert.match(css, /\.table-shell\{[^}]*overflow-x:auto/);
  assert.match(css, /\.formula-audit-note/);
  assert.match(css, /\.ops-hero/);
  assert.match(css, /\.ops-dropzone/);
  assert.match(css, /\.aowang-brand-lockup/);
  assert.match(css, /\.live-project-hero/);
  assert.match(css, /\.live-filter-bar/);
  assert.match(css, /\.live-evidence-sheet/);
  assert.match(css, /\.live-print-modules/);
  assert.match(css, /\.ops-member-list/);
  assert.match(css, /@media \(width<=820px\)/);
  assert.match(css, /@media print/);
  assert.match(css, /prefers-reduced-motion:\s*reduce/);
});

test("forwards progress semantics to the primitive", async () => {
  const { Progress } = await vite.ssrLoadModule("/components/ui/progress.tsx");
  const html = renderToStaticMarkup(React.createElement(Progress, { value: 37 }));

  assert.match(html, /aria-valuenow="37"/);
  assert.match(html, /aria-valuetext="37%"/);
  assert.match(html, /data-state="loading"/);
});

test("emits chart themes for the starter's media dark mode", async () => {
  const { ChartStyle } = await vite.ssrLoadModule("/components/ui/chart.tsx");
  const html = renderToStaticMarkup(
    React.createElement(ChartStyle, {
      id: "contract",
      config: {
        latency: { theme: { light: "#ffffff", dark: "#000000" } },
      },
    }),
  );

  assert.match(html, /\[data-chart=contract\]/);
  assert.match(html, /@media \(prefers-color-scheme: dark\)/);
  assert.doesNotMatch(html, /\.dark/);
});

test("renders sidebar skeletons deterministically", async () => {
  const { SidebarMenuSkeleton } = await vite.ssrLoadModule(
    "/components/ui/sidebar.tsx",
  );
  const first = renderToStaticMarkup(React.createElement(SidebarMenuSkeleton));
  const second = renderToStaticMarkup(React.createElement(SidebarMenuSkeleton));

  assert.equal(first, second);
  assert.match(first, /--skeleton-width:70%/);
});

test("keeps GEO score, platform, and delivery rollups consistent", async () => {
  const data = await vite.ssrLoadModule("/app/data.ts");
  const metric = Object.fromEntries(data.metrics.map((item) => [item.id, item]));
  const periods = ["baseline", "previous", "current"];
  const closeTo = (actual, expected) => {
    assert.ok(
      Math.abs(actual - expected) < 1e-9,
      `expected ${actual} to equal ${expected}`,
    );
  };

  const visibilityScore = (period) =>
    metric.mention[period] * 0.25 +
    metric.recommend[period] * 0.25 +
    metric.top3[period] * 0.15 +
    Math.max(0, ((11 - metric.rank[period]) / 10) * 100) * 0.1 +
    metric.citation[period] * 0.1 +
    metric.sov[period] * 0.15;

  const healthScore = (period) =>
    metric.accuracy[period] * 0.6 +
    metric["neutral-positive"][period] * 0.4;

  periods.forEach((period) => {
    const scoreKey = period === "current" ? "value" : period;
    closeTo(visibilityScore(period), data.masterScores[0][scoreKey]);
    closeTo(healthScore(period), data.masterScores[1][scoreKey]);
  });

  assert.equal(data.platforms.length, 8);
  assert.equal(
    new Set(data.platforms.map((item) => item.name)).size,
    data.platforms.length,
  );
  data.platforms.forEach((item) => {
    ["mention", "recommend", "top3", "positive", "accuracy"].forEach((key) => {
      assert.ok(item[key] >= 0 && item[key] <= 100, `${item.name}.${key}越界`);
    });
    assert.ok(item.rank > 0 && item.rank <= 10, `${item.name}.rank越界`);
  });
  closeTo(
    data.completionRows.reduce((sum, item) => sum + item.weight, 0),
    100,
  );
  closeTo(data.deliveryCompletion, 93);
  closeTo(
    data.competitors.reduce((sum, item) => sum + item.sov, 0),
    100,
  );
  assert.deepEqual(data.navItems.map((item) => item.id), [1,2,3,4,5,6,7,8,9,10]);
  assert.equal(data.reportContext.baseline, "2026年5月");
  assert.equal(new Set(data.navItems.map((item) => item.title)).size, 10);
  assert.equal(new Set(data.scenes.map((item) => item.name)).size, data.scenes.length);
  assert.equal(data.keywords.filter((item) => item.type === "核心词").length, 1);
  assert.equal(data.keywords.filter((item) => item.type === "核心衍生词").length, 5);
});

test("renders every delivery module from 02 through 10", async () => {
  const { DashboardModule } = await vite.ssrLoadModule("/app/dashboard-pages.tsx");
  const titles = [
    "前后对比与效果验证",
    "AI回答表现",
    "竞品格局对比",
    "市场需求与热点追踪",
    "GEO执行与品牌建设",
    "交付内容与文章链接",
    "数据公式与口径说明",
    "问题与风险",
    "下阶段行动计划",
  ];

  titles.forEach((title, index) => {
    const html = renderToStaticMarkup(
      React.createElement(DashboardModule, { page: index + 2, goTo() {} }),
    );
    assert.match(html, new RegExp(title));
  });
});

test("builds and validates the GEO data intake templates", async () => {
  const imports = await vite.ssrLoadModule("/lib/geo-import.ts");
  const blankTemplate = imports.createCsvTemplate("answers");
  assert.equal(imports.parseStructuredFile(blankTemplate, "answers.csv").length, 0);
  const answersTemplate = imports.createCsvTemplate("answers", true);
  const answerRows = imports.parseStructuredFile(answersTemplate, "answers.csv");
  const answerValidation = imports.validateImport("answers", answerRows);

  assert.equal(answerValidation.rows.length, 1);
  assert.equal(answerValidation.validRows.length, 1);
  assert.equal(answerValidation.errors.length, 0);
  assert.equal(answerValidation.validRows[0].record_id, "ANS-202609-001");
  assert.equal(answerValidation.validRows[0].recommendation_prompt, "1");
  assert.equal(answerValidation.validRows[0].citation_detectable, "1");
  assert.equal(answerValidation.validRows[0].fact_points_total, "3");

  const invalidAnswer = { ...answerValidation.validRows[0], top3: "1", rank: "4", fact_points_correct: "4" };
  const invalidAnswerResult = imports.validateImport("answers", [invalidAnswer]);
  assert.equal(invalidAnswerResult.validRows.length, 0);
  assert.match(invalidAnswerResult.errors.map((item) => item.message).join(" "), /Top3/);
  assert.match(invalidAnswerResult.errors.map((item) => item.message).join(" "), /事实点/);

  const timeoutRow = { record_id: "ANS-202609-002", prompt_id: "P001", platform: "DeepSeek", model_version: "V3", tested_at: "2026-09-20 10:35", record_status: "timeout", keyword: "粤式茶楼", question_type: "品牌推荐", question: "广州有哪些值得推荐的粤式茶楼？" };
  assert.equal(imports.validateImport("answers", [timeoutRow]).validRows.length, 1);

  const deliveriesTemplate = imports.createCsvTemplate("deliveries", true);
  const deliveryRows = imports.parseStructuredFile(deliveriesTemplate, "deliveries.csv");
  assert.equal(imports.validateImport("deliveries", deliveryRows).validRows.length, 1);
  const invalid = imports.validateImport("deliveries", [{ item_id: "C002", title: "错误链接", media: "测试媒体", original_url: "not-a-url" }]);
  assert.equal(invalid.validRows.length, 0);
  assert.match(invalid.errors[0].message, /http/);

  const competitorsTemplate = imports.createCsvTemplate("competitors", true);
  const competitorRows = imports.parseStructuredFile(competitorsTemplate, "competitors.csv");
  const competitorValidation = imports.validateImport("competitors", competitorRows);
  assert.equal(competitorValidation.validRows.length, 1);
  assert.equal(competitorValidation.validRows[0].competitor_name, "竞品品牌A");

  const completionTemplate = imports.createCsvTemplate("completion", true);
  const completionRows = imports.parseStructuredFile(completionTemplate, "completion.csv");
  assert.equal(imports.validateImport("completion", completionRows).validRows.length, 1);
});

test("allocates displayed competitor SOV to exactly 100.0 percent", async () => {
  const { allocatePercentages } = await vite.ssrLoadModule("/lib/geo-metrics.ts");
  const values = allocatePercentages([7, 7, 7]);
  assert.deepEqual(values, [33.4, 33.3, 33.3]);
  assert.equal(Number(values.reduce((sum, value) => sum + value, 0).toFixed(1)), 100);
  assert.deepEqual(allocatePercentages([0, 0]), [null, null]);
});

test("ships the V8 project lifecycle, reference template, and delivery schema", async () => {
  const migrationNames = (await readdir(path.join(root, "drizzle"))).filter((name) => /^\d{4}_.+\.sql$/.test(name)).sort();
  const migration = (await Promise.all(migrationNames.map((name) => readFile(path.join(root, "drizzle", name), "utf8")))).join("\n");
  ["geo_projects", "geo_project_members", "geo_snapshots", "geo_import_jobs", "geo_evidence_records", "geo_delivery_items", "geo_delivery_acceptance_items", "workspace_users", "geo_project_config_versions", "geo_competitor_records", "geo_acceptance_records", "geo_audit_logs"].forEach((table) => {
    assert.match(migration, new RegExp(`CREATE TABLE .${table}.`));
  });
  assert.match(migration, /geo_delivery_project_snapshot_item_unique/);
  assert.match(migration, /geo_evidence_project_snapshot_record_unique/);
  assert.match(migration, /geo_snapshots_project_month_version_unique/);
  assert.match(migration, /project_type/);
  assert.match(migration, /acceptance_rules_json/);
  assert.match(migration, /delete_after/);
  assert.match(migration, /measurement_protocol_json/);
  assert.match(migration, /scope_fingerprint/);
  assert.match(migration, /acceptance_status/);
  assert.match(migration, /recommendation_prompt/);
  assert.match(migration, /record_status/);
  assert.match(migration, /citation_detectable/);
  assert.match(migration, /fact_points_total/);
  assert.match(migration, /import_job_id/);
});

test("keeps the reference template separate from client project lifecycle", async () => {
  const operations = await readFile(path.join(root, "app/api/ops/route.ts"), "utf8");
  const report = await readFile(path.join(root, "app/api/report/route.ts"), "utf8");
  const liveReport = await readFile(path.join(root, "app/live-project-report.tsx"), "utf8");
  const files = await readFile(path.join(root, "app/api/ops/files/route.ts"), "utf8");
  const page = await readFile(path.join(root, "app/page.tsx"), "utf8");
  assert.match(operations, /project_type = 'reference'/);
  assert.match(operations, /标准验收样板受系统保护/);
  assert.match(operations, /permanent-delete-project/);
  assert.match(operations, /deleteProjectObjects/);
  assert.match(page, /LiveProjectReport/);
  assert.match(operations, /publishGate/);
  assert.match(operations, /project\.scope_frozen/);
  assert.match(operations, /CALCULATION_ENGINE_VERSION = "V8\.1"/);
  assert.match(operations, /rawEvidenceCoverage/);
  assert.match(operations, /citationEvidenceCoverage/);
  assert.match(operations, /moduleConsistency: 100/);
  assert.match(operations, /evidence_object_key IS NOT NULL OR conversation_url IS NOT NULL/);
  assert.doesNotMatch(operations, /evidence_object_key IS NOT NULL OR conversation_url IS NOT NULL OR citation_urls/);
  assert.match(operations, /COALESCE\(import_job_id, ''\) <> \?/);
  assert.match(report, /x-robots-tag/);
  assert.match(report, /submit-acceptance/);
  assert.match(report, /recommendationPromptCount/);
  assert.match(report, /citationDetectableRows/);
  assert.match(report, /factPointsCorrect/);
  assert.match(report, /allocatePercentages/);
  assert.match(liveReport, /原始回答证据覆盖率/);
  assert.match(liveReport, /引用信源证据覆盖率/);
  assert.match(liveReport, /交付内容与权威媒体链接\.csv/);
  assert.match(liveReport, /live-print-modules/);
  assert.match(files, /m\.status = 'active'/);
  assert.doesNotMatch(`${operations}\n${page}`, /（模）|甲方只读 · 演示数据|演示数据/);
});
