import { describe, expect, it } from "vitest";

import {
  graphPatchSchema,
  validateEdgeReferences,
  validateEdgeTypes,
  validateGraphPatch,
  validateNodeTypes,
  validateUniqueIds,
} from "./mutation-schema";

import type { GraphPatch } from "./mutation-schema";

function createTestPatch(overrides: Partial<GraphPatch> = {}): GraphPatch {
  return {
    meta: { diagramType: "flowchart", title: "Test Diagram" },
    nodes: [
      { id: "n1", type: "terminal", label: "Start" },
      { id: "n2", type: "process", label: "Do something" },
      { id: "n3", type: "terminal", label: "End" },
    ],
    edges: [
      { id: "e1", from: "n1", to: "n2" },
      { id: "e2", from: "n2", to: "n3" },
    ],
    ...overrides,
  };
}

describe("graphPatchSchema", () => {
  it("should accept a valid flowchart patch", () => {
    const patch = createTestPatch();
    const result = graphPatchSchema.safeParse(patch);
    expect(result.success).toBe(true);
  });

  it("should accept a valid BPMN patch with pools", () => {
    const patch = createTestPatch({
      meta: { diagramType: "bpmn", title: "BPMN Process" },
      nodes: [
        { id: "n1", type: "start-event", label: "Start", lane: "lane1" },
        { id: "n2", type: "activity", label: "Task", lane: "lane1" },
      ],
      edges: [{ id: "e1", from: "n1", to: "n2", type: "sequence" }],
      pools: [
        {
          id: "pool1",
          label: "Customer",
          lanes: [{ id: "lane1", label: "Web" }],
        },
      ],
    });
    const result = graphPatchSchema.safeParse(patch);
    expect(result.success).toBe(true);
  });

  it("should accept a valid E-R patch with columns", () => {
    const patch = createTestPatch({
      meta: { diagramType: "er", title: "Schema" },
      nodes: [
        {
          id: "n1",
          type: "entity",
          label: "User",
          columns: [
            { name: "id", type: "uuid", isPrimaryKey: true },
            { name: "email", type: "varchar", isUnique: true },
          ],
        },
      ],
      edges: [],
    });
    const result = graphPatchSchema.safeParse(patch);
    expect(result.success).toBe(true);
  });

  it("should accept a valid sequence patch with lifeline", () => {
    const patch = createTestPatch({
      meta: { diagramType: "sequence", title: "Flow" },
      nodes: [
        { id: "n1", type: "participant", label: "Client", lifeline: true },
        { id: "n2", type: "participant", label: "Server", lifeline: true },
      ],
      edges: [{ id: "e1", from: "n1", to: "n2", type: "sync", label: "request" }],
    });
    const result = graphPatchSchema.safeParse(patch);
    expect(result.success).toBe(true);
  });

  it("should reject patch without meta", () => {
    const result = graphPatchSchema.safeParse({
      nodes: [{ id: "n1", type: "process", label: "A" }],
      edges: [],
    });
    expect(result.success).toBe(false);
  });

  it("should reject patch without nodes", () => {
    const result = graphPatchSchema.safeParse({
      meta: { diagramType: "flowchart", title: "Test" },
      edges: [],
    });
    expect(result.success).toBe(false);
  });

  it("should reject node without required fields", () => {
    const result = graphPatchSchema.safeParse({
      meta: { diagramType: "flowchart", title: "Test" },
      nodes: [{ id: "n1" }],
      edges: [],
    });
    expect(result.success).toBe(false);
  });

  it("should reject edge without from/to", () => {
    const result = graphPatchSchema.safeParse({
      meta: { diagramType: "flowchart", title: "Test" },
      nodes: [{ id: "n1", type: "process", label: "A" }],
      edges: [{ id: "e1", from: "n1" }],
    });
    expect(result.success).toBe(false);
  });

  it("should reject more than 200 nodes", () => {
    const nodes = Array.from({ length: 201 }, (_, i) => ({
      id: `n${i}`,
      type: "process",
      label: `Node ${i}`,
    }));
    const result = graphPatchSchema.safeParse({
      meta: { diagramType: "flowchart", title: "Too Many" },
      nodes,
      edges: [],
    });
    expect(result.success).toBe(false);
  });

  it("should accept optional layoutDirection", () => {
    const patch = createTestPatch({
      meta: { diagramType: "flowchart", title: "Test", layoutDirection: "RIGHT" },
    });
    const result = graphPatchSchema.safeParse(patch);
    expect(result.success).toBe(true);
  });

  it("should accept groups", () => {
    const patch = createTestPatch({
      groups: [{ id: "g1", label: "Group A", color: "#ff0000" }],
      nodes: [
        { id: "n1", type: "process", label: "Task", group: "g1" },
        { id: "n2", type: "terminal", label: "End" },
      ],
      edges: [{ id: "e1", from: "n1", to: "n2" }],
    });
    const result = graphPatchSchema.safeParse(patch);
    expect(result.success).toBe(true);
  });
});

describe("validateNodeTypes", () => {
  it("should accept valid BPMN node types", () => {
    const patch = createTestPatch({
      meta: { diagramType: "bpmn", title: "Test" },
      nodes: [
        { id: "n1", type: "activity", label: "Task" },
        { id: "n2", type: "gateway-exclusive", label: "Decision" },
        { id: "n3", type: "start-event", label: "Start" },
      ],
    });
    expect(validateNodeTypes(patch)).toEqual([]);
  });

  it("should reject invalid BPMN node types", () => {
    const patch = createTestPatch({
      meta: { diagramType: "bpmn", title: "Test" },
      nodes: [{ id: "n1", type: "invalid-type", label: "Bad" }],
    });
    const errors = validateNodeTypes(patch);
    expect(errors).toHaveLength(1);
    expect(errors[0]).toContain("invalid-type");
  });

  it("should strip prefix before validation", () => {
    const patch = createTestPatch({
      meta: { diagramType: "bpmn", title: "Test" },
      nodes: [{ id: "n1", type: "bpmn:activity", label: "Task" }],
    });
    expect(validateNodeTypes(patch)).toEqual([]);
  });

  it("should accept valid architecture node types", () => {
    const patch = createTestPatch({
      meta: { diagramType: "architecture", title: "Test" },
      nodes: [
        { id: "n1", type: "service", label: "API" },
        { id: "n2", type: "database", label: "DB" },
        { id: "n3", type: "queue", label: "Queue" },
        { id: "n4", type: "loadbalancer", label: "LB" },
        { id: "n5", type: "external", label: "Ext" },
      ],
    });
    expect(validateNodeTypes(patch)).toEqual([]);
  });

  it("should accept valid flowchart node types", () => {
    const patch = createTestPatch({
      meta: { diagramType: "flowchart", title: "Test" },
      nodes: [
        { id: "n1", type: "process", label: "Do" },
        { id: "n2", type: "decision", label: "?" },
        { id: "n3", type: "terminal", label: "End" },
        { id: "n4", type: "io", label: "Read" },
        { id: "n5", type: "subprocess", label: "Sub" },
      ],
    });
    expect(validateNodeTypes(patch)).toEqual([]);
  });

  it("should accept valid sequence node types", () => {
    const patch = createTestPatch({
      meta: { diagramType: "sequence", title: "Test" },
      nodes: [
        { id: "n1", type: "participant", label: "Client" },
        { id: "n2", type: "fragment", label: "alt" },
      ],
    });
    expect(validateNodeTypes(patch)).toEqual([]);
  });
});

describe("validateEdgeTypes", () => {
  it("should accept valid BPMN edge types", () => {
    const patch = createTestPatch({
      meta: { diagramType: "bpmn", title: "Test" },
      nodes: [
        { id: "n1", type: "activity", label: "A" },
        { id: "n2", type: "activity", label: "B" },
      ],
      edges: [
        { id: "e1", from: "n1", to: "n2", type: "sequence" },
        { id: "e2", from: "n1", to: "n2", type: "message" },
      ],
    });
    expect(validateEdgeTypes(patch)).toEqual([]);
  });

  it("should reject invalid BPMN edge types", () => {
    const patch = createTestPatch({
      meta: { diagramType: "bpmn", title: "Test" },
      nodes: [
        { id: "n1", type: "activity", label: "A" },
        { id: "n2", type: "activity", label: "B" },
      ],
      edges: [{ id: "e1", from: "n1", to: "n2", type: "invalid" }],
    });
    const errors = validateEdgeTypes(patch);
    expect(errors).toHaveLength(1);
    expect(errors[0]).toContain("invalid");
  });

  it("should accept valid sequence edge types", () => {
    const patch = createTestPatch({
      meta: { diagramType: "sequence", title: "Test" },
      nodes: [
        { id: "n1", type: "participant", label: "A" },
        { id: "n2", type: "participant", label: "B" },
      ],
      edges: [
        { id: "e1", from: "n1", to: "n2", type: "sync" },
        { id: "e2", from: "n2", to: "n1", type: "return" },
        { id: "e3", from: "n1", to: "n2", type: "async" },
      ],
    });
    expect(validateEdgeTypes(patch)).toEqual([]);
  });

  it("should skip validation for types with no edge type constraints", () => {
    const patch = createTestPatch({
      meta: { diagramType: "er", title: "Test" },
      nodes: [{ id: "n1", type: "entity", label: "A" }],
      edges: [{ id: "e1", from: "n1", to: "n1", type: "anything" }],
    });
    expect(validateEdgeTypes(patch)).toEqual([]);
  });

  it("should allow edges without type for constrained diagram types", () => {
    const patch = createTestPatch({
      meta: { diagramType: "bpmn", title: "Test" },
      nodes: [
        { id: "n1", type: "activity", label: "A" },
        { id: "n2", type: "activity", label: "B" },
      ],
      edges: [{ id: "e1", from: "n1", to: "n2" }],
    });
    expect(validateEdgeTypes(patch)).toEqual([]);
  });
});

describe("validateEdgeReferences", () => {
  it("should accept edges referencing existing nodes", () => {
    const patch = createTestPatch();
    expect(validateEdgeReferences(patch)).toEqual([]);
  });

  it("should reject edges referencing non-existent source", () => {
    const patch = createTestPatch({
      edges: [{ id: "e1", from: "missing", to: "n1" }],
    });
    const errors = validateEdgeReferences(patch);
    expect(errors).toHaveLength(1);
    expect(errors[0]).toContain("missing");
  });

  it("should reject edges referencing non-existent target", () => {
    const patch = createTestPatch({
      edges: [{ id: "e1", from: "n1", to: "missing" }],
    });
    const errors = validateEdgeReferences(patch);
    expect(errors).toHaveLength(1);
    expect(errors[0]).toContain("missing");
  });
});

describe("validateGraphPatch", () => {
  it("should return success for valid patch", () => {
    const result = validateGraphPatch(createTestPatch());
    expect(result.success).toBe(true);
    if (result.success) {
      expect(result.data.nodes).toHaveLength(3);
      expect(result.data.edges).toHaveLength(2);
    }
  });

  it("should return errors for invalid schema", () => {
    const result = validateGraphPatch({ invalid: true });
    expect(result.success).toBe(false);
    if (!result.success) {
      expect(result.errors.length).toBeGreaterThan(0);
    }
  });

  it("should return errors for invalid node types", () => {
    const result = validateGraphPatch({
      meta: { diagramType: "bpmn", title: "Test" },
      nodes: [{ id: "n1", type: "invalid", label: "Bad" }],
      edges: [],
    });
    expect(result.success).toBe(false);
  });

  it("should return errors for broken edge references", () => {
    const result = validateGraphPatch({
      meta: { diagramType: "flowchart", title: "Test" },
      nodes: [{ id: "n1", type: "process", label: "A" }],
      edges: [{ id: "e1", from: "n1", to: "missing" }],
    });
    expect(result.success).toBe(false);
  });

  it("should combine node type and edge reference errors", () => {
    const result = validateGraphPatch({
      meta: { diagramType: "bpmn", title: "Test" },
      nodes: [{ id: "n1", type: "invalid", label: "Bad" }],
      edges: [{ id: "e1", from: "n1", to: "missing" }],
    });
    expect(result.success).toBe(false);
    if (!result.success) {
      expect(result.errors.length).toBe(2);
    }
  });

  it("should reject invalid diagramType", () => {
    const result = validateGraphPatch({
      meta: { diagramType: "unknown", title: "Test" },
      nodes: [{ id: "n1", type: "process", label: "A" }],
      edges: [],
    });
    expect(result.success).toBe(false);
  });

  it("should reject empty title", () => {
    const result = validateGraphPatch({
      meta: { diagramType: "flowchart", title: "" },
      nodes: [{ id: "n1", type: "process", label: "A" }],
      edges: [],
    });
    expect(result.success).toBe(false);
  });

  it("should reject duplicate node IDs", () => {
    const result = validateGraphPatch({
      meta: { diagramType: "flowchart", title: "Test" },
      nodes: [
        { id: "n1", type: "process", label: "A" },
        { id: "n1", type: "decision", label: "B" },
      ],
      edges: [],
    });
    expect(result.success).toBe(false);
    if (!result.success) {
      expect(result.errors[0]).toContain("Duplicate node ID");
    }
  });

  it("should reject duplicate edge IDs", () => {
    const result = validateGraphPatch({
      meta: { diagramType: "flowchart", title: "Test" },
      nodes: [
        { id: "n1", type: "process", label: "A" },
        { id: "n2", type: "process", label: "B" },
      ],
      edges: [
        { id: "e1", from: "n1", to: "n2" },
        { id: "e1", from: "n2", to: "n1" },
      ],
    });
    expect(result.success).toBe(false);
    if (!result.success) {
      expect(result.errors[0]).toContain("Duplicate edge ID");
    }
  });
});

describe("validateUniqueIds", () => {
  it("should accept unique IDs", () => {
    const patch = createTestPatch();
    expect(validateUniqueIds(patch)).toEqual([]);
  });

  it("should detect duplicate node IDs", () => {
    const patch = createTestPatch({
      nodes: [
        { id: "n1", type: "process", label: "A" },
        { id: "n1", type: "process", label: "B" },
      ],
    });
    const errors = validateUniqueIds(patch);
    expect(errors).toHaveLength(1);
    expect(errors[0]).toContain("n1");
  });

  it("should detect duplicate edge IDs", () => {
    const patch = createTestPatch({
      edges: [
        { id: "e1", from: "n1", to: "n2" },
        { id: "e1", from: "n2", to: "n3" },
      ],
    });
    const errors = validateUniqueIds(patch);
    expect(errors).toHaveLength(1);
    expect(errors[0]).toContain("e1");
  });

  it("should allow same ID across nodes and edges", () => {
    const patch = createTestPatch({
      nodes: [
        { id: "x1", type: "process", label: "A" },
        { id: "x2", type: "process", label: "B" },
      ],
      edges: [{ id: "x1", from: "x1", to: "x2" }],
    });
    expect(validateUniqueIds(patch)).toEqual([]);
  });
});
