import { beforeEach, describe, it } from "@std/testing/bdd";
import { assert, assertEquals, assertMatch, assertNotEquals } from "@std/assert";
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
import { DiagramModel, WATERMARK_CELL_ID } from "../src/diagram_model.ts";
import { VERSION } from "../src/config.ts";
import { handlers as baseHandlers } from "../src/tools.ts";

function parseResult(result: CallToolResult): any {
  const content = result.content[0];
  if (content.type !== "text") {
    throw new Error(`Expected text content, got ${content.type}`);
  }
  return JSON.parse(content.text);
}

let diagramXml: string | undefined;

const handlers = new Proxy(baseHandlers, {
  get(target, prop: string) {
    const handler = target[prop as keyof typeof target] as ((args?: any) => CallToolResult) | undefined;
    if (!handler) return undefined;
    return async (args: Record<string, unknown> = {}) => {
      const result = await handler({
        ...args,
        ...(diagramXml ? { diagram_xml: diagramXml } : {}),
      });
      if (!result.isError) {
        const parsed = parseResult(result);
        if (parsed?.data?.diagram_xml) {
          diagramXml = parsed.data.diagram_xml;
        }
      }
      return result;
    };
  },
}) as unknown as Record<string, (args?: Record<string, unknown>) => Promise<CallToolResult>>;

/** Build expected date string (yyyy/mm/dd) for today. */
function todayDate(): string {
  const now = new Date();
  return `${now.getFullYear()}/${String(now.getMonth() + 1).padStart(2, "0")}/${String(now.getDate()).padStart(2, "0")}`;
}

describe("DiagramModel watermark", () => {
  let model: DiagramModel;

  beforeEach(() => {
    model = new DiagramModel();
  });

  describe("toXml output", () => {
    it("should not include watermark by default", () => {
      model.addRectangle({ text: "A" });
      const xml = model.toXml();
      assert(!xml.includes(WATERMARK_CELL_ID), "watermark should not appear without watermark option");
    });

    it("should include watermark with version, URL as text, and creation date", () => {
      model.addRectangle({ text: "A" });
      const xml = model.toXml({ watermark: true });
      assert(xml.includes(WATERMARK_CELL_ID));
      assert(xml.includes(`Generated by Draw.io MCP Server (simonkurtz-MSFT) v${VERSION}`));
      assert(xml.includes("https://github.com/simonkurtz-MSFT/drawio-mcp-server"));
      assert(xml.includes(`Creation date: ${todayDate()}`));
    });

    it("should include watermark for empty diagram", () => {
      const xml = model.toXml({ watermark: true });
      assert(xml.includes(WATERMARK_CELL_ID));
      assert(xml.includes(`Generated by Draw.io MCP Server (simonkurtz-MSFT) v${VERSION}`));
      assert(xml.includes(`Creation date: ${todayDate()}`));
    });

    it("should render watermark as plain mxCell without link", () => {
      model.addRectangle({ text: "A" });
      const xml = model.toXml({ watermark: true });
      assert(!xml.includes("<UserObject"), "should not use UserObject wrapper");
      assert(!xml.includes('link="'), "should not have a link attribute");
      assert(xml.includes(`id="${WATERMARK_CELL_ID}"`));
    });

    it("should include creation date in yyyy/mm/dd format", () => {
      model.addRectangle({ text: "A" });
      const xml = model.toXml({ watermark: true });
      assertMatch(xml, /\d{4}\/\d{2}\/\d{2}/);
    });

    it("should use black left-aligned styling", () => {
      model.addRectangle({ text: "A" });
      const xml = model.toXml({ watermark: true });
      assert(xml.includes("fontColor=#000000"));
      assert(xml.includes("align=left"));
      assert(xml.includes("fillColor=none"));
      assert(xml.includes("strokeColor=none"));
    });

    it("should position watermark at fixed offset from bottom of page", () => {
      model.addRectangle({ text: "A", x: 100, y: 100, width: 200, height: 80 });
      const xml = model.toXml({ watermark: true });
      const match = xml.match(new RegExp(`id="${WATERMARK_CELL_ID}".*?<mxGeometry x="([^"]+)" y="([^"]+)"`));
      assertNotEquals(match, null);
      const watermarkX = parseFloat(match![1]);
      const watermarkY = parseFloat(match![2]);
      // Fixed position: x=40, y=1100-50-5=1045
      assertEquals(watermarkX, 40);
      assertEquals(watermarkY, 1045);
    });

    it("should position watermark at same location for empty diagram", () => {
      const xml = model.toXml({ watermark: true });
      const match = xml.match(new RegExp(`id="${WATERMARK_CELL_ID}".*?<mxGeometry x="([^"]+)" y="([^"]+)"`));
      assertNotEquals(match, null);
      const watermarkX = parseFloat(match![1]);
      const watermarkY = parseFloat(match![2]);
      // Same fixed position regardless of content
      assertEquals(watermarkX, 40);
      assertEquals(watermarkY, 1045);
    });

    it("should position watermark at same location regardless of content layout", () => {
      const group = model.createGroup({ x: 100, y: 100, width: 300, height: 200 });
      const child = model.addRectangle({ text: "Child", x: 20, y: 20, width: 100, height: 50 });
      model.addCellToGroup(child.id, group.id);
      const xml = model.toXml({ watermark: true });
      const match = xml.match(new RegExp(`id="${WATERMARK_CELL_ID}".*?<mxGeometry x="([^"]+)" y="([^"]+)"`));
      assertNotEquals(match, null);
      const watermarkX = parseFloat(match![1]);
      const watermarkY = parseFloat(match![2]);
      // Same fixed position regardless of content
      assertEquals(watermarkX, 40);
      assertEquals(watermarkY, 1045);
    });
  });

  describe("import handling", () => {
    it("should strip watermark on import", () => {
      model.addRectangle({ text: "A" });
      const xml = model.toXml({ watermark: true });

      const newModel = new DiagramModel();
      newModel.importXml(xml);
      const cells = newModel.listCells();
      assertEquals(cells.length, 1);
      assertEquals(cells[0].value, "A");
    });

    it("should not count watermark in import cell count", () => {
      model.addRectangle({ text: "A" });
      const xml = model.toXml({ watermark: true });

      const newModel = new DiagramModel();
      const result = newModel.importXml(xml);
      assertEquals("error" in result, false);
      if (!("error" in result)) {
        assertEquals(result.cells, 1);
      }
    });

    it("should re-add watermark after roundtrip", () => {
      model.addRectangle({ text: "A" });
      const xml1 = model.toXml({ watermark: true });

      const newModel = new DiagramModel();
      newModel.importXml(xml1);
      const xml2 = newModel.toXml({ watermark: true });

      assert(xml2.includes(WATERMARK_CELL_ID));
      assert(xml2.includes(`Generated by Draw.io MCP Server (simonkurtz-MSFT) v${VERSION}`));
      assert(xml2.includes(`Creation date: ${todayDate()}`));
    });

    it("should strip watermark from compressed import", () => {
      model.addRectangle({ text: "A" });
      const xml = model.toXml({ compress: true, watermark: true });

      const newModel = new DiagramModel();
      newModel.importXml(xml);
      assertEquals(newModel.listCells().length, 1);
    });
  });

  describe("model isolation", () => {
    it("should not include watermark in listCells", () => {
      model.addRectangle({ text: "A" });
      const cells = model.listCells();
      assertEquals(cells.length, 1);
      assert(!cells.some((c) => c.id === WATERMARK_CELL_ID));
    });

    it("should not count watermark in getStats", () => {
      model.addRectangle({ text: "A" });
      const stats = model.getStats();
      assertEquals(stats.total_cells, 1);
      assertEquals(stats.vertices, 1);
    });

    it("should not return watermark from getCell", () => {
      model.addRectangle({ text: "A" });
      assertEquals(model.getCell(WATERMARK_CELL_ID), undefined);
    });
  });
});

describe("watermark through handlers", () => {
  beforeEach(() => {
    diagramXml = undefined;
  });

  it("should include watermark in exported diagram XML", async () => {
    await handlers["add-cells"]({ cells: [{ type: "vertex", text: "Test" }] });
    const result = await handlers["export-diagram"]({});
    const parsed = parseResult(result);
    assert(parsed.data.xml.includes(WATERMARK_CELL_ID));
    assert(parsed.data.xml.includes(`Generated by Draw.io MCP Server (simonkurtz-MSFT) v${VERSION}`));
  });

  it("should not count watermark in diagram stats", async () => {
    await handlers["add-cells"]({ cells: [{ type: "vertex", text: "A" }] });
    const result = await handlers["get-diagram-stats"]();
    const parsed = parseResult(result);
    assertEquals(parsed.data.stats.total_cells, 1);
  });

  it("should preserve watermark through handler roundtrips", async () => {
    await handlers["add-cells"]({ cells: [{ type: "vertex", text: "A" }] });
    await handlers["add-cells"]({ cells: [{ type: "vertex", text: "B" }] });
    const result = await handlers["get-diagram-stats"]();
    const parsed = parseResult(result);
    assertEquals(parsed.data.stats.total_cells, 2);

    const exportResult = await handlers["export-diagram"]({});
    const exported = parseResult(exportResult);
    assert(exported.data.xml.includes(WATERMARK_CELL_ID));
  });
});
