import { expect, test, type Page } from "@playwright/test";
import { createServer, type ViteDevServer } from "vite";
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { homedir } from "node:os";
import { join, resolve } from "node:path";
import type { CanvasState } from "@dada/shared-contracts";

let vite: ViteDevServer;
let webUrl: string;

test.beforeAll(async () => {
  vite = await createServer({ configFile: resolve("apps/web/vite.config.ts"), root: resolve("apps/web"), server: { host: "127.0.0.1", port: 0 } });
  await vite.listen();
  const address = vite.httpServer?.address();
  if (!address || typeof address === "string") throw new Error("Vite did not expose a test port.");
  webUrl = `http://127.0.0.1:${address.port}`;
});

test.afterAll(async () => vite.close());

const userId = "00000000-0000-4000-8000-000000000821";
const session = {
  csrf_token: "csrf-wp4-04-fixture-0000000000000000000000000000",
  user: { creator_name: "Dada Creator", social_id: "@@dada", user_id: userId },
};

const rawImages: Record<string, string[]> = {
  "00000000-0000-4000-8000-000000000811": ["#F42020", "#20D260", "#1860DC", "#F8D230", "#7840B4"],
  "00000000-0000-4000-8000-000000000812": ["#102030", "#405060", "#708090", "#A0B0C0", "#D0E0F0"],
};

const dynamicRoot = process.env.DADA_DYNAMIC_ASSET_ROOT ?? join(homedir(), "Desktop", "sticker_interactive", "单模板归档", "templates");
const textRoot = process.env.DADA_TEXT_ASSET_ROOT ?? join(homedir(), "Desktop", "sticker_text");
const dynamicSourceAssets: Readonly<Record<string, { contentType: string; path: string }>> = {
  "15974853bc3294ef68e7e6d58fe74fd7": { contentType: "font/ttf", path: join(dynamicRoot, "DYN002", "fonts", "15974853bc3294ef68e7e6d58fe74fd7", "0202b90o6r57rxed4027b5689e0dxe7e142r0yho9d0lljuqj.ttf") },
  "46f8336813e4c48d06a1aef294fdccf6": { contentType: "font/ttf", path: join(dynamicRoot, "DYN016", "fonts", "46f8336813e4c48d06a1aef294fdccf6", "9fbfbb420cea1df916d7c7c7ac90b1c88b61e117-PingFang-SC-Semibold-2.ttf") },
  "53ca6b704728520da50c145eabb2e635": { contentType: "font/ttf", path: join(dynamicRoot, "DYN007", "fonts", "53ca6b704728520da50c145eabb2e635", "fab6c26a0b21d5e9b57fb5238843ac1fb77a2ce6-HYZhengYuan.ttf") },
  "cca5efc0e02fb1bf62349bd68ef30fc1": { contentType: "font/otf", path: join(dynamicRoot, "DYN015", "fonts", "cca5efc0e02fb1bf62349bd68ef30fc1", "e11ced673fc7e63e8b0b4730166d29845d8bebae-NotoSansCJKsc-Regular.otf") },
  "dd25b35dcb7ba4476cbaa9a9592e39e2": { contentType: "font/ttf", path: join(dynamicRoot, "DYN001", "fonts", "dd25b35dcb7ba4476cbaa9a9592e39e2", "0202b90o6r57rxed4027b5689e0dxe7e142r0ygbx80porvko.ttf") },
  "e4210c9872f0c279b35273f230809821": { contentType: "font/ttf", path: join(dynamicRoot, "DYN011", "fonts", "e4210c9872f0c279b35273f230809821", "06b980259e2104e1211a6819a61bc5ddeca77dcb-DJB-Get-Digital-1.ttf") },
  "f4bfd4132df2d6be97ceabadf3853505": { contentType: "font/ttf", path: join(dynamicRoot, "DYN008", "fonts", "f4bfd4132df2d6be97ceabadf3853505", "6ce05a147aedabbb610d9cb3e75bbe60c064c3f5-BarlowCondensed-SemiBold.ttf") },
  "DYN001-image28": { contentType: "image/png", path: join(dynamicRoot, "DYN001", "resource", "image28.png") },
  "DYN002-image29": { contentType: "image/png", path: join(dynamicRoot, "DYN002", "resource", "image29.png") },
  "DYN003-image30": { contentType: "image/png", path: join(dynamicRoot, "DYN003", "resource", "image30.png") },
  "DYN004-image32": { contentType: "image/png", path: join(dynamicRoot, "DYN004", "resource", "image32.png") },
  "DYN008-backendui0": { contentType: "image/png", path: join(dynamicRoot, "DYN008", "resource", "backendui0.png") },
  "DYN011-backendui0": { contentType: "image/png", path: join(dynamicRoot, "DYN011", "resource", "backendui0.png") },
  "DYN015-imager2": { contentType: "image/png", path: join(dynamicRoot, "DYN015", "resource", "imager2_2.png") },
  "DYN016-image21": { contentType: "image/png", path: join(dynamicRoot, "DYN016", "resource", "image21.png") },
};
const font081Path = join(textRoot, "字体", "面板全量采集", "font_panel_full_20260722", "resources", "font_packages", "FONT081_Lexend Deca", "font_files", "02034l0o6r57rxed4027b5689e0dxe7e142r0vi8920akeqto.ttf");

function rawSvg(colors: string[]) {
  return `<svg xmlns="http://www.w3.org/2000/svg" width="250" height="250">${colors.map((color, index) => `<rect x="${index * 50}" y="0" width="50" height="250" fill="${color}"/>`).join("")}</svg>`;
}

function emptyCanvas(assetId: string | null = null): CanvasState {
  return {
    background: { adjustments: { brightness: 0, contrast: 0, crop: null, filter: "none", fit: "fill", saturation: 0, sharpness: 0, temperature: 0 }, asset_id: assetId },
    elements: [], pixel_height: 1440, pixel_width: 1080, ratio: "3:4", schema_version: 1,
  };
}

interface Backend {
  canvas: CanvasState;
  saves: number;
  version: number;
}

function writeEvidence(caseId: string, name: string, value: unknown) {
  const root = process.env.DADA_EVIDENCE_DIR_COLOR_DYNAMIC;
  if (!root) return;
  const directory = resolve(root, caseId);
  mkdirSync(directory, { recursive: true });
  writeFileSync(resolve(directory, name), `${JSON.stringify(value, null, 2)}\n`);
}

async function routeEditor(page: Page, projectId: string, backend: Backend) {
  const sourceFiles = [...Object.values(dynamicSourceAssets).map((asset) => asset.path), font081Path];
  if (!sourceFiles.every(existsSync)) throw new Error("Archived dynamic source fixture is unavailable.");
  await page.route("**/api/v1/auth/session", (route) => route.fulfill({ body: JSON.stringify(session), contentType: "application/json" }));
  await page.route(`**/api/v1/projects/${projectId}`, (route) => route.fulfill({
    body: JSON.stringify({
      canvas_state: backend.canvas, created_at: "2026-08-03T04:00:00.000Z", current_image_id: backend.canvas.background.asset_id,
      images: Object.keys(rawImages).map((imageId, index) => ({ created_at: `2026-08-03T0${index + 4}:00:00.000Z`, generation_id: `00000000-0000-4000-8000-00000000081${index + 3}`, image_id: imageId })),
      name: "色卡动态画布", project_id: projectId, ratio: "3:4", state_version: backend.version,
    }), contentType: "application/json",
  }));
  await page.route(`**/api/v1/projects/${projectId}/state`, async (route) => {
    backend.canvas = (route.request().postDataJSON() as { canvas_state: CanvasState }).canvas_state;
    backend.saves += 1;
    backend.version += 1;
    await route.fulfill({ body: JSON.stringify({ save_status: "saved", state_version: backend.version }), contentType: "application/json" });
  });
  await page.route(`**/api/v1/private-assets/projects/${projectId}/images/*`, (route) => {
    const assetId = route.request().url().split("/").at(-1)!;
    const colors = rawImages[assetId];
    if (!colors) return route.fulfill({ status: 404 });
    return route.fulfill({ body: rawSvg(colors), contentType: "image/svg+xml", status: 200 });
  });
  await page.route("**/api/v1/assets/recent?asset_kind=text_template", (route) => route.fulfill({ body: JSON.stringify({ items: [] }), contentType: "application/json" }));
  await page.route("**/api/v1/assets/public/p0a-complex-v1/*", (route) => {
    const assetId = decodeURIComponent(route.request().url().split("/").at(-1)!);
    const asset = assetId === "FONT081" ? { contentType: "font/ttf", path: font081Path } : dynamicSourceAssets[assetId];
    if (!asset) return route.fulfill({ status: 404 });
    return route.fulfill({ body: readFileSync(asset.path), contentType: asset.contentType, status: 200 });
  });
}

async function stageInk(page: Page) {
  return page.getByLabel("编辑画布").evaluate((canvas: HTMLCanvasElement) => {
    const context = canvas.getContext("2d");
    if (!context) throw new Error("Canvas context unavailable.");
    const pixels = context.getImageData(0, 0, canvas.width, canvas.height).data;
    let nonWhite = 0;
    for (let index = 0; index < pixels.length; index += 4) {
      if ((pixels[index] ?? 255) < 245 || (pixels[index + 1] ?? 255) < 245 || (pixels[index + 2] ?? 255) < 245) nonWhite += 1;
    }
    return { canvas_pixels: canvas.width * canvas.height, non_white_pixels: nonWhite };
  });
}

async function nudgeSelected(page: Page, directions: Array<{ key: "ArrowDown" | "ArrowLeft" | "ArrowRight" | "ArrowUp"; times: number }>) {
  const stage = page.getByLabel("编辑画布");
  for (const direction of directions) {
    for (let index = 0; index < direction.times; index += 1) await stage.press(`Shift+${direction.key}`);
  }
}

test("TDD-WP4-COL-001 extracts once from raw pixels and refreshes only for a new background", async ({ page }) => {
  const projectId = "00000000-0000-4000-8000-000000000830";
  const initialAsset = Object.keys(rawImages)[0]!;
  const backend: Backend = { canvas: emptyCanvas(initialAsset), saves: 0, version: 1 };
  const requests: Array<{ method: string; url: string }> = [];
  page.on("request", (request) => requests.push({ method: request.method(), url: request.url() }));
  await routeEditor(page, projectId, backend);
  await page.goto(`${webUrl}/app/projects/${projectId}/editor`);
  await page.getByRole("button", { name: "色卡", exact: true }).click();
  await expect(page.getByRole("button", { name: /^添加色卡/ })).toHaveCount(4);
  await expect(page.getByRole("button", { name: "色卡说明" })).toHaveAttribute("title", "色卡基于原始底图，更换底图时更新，不随裁剪、调色和滤镜变化");
  const placements = [
    [{ key: "ArrowLeft", times: 15 }, { key: "ArrowUp", times: 14 }],
    [{ key: "ArrowRight", times: 15 }, { key: "ArrowUp", times: 14 }],
    [{ key: "ArrowLeft", times: 15 }, { key: "ArrowDown", times: 14 }],
    [{ key: "ArrowRight", times: 15 }, { key: "ArrowDown", times: 14 }],
  ] as const;
  for (const [index, id] of ["COLOR001", "COLOR002", "COLOR008", "COLOR016"].entries()) {
    await page.getByRole("button", { name: new RegExp(`添加色卡 ${id}`) }).click();
    await nudgeSelected(page, [...placements[index]!]);
  }
  await expect.poll(() => backend.canvas.elements.length).toBe(4);
  const palettes = backend.canvas.elements.map((element) => element.colors);
  expect(palettes.every((palette) => JSON.stringify(palette) === JSON.stringify(palettes[0]))).toBe(true);
  expect(backend.canvas.elements.every((element) => element.style_parameters?.palette_algorithm_version === "mmcq-v1")).toBe(true);
  const beforeAdjustments = structuredClone(palettes[0]);
  await page.getByLabel("编辑画布").press("Escape");
  await page.getByRole("button", { name: "底图", exact: true }).click();
  await page.locator(".editor-inspector label").filter({ hasText: "亮度" }).locator('input[type="range"]').fill("40");
  await page.getByRole("button", { name: "应用调整" }).click();
  expect(backend.canvas.elements.every((element) => JSON.stringify(element.colors) === JSON.stringify(beforeAdjustments))).toBe(true);
  await page.getByRole("button", { name: "历史", exact: true }).click();
  await page.locator(".editor-history-list button").last().click();
  await page.getByRole("button", { name: "确认更换" }).click();
  await expect.poll(() => backend.canvas.background.asset_id).toBe(Object.keys(rawImages)[1]);
  expect(backend.canvas.elements.every((element) => JSON.stringify(element.colors) === JSON.stringify(backend.canvas.elements[0]?.colors))).toBe(true);
  expect(backend.canvas.elements[0]?.colors).not.toEqual(beforeAdjustments);
  const network = {
    image_gets: requests.filter((item) => item.method === "GET" && item.url.includes("/private-assets/")).length,
    image_uploads: requests.filter((item) => item.method !== "GET" && item.url.includes("/private-assets/")).length,
  };
  expect(network.image_uploads).toBe(0);
  const pixels = await stageInk(page);
  await page.getByLabel("编辑画布").press("Escape");
  await page.getByRole("button", { name: "色卡", exact: true }).click();
  writeEvidence("TDD-WP4-COL-001-deterministic-palette", "palette.json", { algorithm: "mmcq-v1", initial: beforeAdjustments, replacement: backend.canvas.elements[0]?.colors, styles: backend.canvas.elements.map((item) => item.style_id) });
  writeEvidence("TDD-WP4-COL-001-deterministic-palette", "network-timeline.json", network);
  writeEvidence("TDD-WP4-COL-001-deterministic-palette", "pixel-diff.json", { ...pixels, four_renderers_visible: true });
  writeEvidence("TDD-WP4-COL-001-deterministic-palette", "db-diff.json", { elements: backend.canvas.elements, save_count: backend.saves });
  if (process.env.DADA_EVIDENCE_DIR_COLOR_DYNAMIC) await page.screenshot({ fullPage: true, path: resolve(process.env.DADA_EVIDENCE_DIR_COLOR_DYNAMIC, "TDD-WP4-COL-001-deterministic-palette", "color-cards.png") });
});

test("TDD-WP4-DYN-001 snapshots the confirmed local time across clock changes and reopen", async ({ page }) => {
  await page.clock.setFixedTime(new Date("2026-08-03T09:07:00+08:00"));
  const projectId = "00000000-0000-4000-8000-000000000840";
  const backend: Backend = { canvas: emptyCanvas(), saves: 0, version: 2 };
  await routeEditor(page, projectId, backend);
  await page.goto(`${webUrl}/app/projects/${projectId}/editor`);
  await page.getByRole("button", { name: "动态贴纸", exact: true }).click();
  for (const id of ["DYN007", "DYN008", "DYN011", "DYN012"]) await page.getByRole("button", { name: new RegExp(`添加动态贴纸 ${id}`) }).click();
  await expect.poll(() => backend.canvas.elements.length).toBe(4);
  const snapshots = backend.canvas.elements.map((element) => ({ fields: element.dynamic_fields, id: element.template_or_asset_id, value: element.formatted_value }));
  await page.clock.setFixedTime(new Date("2026-08-06T23:59:00+08:00"));
  await page.reload();
  expect(backend.canvas.elements.map((element) => ({ fields: element.dynamic_fields, id: element.template_or_asset_id, value: element.formatted_value }))).toEqual(snapshots);
  writeEvidence("TDD-WP4-DYN-001-fixed-time", "canvas-state.json", backend.canvas);
  writeEvidence("TDD-WP4-DYN-001-fixed-time", "clock-trace.json", { inserted_at: "2026-08-03T09:07:00+08:00", reopened_at: "2026-08-06T23:59:00+08:00", snapshots });
  writeEvidence("TDD-WP4-DYN-001-fixed-time", "pixel-diff.json", { ...(await stageInk(page)), static_canvas_content: true, links: 0 });
});

test("TDD-WP4-DYN-002 gates coordinates behind consent and preserves manual fallback", async ({ page }) => {
  const projectId = "00000000-0000-4000-8000-000000000850";
  const backend: Backend = { canvas: emptyCanvas(), saves: 0, version: 3 };
  await page.addInitScript(() => {
    (window as Window & { __dadaLocationCalls?: number }).__dadaLocationCalls = 0;
    Object.defineProperty(navigator, "geolocation", {
      configurable: true,
      value: { getCurrentPosition(success: PositionCallback) { (window as Window & { __dadaLocationCalls?: number }).__dadaLocationCalls = ((window as Window & { __dadaLocationCalls?: number }).__dadaLocationCalls ?? 0) + 1; success({ coords: { latitude: 27.9943, longitude: 120.6994 } } as GeolocationPosition); } },
    });
  });
  let reverseCalls = 0;
  let servicePaused = false;
  await routeEditor(page, projectId, backend);
  await page.route("**/api/v1/location/reverse-geocode", async (route) => {
    reverseCalls += 1;
    if (servicePaused) return route.fulfill({ status: 503 });
    return route.fulfill({ body: JSON.stringify({ formatted_value: "浙江省温州市", service_mode: "mock", status: "resolved" }), contentType: "application/json" });
  });
  await page.goto(`${webUrl}/app/projects/${projectId}/editor`);
  await page.getByRole("button", { name: "动态贴纸", exact: true }).click();
  await page.getByRole("button", { name: /添加动态贴纸 DYN004/ }).click();
  const dialog = page.getByRole("dialog", { name: "使用自动定位" });
  await expect(dialog).toContainText("原始经纬度会保存到当前项目并显示在导出成品中");
  await dialog.getByRole("button", { name: "暂不定位" }).click();
  expect(await page.evaluate(() => (window as Window & { __dadaLocationCalls?: number }).__dadaLocationCalls)).toBe(0);
  expect(reverseCalls).toBe(0);
  expect(backend.canvas.elements).toHaveLength(0);
  await page.getByRole("button", { name: /添加动态贴纸 DYN004/ }).click();
  await page.getByRole("button", { name: "同意并自动定位" }).click();
  await expect.poll(() => backend.canvas.elements.length).toBe(1);
  expect(backend.canvas.elements[0]?.coordinates).toEqual({ latitude: 27.9943, longitude: 120.6994 });
  expect(reverseCalls).toBe(1);
  await page.getByRole("button", { name: "删除" }).click();
  await expect.poll(() => backend.canvas.elements.length).toBe(0);
  servicePaused = true;
  await page.getByRole("button", { name: /添加动态贴纸 DYN004/ }).click();
  await page.getByRole("button", { name: "同意并自动定位" }).click();
  await expect(page.getByText("自动定位不可用，请改用手动地点贴纸。")).toBeVisible();
  await page.getByLabel("手动地点文字").fill("温州手动地点");
  await page.getByRole("button", { name: "改用手动地点贴纸" }).click();
  await expect.poll(() => backend.canvas.elements.length).toBe(1);
  expect(backend.canvas.elements[0]?.template_or_asset_id).toBe("DYN001");
  expect(backend.canvas.elements[0]?.coordinates).toBeUndefined();
  const calls = { geolocation: await page.evaluate(() => (window as Window & { __dadaLocationCalls?: number }).__dadaLocationCalls), reverse_geocode: reverseCalls };
  writeEvidence("TDD-WP4-DYN-002-location-consent", "external-calls.json", { ...calls, rejected_dada_prompt_calls: 0, service_mode: "mock", service_paused_manual_available: true });
  writeEvidence("TDD-WP4-DYN-002-location-consent", "canvas-state.json", backend.canvas);
  writeEvidence("TDD-WP4-DYN-002-location-consent", "db-diff.json", { coordinates_after_delete: null, manual_element: backend.canvas.elements[0] });
});

test("TDD-WP4-DYN-003 keeps identity overrides local and discloses DYN012 FONT081 substitution", async ({ page }) => {
  await page.clock.setFixedTime(new Date("2026-08-03T09:07:00+08:00"));
  const projectId = "00000000-0000-4000-8000-000000000860";
  const backend: Backend = { canvas: emptyCanvas(Object.keys(rawImages)[0]!), saves: 0, version: 4 };
  await routeEditor(page, projectId, backend);
  await page.goto(`${webUrl}/app/projects/${projectId}/editor`);
  await page.getByRole("button", { name: "动态贴纸", exact: true }).click();
  await page.getByRole("button", { name: /添加动态贴纸 DYN016/ }).click();
  await expect.poll(() => backend.canvas.elements[0]?.formatted_value).toBe("@dada");
  await page.getByLabel("动态贴纸显示文字").fill("@single-instance");
  await page.getByRole("button", { name: "应用显示文字" }).click();
  await expect.poll(() => backend.canvas.elements[0]?.formatted_value).toBe("@single-instance");
  await nudgeSelected(page, [{ key: "ArrowUp", times: 18 }]);
  await page.getByRole("button", { name: "动态贴纸", exact: true }).click();
  await page.getByRole("button", { name: /添加动态贴纸 DYN012/ }).click();
  await expect(page.getByText(/当前明确使用 FONT081 · Lexend Deca 替代/)).toBeVisible();
  await expect.poll(() => page.evaluate(() => document.fonts.check('16px "Dada_FONT081"'))).toBe(true);
  await expect.poll(() => backend.canvas.elements.some((element) => element.template_or_asset_id === "DYN012")).toBe(true);
  expect(backend.canvas.elements.find((element) => element.template_or_asset_id === "DYN012")).toMatchObject({
    dynamic_fields: { font_substitution: "FONT081" }, font_override: "FONT081",
  });
  expect(session.user.social_id).toBe("@@dada");
  const pixels = await stageInk(page);
  writeEvidence("TDD-WP4-DYN-003-identity-font", "canvas-state.json", backend.canvas);
  writeEvidence("TDD-WP4-DYN-003-identity-font", "db-diff.json", { account_profile: session.user, instance_override: "@single-instance", profile_changed: false });
  writeEvidence("TDD-WP4-DYN-003-identity-font", "font-load.json", { fallback: null, font_id: "FONT081", original_font: "DIN_MediumAlternate.otf", ready: true, substitution_disclosed: true });
  writeEvidence("TDD-WP4-DYN-003-identity-font", "pixel-diff.json", { ...pixels, substitution_requires_manual_review: true });
  if (process.env.DADA_EVIDENCE_DIR_COLOR_DYNAMIC) await page.screenshot({ fullPage: true, path: resolve(process.env.DADA_EVIDENCE_DIR_COLOR_DYNAMIC, "TDD-WP4-DYN-003-identity-font", "dyn012-substitution.png") });
});
