import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { describe, it } from "node:test";

import { renderReviewHtml } from "../render.ts";
import type { FileContent } from "../git.ts";

const REVIEW_CSS = readFileSync(new URL("../assets/review.css", import.meta.url), "utf8");

function sampleFiles(): FileContent[] {
	return [
		{
			path: "src/app.ts",
			status: "modified",
			additions: 5,
			deletions: 2,
			binary: false,
			original: 'const x = 1;\nconst y = 2;\n',
			modified: 'const x = 10;\nconst y = 2;\nconst z = 3;\n',
		},
		{
			path: "src/new.ts",
			status: "added",
			additions: 3,
			deletions: 0,
			binary: false,
			original: "",
			modified: 'export const foo = "bar";\n',
		},
	];
}

describe("renderReviewHtml", () => {
	it("produces self-contained HTML with __FILES__ JSON, CodeMirror bundle, and CSS", () => {
		const html = renderReviewHtml(sampleFiles(), "test: branch vs main", {
			repoName: "test",
			branch: "branch",
			base: "main",
		});

		assert.match(html, /<!DOCTYPE html>/);
		assert.match(html, /window\.__FILES__\s*=/);
		assert.ok(html.includes(REVIEW_CSS), "should inline review CSS");
		assert.match(html, /globalThis\.CM\s*=/);
		assert.match(html, /window\.__FILES__/);
	});

	it("includes status bar with branch, base, file count, and stats", () => {
		const html = renderReviewHtml(sampleFiles(), "myrepo: feature vs main", {
			repoName: "myrepo",
			branch: "feature",
			base: "main",
		});

		assert.match(html, /status-bar-branch/);
		assert.match(html, /main/);
		assert.match(html, /feature/);
		assert.match(html, /2 files/);
		assert.match(html, /\+8/);
		assert.match(html, /-2/);
	});

	it("serializes file content safely via safeScriptJson", () => {
		const files: FileContent[] = [{
			path: "evil/<script>&file.ts",
			status: "modified",
			additions: 1,
			deletions: 1,
			binary: false,
			original: '<script>alert("xss")</script>',
			modified: '<script>alert("xss2")</script>',
		}];

		const html = renderReviewHtml(files, "XSS test", {
			repoName: "repo",
			branch: "b",
			base: "main",
		});

		const filesAssignment = html.match(/window\.__FILES__\s*=\s*(.*);/)?.[1] ?? "";
		assert.ok(filesAssignment.length > 0);
		assert.doesNotMatch(filesAssignment, /<script>/);
		assert.match(filesAssignment, /\\u003cscript\\u003e/);
		const parsed = JSON.parse(filesAssignment) as FileContent[];
		assert.equal(parsed[0]?.path, "evil/<script>&file.ts");
	});

	it("HTML-escapes title and context values", () => {
		const html = renderReviewHtml([], '<script>"test"</script>', {
			repoName: '<img>',
			branch: '"branch"',
			base: '&base',
		});

		assert.doesNotMatch(html.split("<script>")[0] ?? "", /<img>/);
		assert.match(html, /&lt;script&gt;/);
		assert.match(html, /&amp;base/);
	});

	it("includes favicon as data URL", () => {
		const html = renderReviewHtml([], "test", { repoName: "r", branch: "b", base: "m" });
		assert.match(html, /rel="icon" href="data:image\/svg\+xml;base64,/);
	});

	it("includes view mode toggle buttons", () => {
		const html = renderReviewHtml(sampleFiles(), "test", { repoName: "r", branch: "b", base: "m" });
		assert.match(html, /id="btn-side"/);
		assert.match(html, /id="btn-unified"/);
	});

	it("includes sidebar and editor containers", () => {
		const html = renderReviewHtml(sampleFiles(), "test", { repoName: "r", branch: "b", base: "m" });
		assert.match(html, /id="file-list"/);
		assert.match(html, /id="editor"/);
	});

	it("parses context from title when no explicit context provided", () => {
		const html = renderReviewHtml(sampleFiles(), "myrepo: feat vs develop");
		assert.match(html, /develop/);
		assert.match(html, /feat/);
	});

	it("works with empty file array", () => {
		const html = renderReviewHtml([], "test: b vs m", { repoName: "r", branch: "b", base: "m" });
		assert.match(html, /0 files/);
		assert.match(html, /\+0/);
		assert.match(html, /-0/);
	});
});
