/**
 * MermaidViewer XSS Protection Tests
 * Tests defense-in-depth security layers for Mermaid diagram rendering
 */

import { describe, it, expect, vi, beforeEach } from "vitest";
import { render, waitFor } from "@testing-library/react";
import { MermaidViewer } from "./MermaidViewer";

// Mock mermaid
vi.mock("mermaid", () => ({
  default: {
    initialize: vi.fn(),
    render: vi.fn(),
  },
}));

describe("MermaidViewer XSS Protection", () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  describe("Script tag injection", () => {
    it("should block script tags in labels", async () => {
      const maliciousDiagram = `graph TD
        A["<script>alert('XSS')</script>"]`;

      const { container } = render(<MermaidViewer diagram={maliciousDiagram} />);

      await waitFor(() => {
        const content = container.innerHTML;
        // Should not contain script tags
        expect(content).not.toContain("<script>");
        expect(content).not.toContain("alert");
      });
    });

    it("should block script tags with various casings", async () => {
      const maliciousDiagram = `graph TD
        A["<ScRiPt>alert('XSS')</sCrIpT>"]`;

      const { container } = render(<MermaidViewer diagram={maliciousDiagram} />);

      await waitFor(() => {
        const content = container.innerHTML.toLowerCase();
        expect(content).not.toContain("<script>");
      });
    });
  });

  describe("Event handler injection", () => {
    it("should block onerror event handlers", async () => {
      const maliciousDiagram = `graph TD
        A["<img src=x onerror=alert(1)>"]`;

      const { container } = render(<MermaidViewer diagram={maliciousDiagram} />);

      await waitFor(() => {
        const content = container.innerHTML;
        expect(content).not.toContain("onerror");
        expect(content).not.toContain("alert(1)");
      });
    });

    it("should block onclick event handlers", async () => {
      const maliciousDiagram = `graph TD
        A["<div onclick=alert(1)>Click</div>"]`;

      const { container } = render(<MermaidViewer diagram={maliciousDiagram} />);

      await waitFor(() => {
        const content = container.innerHTML;
        expect(content).not.toContain("onclick");
      });
    });

    it("should block onload event handlers", async () => {
      const maliciousDiagram = `graph TD
        A["<body onload=alert(1)>"]`;

      const { container } = render(<MermaidViewer diagram={maliciousDiagram} />);

      await waitFor(() => {
        const content = container.innerHTML;
        expect(content).not.toContain("onload");
      });
    });
  });

  describe("JavaScript URL injection", () => {
    it("should block javascript: URLs in href", async () => {
      const maliciousDiagram = `graph TD
        A["<a href='javascript:alert(1)'>Click</a>"]`;

      const { container } = render(<MermaidViewer diagram={maliciousDiagram} />);

      await waitFor(() => {
        const content = container.innerHTML;
        expect(content).not.toContain("javascript:");
      });
    });

    it("should block javascript: URLs in src", async () => {
      const maliciousDiagram = `graph TD
        A["<img src='javascript:alert(1)'>"]`;

      const { container } = render(<MermaidViewer diagram={maliciousDiagram} />);

      await waitFor(() => {
        const content = container.innerHTML;
        expect(content).not.toContain("javascript:");
      });
    });
  });

  describe("Data URL injection", () => {
    it("should block data URLs with scripts", async () => {
      const maliciousDiagram = `graph TD
        A["<img src='data:text/html,<script>alert(1)</script>'>"]`;

      const { container } = render(<MermaidViewer diagram={maliciousDiagram} />);

      await waitFor(() => {
        const content = container.innerHTML;
        expect(content).not.toContain("data:text/html");
        expect(content).not.toContain("<script>");
      });
    });

    it("should block data URLs with base64 encoded scripts", async () => {
      const maliciousDiagram = `graph TD
        A["<object data='data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg=='>"]`;

      const { container } = render(<MermaidViewer diagram={maliciousDiagram} />);

      await waitFor(() => {
        const content = container.innerHTML;
        expect(content).not.toContain("data:text/html");
      });
    });
  });

  describe("SVG injection", () => {
    it("should block SVG with embedded scripts", async () => {
      const maliciousDiagram = `graph TD
        A["<svg><script>alert(1)</script></svg>"]`;

      const { container } = render(<MermaidViewer diagram={maliciousDiagram} />);

      await waitFor(() => {
        const content = container.innerHTML;
        // SVG should be sanitized to remove scripts
        expect(content).not.toContain("<script>");
      });
    });

    it("should block SVG with xlink:href", async () => {
      const maliciousDiagram = `graph TD
        A["<svg><use xlink:href='javascript:alert(1)'/></svg>"]`;

      const { container } = render(<MermaidViewer diagram={maliciousDiagram} />);

      await waitFor(() => {
        const content = container.innerHTML;
        expect(content).not.toContain("javascript:");
      });
    });
  });

  describe("HTML entity bypass", () => {
    it("should block HTML entity encoded scripts", async () => {
      const maliciousDiagram = `graph TD
        A["&lt;script&gt;alert(1)&lt;/script&gt;"]`;

      const { container } = render(<MermaidViewer diagram={maliciousDiagram} />);

      await waitFor(() => {
        const content = container.innerHTML;
        // Should not execute the decoded script
        expect(content).not.toContain("alert(1)");
      });
    });
  });

  describe("Safe content", () => {
    it("should allow safe plain text labels", async () => {
      const safeDiagram = `graph TD
        A["Safe Label"]
        B["Another Safe Label"]
        A --> B`;

      const { container } = render(<MermaidViewer diagram={safeDiagram} />);

      await waitFor(() => {
        // Should render without errors
        expect(container.querySelector(".mermaid-container")).toBeTruthy();
      });
    });

    it("should allow safe special characters", async () => {
      const safeDiagram = `graph TD
        A["Label with & and # and @"]`;

      const { container } = render(<MermaidViewer diagram={safeDiagram} />);

      await waitFor(() => {
        expect(container.querySelector(".mermaid-container")).toBeTruthy();
      });
    });
  });

  describe("Error display (SEC-WEB-33)", () => {
    it("should not display raw diagram source when rendering fails", async () => {
      const sensitiveSource = `graph TD
        A["SECRET_API_KEY=abc123"]
        B["password: hunter2"]`;

      // Mock mermaid to throw an error containing the diagram source
      const mermaid = await import("mermaid");
      vi.mocked(mermaid.default.render).mockRejectedValue(
        new Error(`Parse error in diagram: ${sensitiveSource}`)
      );

      const { container } = render(<MermaidViewer diagram={sensitiveSource} />);

      await waitFor(() => {
        const content = container.innerHTML;
        // Should show generic error message, not raw source or detailed error
        expect(content).toContain("Diagram rendering failed");
        expect(content).not.toContain("SECRET_API_KEY");
        expect(content).not.toContain("password: hunter2");
        expect(content).not.toContain("Parse error in diagram");
      });
    });

    it("should not expose detailed error messages in the UI", async () => {
      const diagram = `graph TD
        A["Test"]`;

      const mermaid = await import("mermaid");
      vi.mocked(mermaid.default.render).mockRejectedValue(
        new Error("Lexical error on line 2. Unrecognized text at /internal/path/file.ts")
      );

      const { container } = render(<MermaidViewer diagram={diagram} />);

      await waitFor(() => {
        const content = container.innerHTML;
        expect(content).toContain("Diagram rendering failed");
        expect(content).not.toContain("Lexical error");
        expect(content).not.toContain("/internal/path/file.ts");
      });
    });

    it("should not display a pre tag with raw diagram source on error", async () => {
      const diagram = `graph TD
        A["Node A"]`;

      const mermaid = await import("mermaid");
      vi.mocked(mermaid.default.render).mockRejectedValue(new Error("render failed"));

      const { container } = render(<MermaidViewer diagram={diagram} />);

      await waitFor(() => {
        // There should be no <pre> element showing raw diagram source
        const preElements = container.querySelectorAll("pre");
        expect(preElements.length).toBe(0);
      });
    });

    it("should log the detailed error to console.error", async () => {
      const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => undefined);
      const diagram = `graph TD
        A["Test"]`;
      const originalError = new Error("Detailed parse error at line 2");

      const mermaid = await import("mermaid");
      vi.mocked(mermaid.default.render).mockRejectedValue(originalError);

      render(<MermaidViewer diagram={diagram} />);

      await waitFor(() => {
        expect(consoleErrorSpy).toHaveBeenCalledWith("Mermaid rendering failed:", originalError);
      });

      consoleErrorSpy.mockRestore();
    });
  });

  describe("DOMPurify integration", () => {
    it("should sanitize rendered SVG output", async () => {
      const diagram = `graph TD
        A["Test Node"]`;

      // Mock mermaid to return SVG with potential XSS
      const mermaid = await import("mermaid");
      vi.mocked(mermaid.default.render).mockResolvedValue({
        svg: "<svg><script>alert(1)</script><text>Test</text></svg>",
        bindFunctions: vi.fn(),
        diagramType: "flowchart",
      });

      const { container } = render(<MermaidViewer diagram={diagram} />);

      await waitFor(() => {
        const content = container.innerHTML;
        // DOMPurify should remove the script tag
        expect(content).not.toContain("<script>");
        expect(content).not.toContain("alert(1)");
        // But keep the safe SVG elements
        expect(content).toContain("<svg");
      });
    });
  });
});
