import type { FullConfig, FullResult, Reporter, TestCase, TestResult } from "@playwright/test/reporter";
import fs from "node:fs";
import path from "node:path";

type Options = { runId: string };

function safeName(value: string) {
  return value.replace(/[\\/:*?"<>|]/g, "_").replace(/\s+/g, "-").slice(0, 100);
}

function relativeTestPath(test: TestCase) {
  const file = test.location.file.replace(/\\/g, "/");
  const marker = "/tests/e2e/";
  const index = file.lastIndexOf(marker);
  return index >= 0 ? file.slice(index + marker.length) : path.basename(file);
}

export default class ChineseFailureReporter implements Reporter {
  private readonly options: Options;
  private config!: FullConfig;
  private failed = 0;

  constructor(options: Options) {
    this.options = options;
  }

  onConfigure(config: FullConfig) {
    this.config = config;
    fs.mkdirSync(path.join(projectRoot(config), "test-reports", this.options.runId, "failures"), { recursive: true });
  }

  onBegin(config: FullConfig) {
    this.config = config;
    fs.mkdirSync(path.join(projectRoot(config), "test-reports", this.options.runId, "failures"), { recursive: true });
  }

  onTestEnd(test: TestCase, result: TestResult) {
    if (result.status === "passed" || result.status === "skipped") return;
    this.failed += 1;
    const testFile = relativeTestPath(test).replace(/\.ts$/, "");
    const target = path.join(projectRoot(this.config), "test-reports", this.options.runId, "failures", testFile, safeName(test.title));
    fs.mkdirSync(target, { recursive: true });
    const attachments: string[] = [];
    for (const attachment of result.attachments) {
      if (!attachment.path) continue;
      const targetPath = path.join(target, `${safeName(attachment.name)}${path.extname(attachment.path)}`);
      fs.copyFileSync(attachment.path, targetPath);
      attachments.push(path.relative(projectRoot(this.config), targetPath));
    }
    const errors = result.errors.map((error) => error.message ?? error.stack ?? "未知错误").join("\n\n");
    fs.writeFileSync(path.join(target, "失败说明.md"), `# ${test.title}\n\n- 文件：\`${relativeTestPath(test)}\`\n- 项目：${test.parent.project()?.name ?? "未知"}\n- 重试次数：${result.retry}\n- 状态：${result.status}\n\n## 失败原因\n\n${errors || "未捕获到错误文本，请查看 trace。"}\n\n## 附件\n\n${attachments.length ? attachments.map((item) => `- \`${item}\``).join("\n") : "无"}\n`, "utf8");
  }

  onEnd(result: FullResult) {
    const root = path.join(projectRoot(this.config), "test-reports", this.options.runId);
    fs.mkdirSync(root, { recursive: true });
    fs.writeFileSync(path.join(root, "SUMMARY.md"), `# 第 ${this.options.runId} 轮测试\n\n- 总体状态：${result.status}\n- 失败测试数：${this.failed}\n- 失败详情：${this.failed ? "见 failures/ 目录" : "无"}\n`, "utf8");
  }
}

function projectRoot(config: FullConfig) {
  return path.resolve(config.rootDir, "../..");
}
