import { readdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import prettier from "prettier";

type DotResult = {
  group: string;
  raw: string;
};

const PASSED_TEST_MARKER = ".";
const FAILED_TEST_MARKER = "X";

const expectedTestSuites = readdirSync("./src/test-suites")
  .filter((file) => statSync(join("./src/test-suites", file)).isDirectory())
  .map((file) => ({
    name: file,
    cases: 0,
  }));

const suiteNameToIndexMap = new Map(
  expectedTestSuites.map((suite, index) => [suite.name, index]),
);

const gatewayResults = readdirSync("./gateways")
  .filter((file) => statSync(join("./gateways", file)).isDirectory())
  .map((id) => {
    const gatewayDetails = JSON.parse(
      readFileSync(join("./gateways", id, "gateway.json"), "utf-8"),
    ) as { name: string; repository: string; website: string };

    const lines = readFileSync(
      join("./gateways", id, "results.txt"),
      "utf-8",
    ).split("\n");

    const dot: DotResult[] = [];

    let collectingDetails = true;
    let previousGroupName: string | null = null;
    for (const line of lines) {
      if (collectingDetails) {
        if (
          line.charAt(0) === PASSED_TEST_MARKER ||
          line.charAt(0) === FAILED_TEST_MARKER
        ) {
          if (!previousGroupName) {
            throw new Error("No previous group found");
          }

          dot.push({ group: previousGroupName, raw: line });
        } else if (line.trim() === "" || line === "---") {
          collectingDetails = false;
        } else {
          previousGroupName = line;
        }
      }
    }

    const name = gatewayDetails?.name ?? id;

    return {
      id,
      name,
      repository: gatewayDetails.repository,
      website: gatewayDetails.website,
      dot,
    };
  });

for (const gateway of gatewayResults) {
  for (const { group, raw } of gateway.dot) {
    const expectedSuiteIndex = suiteNameToIndexMap.get(group);

    if (expectedSuiteIndex === undefined) {
      throw new Error(`Unknown test suite "${group}" in ${gateway.id}`);
    }

    expectedTestSuites[expectedSuiteIndex].cases = Math.max(
      expectedTestSuites[expectedSuiteIndex].cases,
      raw.length,
    );
  }
}

const missingSuiteCaseCounts = expectedTestSuites.filter(
  (suite) => suite.cases === 0,
);

if (missingSuiteCaseCounts.length > 0) {
  throw new Error(
    `Missing case counts for suites: ${missingSuiteCaseCounts.map((suite) => suite.name).join(", ")}`,
  );
}

const normalizedGatewayResults = gatewayResults.map((gateway) => {
  const dotByGroup = new Map(gateway.dot.map((dot) => [dot.group, dot.raw]));
  const dot = expectedTestSuites.map(({ name, cases }) => {
    const raw = dotByGroup.get(name) ?? FAILED_TEST_MARKER.repeat(cases);

    return {
      group: name,
      results: raw
        .replaceAll(PASSED_TEST_MARKER, "🟢")
        .replaceAll(FAILED_TEST_MARKER, "❌"),
      passed: !raw.includes(FAILED_TEST_MARKER),
      passedCases: raw
        .split("")
        .filter((char) => char === PASSED_TEST_MARKER).length,
      totalCases: raw.length,
    };
  });

  const tests = dot.reduce(
    (result, suite) => {
      result.total += suite.totalCases;
      result.passed += suite.passedCases;
      return result;
    },
    { total: 0, passed: 0, failed: 0 },
  );
  tests.failed = tests.total - tests.passed;

  const groups = {
    total: dot.length,
    passed: dot.filter((suite) => suite.passed).length,
    failed: dot.filter((suite) => !suite.passed).length,
  };

  return {
    ...gateway,
    dot,
    groups,
    tests,
  };
});

normalizedGatewayResults.sort((a, b) => {
  const diff = b.tests.passed - a.tests.passed;

  if (diff !== 0) {
    // 9 -> 1
    return diff;
  }

  // A -> Z
  return a.name.localeCompare(b.name);
});

function printResult(
  result: { passed: number; failed: number },
  format: "md" | "html",
) {
  const scores: string[] = [];

  if (result.passed) {
    if (format === "md") {
      scores.push(`🟢 ${result.passed}`);
    } else {
      scores.push(
        `<span class="text-emerald-700 mr-2">✓ ${result.passed}</span>`,
      );
    }
  }

  if (result.failed) {
    if (format === "md") {
      scores.push(`❌ ${result.failed}`);
    } else {
      scores.push(`<span class="text-red-700">✗ ${result.failed}</span>`);
    }
  }

  return scores.join("  ");
}

let tableMd = `|  Gateway   | Compatibility | Test Cases | Test Suites |
| :---------------------------: | :-----------: | :----------: | :---------: |`;
let rowsHtml = ``;
let testDetailsMd = "";

const jsonReport: Array<{
  name: string;
  cases: { passed: number; failed: number };
  suites: { passed: number; failed: number };
}> = [];

for (const gateway of normalizedGatewayResults) {
  jsonReport.push({
    name: gateway.name,
    cases: gateway.tests,
    suites: gateway.groups,
  });

  const score = ((gateway.tests.passed * 100) / gateway.tests.total).toFixed(2);
  const roundedScore = Math.round(
    (gateway.tests.passed * 100) / gateway.tests.total,
  );
  tableMd += `\n| [${gateway.name}](${gateway.website}) | ${score}% |  ${printResult(gateway.tests, "md")} | ${printResult(gateway.groups, "md")} |`;

  rowsHtml += `
    <tr class="border-b transition-colors hover:bg-gray-100/50">
      <td class="p-4 align-middle font-medium  border-l-2 border-${roundedScore === 100 ? "emerald" : roundedScore >= 75 ? "yellow" : "red"}-500">
        <a href="${gateway.website}" class="hover:underline" title="Visit ${gateway.name} website">
          ${gateway.name}
        </a>
      </td>
      <td class="p-4 align-middle font-semibold">${score}%</td>
      <td class="p-4 align-middle">
        ${printResult(gateway.tests, "html")}
      </td>
      <td class="p-4 align-middle">
        ${printResult(gateway.groups, "html")}
      </td>
      <td class="p-4 align-middle"><a href="https://github.com/the-guild-org/federation-compatibility/tree/main/REPORT.md#${gateway.id}" class="text-sky-700 hover:underline">View report</a></td>
    </tr>
  `;

  testDetailsMd += `\n<a id="${gateway.id}"></a>
### ${gateway.name}

- [Repository](${gateway.repository})
- [Website](${gateway.website})

<details>
<summary>Results</summary>\n`;

  for (const { group, results } of gateway.dot) {
    testDetailsMd += `<a href="./src/test-suites/${group}">${group}</a>\n<pre>${results}</pre>\n`;
  }

  testDetailsMd += `</details>\n`;
}

const reportMd = `# Compatibility Results

## Summary

${tableMd}

## Detailed Results

Take a closer look at the results for each gateway.

You can look at the full list of tests [here](./src/test-suites/). Every test id corresponds to a directory in the \`src/test-suites\` folder.
${testDetailsMd}`;

await writeFormatted("./REPORT.md", reportMd);

const readmeMd = readFileSync("./README.md", "utf-8");
const indexHtml = readFileSync("./website/index.html", "utf-8");

const startTag = "<!-- gateways:start -->";
const endTag = "<!-- gateways:end -->";
const mdStartAt = readmeMd.indexOf(startTag);
const mdEndAt = readmeMd.indexOf(endTag);
const htmlStartAt = indexHtml.indexOf(startTag);
const htmlEndAt = indexHtml.indexOf(endTag);

const newReadmeMd =
  readmeMd.substring(0, mdStartAt) +
  "\n\n" +
  startTag +
  "\n\n" +
  tableMd +
  "\n\n" +
  readmeMd.substring(mdEndAt);

await writeFormatted("./README.md", newReadmeMd);

const newIndexHtml =
  indexHtml.substring(0, htmlStartAt) +
  "\n" +
  startTag +
  "\n" +
  rowsHtml +
  "\n" +
  indexHtml.substring(htmlEndAt);

writeFormatted("./website/index.html", newIndexHtml);

writeFileSync(
  "./website/data.json",
  JSON.stringify(jsonReport, null, 2),
  "utf-8",
);

async function writeFormatted(filename: string, content: string) {
  writeFileSync(
    filename,
    await prettier.format(content, {
      filepath: filename,
    }),
  );
}
