import { expect, test } from "@playwright/test";

import {
  THREAD_FOR_WELCOME,
  THREAD_WITH_REFERENCE_FIXTURE,
  THREAD_WITH_STALE_REFERENCE,
  collectMentionCandidateKeys,
  expandComposer,
  mentionCandidateByKey,
  newChatEntry,
  openChat,
  openReferencePicker,
  rewriteFirstReferenceAsArtifact,
  reuseThreadChatEntry,
  skipIfMissingThread,
  stubReferenceFixtures,
  toastByText,
} from "./support/chat-helpers";

const REFERENCE_ARTIFACT_PATH = "/generated/reference-contract.md";
const REFERENCE_UPLOAD_FIXTURES = Array.from({ length: 11 }, (_, index) => {
  const fileNumber = String(index + 1).padStart(2, "0");
  return {
    filename: `fixture-${fileNumber}.md`,
    virtual_path: `/mnt/user-data/uploads/fixture-${fileNumber}.md`,
  };
});

test.describe("聊天工作台 / 输入区与发送", () => {
  test.describe.configure({ mode: "serial" });

  test("DF-INPUT-001 欢迎态输入框默认展开", async ({ page }, testInfo) => {
    skipIfMissingThread(testInfo, THREAD_FOR_WELCOME, "FRONTEND_E2E_THREAD_ID");
    await openChat(page, newChatEntry(THREAD_FOR_WELCOME!));

    const textarea = page.locator("textarea[name='message']");
    await expect
      .poll(async () => {
        return await textarea.evaluate((element) => {
          return Math.round(
            (element as HTMLTextAreaElement).getBoundingClientRect().height,
          );
        });
      })
      .toBeGreaterThan(120);
  });

  test("DF-INPUT-002 非欢迎态输入框可展开并在失焦后收起", async ({
    page,
  }, testInfo) => {
    skipIfMissingThread(testInfo, THREAD_FOR_WELCOME, "FRONTEND_E2E_THREAD_ID");
    await openChat(page, reuseThreadChatEntry(THREAD_FOR_WELCOME!));

    const textarea = page.locator("textarea[name='message']");
    const inputHeight = async () =>
      await textarea.evaluate((element) => {
        return Math.round(
          (element as HTMLTextAreaElement).getBoundingClientRect().height,
        );
      });

    await expect.poll(inputHeight).toBeLessThan(110);

    await page.locator("div.absolute.inset-0.z-1.cursor-text").click();
    await expect.poll(inputHeight).toBeGreaterThan(120);

    await page
      .getByRole("main")
      .first()
      .click({ position: { x: 20, y: 20 } });
    await expect.poll(inputHeight).toBeLessThan(110);
  });

  test("DF-INPUT-003 点击欢迎态建议词不会导致输入区异常", async ({
    page,
  }, testInfo) => {
    skipIfMissingThread(testInfo, THREAD_FOR_WELCOME, "FRONTEND_E2E_THREAD_ID");
    await openChat(page, newChatEntry(THREAD_FOR_WELCOME!));

    const suggestions = page.getByTestId("welcome-suggestions");
    await expect(suggestions).toBeVisible();
    await suggestions.locator("button").first().click();

    const textarea = page.locator("textarea[name='message']");
    await expect(textarea).toBeVisible();
    await expect(page.locator(".is-user")).toHaveCount(0);
  });

  test("DF-INPUT-004 空消息不可提交", async ({ page }, testInfo) => {
    skipIfMissingThread(testInfo, THREAD_FOR_WELCOME, "FRONTEND_E2E_THREAD_ID");
    await openChat(page, newChatEntry(THREAD_FOR_WELCOME!));

    const textarea = page.locator("textarea[name='message']");
    const submit = page.locator("button[aria-label='Submit']");

    await textarea.fill("   ");

    await expect(submit).toBeDisabled();
    await expect(page.locator(".is-user")).toHaveCount(0);
  });

  test("DF-INPUT-005 发送后输入框清空且只产生一条用户消息", async ({
    page,
  }, testInfo) => {
    skipIfMissingThread(testInfo, THREAD_FOR_WELCOME, "FRONTEND_E2E_THREAD_ID");
    await openChat(page, newChatEntry(THREAD_FOR_WELCOME!));

    const textarea = page.locator("textarea[name='message']");
    const submit = page.locator("button[aria-label='Submit']");
    await textarea.click();
    await textarea.fill("你好，测试发送");
    await submit.evaluate((button) => {
      (button as HTMLButtonElement).click();
    });

    await expect(
      page.locator(".is-user").filter({ hasText: /你好，测试发送|测试发送/ }),
    ).toHaveCount(1);
    await expect(textarea).toHaveValue("");
  });

  test("DF-INPUT-006 快速重复点击发送不会重复提交", async ({
    page,
  }, testInfo) => {
    skipIfMissingThread(testInfo, THREAD_FOR_WELCOME, "FRONTEND_E2E_THREAD_ID");
    await openChat(page, newChatEntry(THREAD_FOR_WELCOME!));

    const textarea = page.locator("textarea[name='message']");
    const submit = page.locator("button[aria-label='Submit']");

    await textarea.fill("重复提交测试");
    await submit.evaluate((button) => {
      const target = button as HTMLButtonElement;
      target.click();
      target.click();
      target.click();
    });

    await expect(
      page.locator(".is-user").filter({ hasText: "重复提交测试" }),
    ).toHaveCount(1);
  });

  test("DF-INPUT-007 输入@时展示文件候选并可选择为引用 chip", async ({
    page,
  }, testInfo) => {
    skipIfMissingThread(
      testInfo,
      THREAD_WITH_REFERENCE_FIXTURE,
      "FRONTEND_E2E_ARTIFACTS_THREAD_ID 或 FRONTEND_E2E_THREAD_ID",
    );
    await stubReferenceFixtures(page, {
      threadId: THREAD_WITH_REFERENCE_FIXTURE!,
      artifactPaths: [REFERENCE_ARTIFACT_PATH],
      uploadFiles: REFERENCE_UPLOAD_FIXTURES,
    });
    await openChat(page, reuseThreadChatEntry(THREAD_WITH_REFERENCE_FIXTURE!));

    await expandComposer(page);
    const textarea = page.locator("textarea[name='message']");
    await textarea.fill("请基于这个文件回答 ");
    const { panel } = await openReferencePicker(page);
    const items = page.getByTestId("mention-candidate-item");
    const itemCount = await items.count();
    testInfo.skip(itemCount === 0, "当前线程没有可引用文件候选。");

    await items.first().click();
    await expect(textarea).toBeFocused();
    await expect(textarea).toHaveValue(/请基于这个文件回答/);
    await expect(page.getByTestId("reference-inline-preview")).toBeVisible();
    await expect(page.getByTestId("reference-chip")).toHaveCount(1);
    await expect(page.getByTestId("reference-chip-remove")).toHaveCount(1);
    await expect(panel).toBeHidden();
  });

  test("DF-INPUT-008 失效引用不会阻断文本发送（可解释 skip）", async ({
    page,
  }, testInfo) => {
    skipIfMissingThread(
      testInfo,
      THREAD_WITH_STALE_REFERENCE,
      "FRONTEND_E2E_ARTIFACTS_THREAD_ID 或 FRONTEND_E2E_THREAD_ID",
    );
    await stubReferenceFixtures(page, {
      threadId: THREAD_WITH_STALE_REFERENCE!,
      artifactPaths: [REFERENCE_ARTIFACT_PATH],
      uploadFiles: REFERENCE_UPLOAD_FIXTURES,
    });
    await openChat(page, reuseThreadChatEntry(THREAD_WITH_STALE_REFERENCE!));

    await expandComposer(page);
    const textarea = page.locator("textarea[name='message']");
    await textarea.fill("stale 引用回归验证 ");

    await openReferencePicker(page);
    const uploadItem = page
      .locator(
        '[data-testid="mention-candidate-item"][data-candidate-key^="upload:"]',
      )
      .first();
    await expect(uploadItem).toBeVisible();
    await uploadItem.click();
    await expect(page.getByTestId("reference-chip")).toHaveCount(1);

    const stalePath = REFERENCE_ARTIFACT_PATH;
    let staleArtifactRequested = false;
    await rewriteFirstReferenceAsArtifact(page, stalePath);
    await expect(page.getByTestId("reference-chip").first()).toContainText(
      "生成文件",
    );
    await page.route(
      new RegExp(
        `/api/threads/${THREAD_WITH_STALE_REFERENCE}/artifacts${stalePath.replace(/[.*+?^${}()|[\\]\\\\]/g, "\\$&")}(\\?.*)?$`,
      ),
      async (route) => {
        staleArtifactRequested = true;
        await route.fulfill({
          status: 404,
          contentType: "application/json",
          body: JSON.stringify({ detail: "e2e stale artifact" }),
        });
      },
      { times: 1 },
    );

    const submitRequest = page.waitForRequest(
      (request) =>
        request.method() === "POST" &&
        request
          .url()
          .includes(
            `/api/langgraph/threads/${THREAD_WITH_STALE_REFERENCE}/runs/stream`,
          ),
    );
    await page.locator("button[aria-label='Submit']").click();
    await expect.poll(() => staleArtifactRequested).toBe(true);
    await submitRequest;

    await expect(
      toastByText(page, "部分引用文件已失效，已自动移除并继续发送。"),
    ).toBeVisible();
    await expect(page.locator("textarea[name='message']")).toHaveValue("");
  });

  test("DF-INPUT-008A 提交态附加优先提示但消息区只显示原文", async ({
    page,
  }, testInfo) => {
    skipIfMissingThread(
      testInfo,
      THREAD_WITH_REFERENCE_FIXTURE,
      "FRONTEND_E2E_ARTIFACTS_THREAD_ID 或 FRONTEND_E2E_THREAD_ID",
    );
    await stubReferenceFixtures(page, {
      threadId: THREAD_WITH_REFERENCE_FIXTURE!,
      artifactPaths: [REFERENCE_ARTIFACT_PATH],
      uploadFiles: REFERENCE_UPLOAD_FIXTURES,
    });
    await openChat(page, reuseThreadChatEntry(THREAD_WITH_REFERENCE_FIXTURE!));

    await expandComposer(page);
    const textarea = page.locator("textarea[name='message']");
    const userInput = "请根据引用文件给出摘要";
    await textarea.fill(`${userInput} `);

    await openReferencePicker(page);
    const firstReference = page.getByTestId("mention-candidate-item").first();
    await expect(firstReference).toBeVisible();
    const referenceName =
      REFERENCE_UPLOAD_FIXTURES[0]?.filename ?? "fixture-01.md";
    await firstReference.click();
    await expect(page.getByTestId("reference-chip")).toHaveCount(1);

    const submitRequest = page.waitForRequest(
      (request) =>
        request.method() === "POST" &&
        request
          .url()
          .includes(
            `/api/langgraph/threads/${THREAD_WITH_REFERENCE_FIXTURE}/runs/stream`,
          ),
    );

    await page.locator("button[aria-label='Submit']").click();
    const request = await submitRequest;
    const requestBody = request.postData() ?? "";

    expect(requestBody).toContain(userInput);
    expect(requestBody).toContain("XClaw优先使用【");
    expect(requestBody).toContain(referenceName);
    await expect(
      page.locator(".is-user").filter({ hasText: "XClaw优先使用【" }),
    ).toHaveCount(0);
  });

  test("DF-INPUT-009 引用上限为 10，第 11 个被阻止并提示", async ({
    page,
  }, testInfo) => {
    skipIfMissingThread(
      testInfo,
      THREAD_WITH_REFERENCE_FIXTURE,
      "FRONTEND_E2E_ARTIFACTS_THREAD_ID 或 FRONTEND_E2E_THREAD_ID",
    );
    await stubReferenceFixtures(page, {
      threadId: THREAD_WITH_REFERENCE_FIXTURE!,
      artifactPaths: [REFERENCE_ARTIFACT_PATH],
      uploadFiles: REFERENCE_UPLOAD_FIXTURES,
    });
    await openChat(page, reuseThreadChatEntry(THREAD_WITH_REFERENCE_FIXTURE!));

    await expandComposer(page);
    const textarea = page.locator("textarea[name='message']");
    await textarea.fill("请参考这些文件 ");

    await openReferencePicker(page);
    const candidateKeys = await collectMentionCandidateKeys(page);
    testInfo.skip(
      candidateKeys.length < 11,
      "当前线程候选文件不足 11 个，无法验证 10 个上限。",
    );

    for (const [index, key] of candidateKeys.slice(0, 10).entries()) {
      await openReferencePicker(page);
      const candidate = mentionCandidateByKey(page, key);
      await expect(candidate).toBeVisible();
      await candidate.evaluate((element) => {
        (element as HTMLElement).click();
      });
      await expect(page.getByTestId("reference-chip-remove")).toHaveCount(
        index + 1,
      );
    }

    await expect(page.getByTestId("reference-chip-remove")).toHaveCount(10);

    await openReferencePicker(page);
    const blockedCandidate = mentionCandidateByKey(page, candidateKeys[10]!);
    await expect(blockedCandidate).toBeVisible();
    await blockedCandidate.evaluate((element) => {
      (element as HTMLElement).click();
    });

    await expect(page.getByTestId("reference-chip-remove")).toHaveCount(10);
    await expect(toastByText(page, "单条消息最多引用 10 个文件")).toBeVisible();
  });
});
