import { describe, it, expect } from "vitest";
import {
  sanitizeCode,
  CodeSanitizationError,
  DEFAULT_RULES,
} from "../CodeSanitizer";

describe("CodeSanitizationError", () => {
  it("tem name e rule corretos", () => {
    const err = new CodeSanitizationError("mensagem de teste", "alguma_regra");
    expect(err).toBeInstanceOf(Error);
    expect(err.name).toBe("CodeSanitizationError");
    expect(err.rule).toBe("alguma_regra");
    expect(err.message).toBe("mensagem de teste");
  });
});

describe("sanitizeCode — verificação de tipo", () => {
  it("lança type_check para entrada não-string", () => {
    expect(() => sanitizeCode(42)).toThrow(CodeSanitizationError);
    expect(() => sanitizeCode(42)).toThrow(
      expect.objectContaining({ rule: "type_check" }),
    );
  });

  it("lança type_check para entrada null", () => {
    expect(() => sanitizeCode(null)).toThrow(
      expect.objectContaining({ rule: "type_check" }),
    );
  });
});

describe("sanitizeCode — regra max_length", () => {
  it("lança max_length quando o código excede 50 000 caracteres", () => {
    const codigoLongo = "a".repeat(50_001);
    expect(() => sanitizeCode(codigoLongo)).toThrow(
      expect.objectContaining({ rule: "max_length" }),
    );
  });

  it("não lança quando o código tem exatamente 50 000 caracteres", () => {
    const codigo = "a".repeat(50_000);
    expect(() => sanitizeCode(codigo)).not.toThrow();
  });
});

describe("sanitizeCode — regra infinite_while", () => {
  it("lança para while(true) sem break ou return", () => {
    const codigo = "while(true) { doSomething(); }";
    expect(() => sanitizeCode(codigo)).toThrow(
      expect.objectContaining({ rule: "infinite_while" }),
    );
  });

  it("lança para while(!0) sem break ou return", () => {
    const codigo = "while(!0) { doSomething(); }";
    expect(() => sanitizeCode(codigo)).toThrow(
      expect.objectContaining({ rule: "infinite_while" }),
    );
  });

  it("lança para while(1) sem break ou return", () => {
    const codigo = "while(1) { doSomething(); }";
    expect(() => sanitizeCode(codigo)).toThrow(
      expect.objectContaining({ rule: "infinite_while" }),
    );
  });

  it("não lança para while(true) com break", () => {
    const codigo = "while(true) { if (x > 0) break; }";
    expect(() => sanitizeCode(codigo)).not.toThrow();
  });

  it("não lança para while(true) com return", () => {
    const codigo = "function f() { while(true) { return 1; } }";
    expect(() => sanitizeCode(codigo)).not.toThrow();
  });

  it("não lança para while(condição) sem break", () => {
    const codigo = "while(i < 10) { i++; }";
    expect(() => sanitizeCode(codigo)).not.toThrow();
  });
});

describe("sanitizeCode — regra infinite_for", () => {
  it("lança para for(;;) sem break ou return", () => {
    const codigo = "for(;;) { doSomething(); }";
    expect(() => sanitizeCode(codigo)).toThrow(
      expect.objectContaining({ rule: "infinite_for" }),
    );
  });

  it("não lança para for(;;) com break", () => {
    const codigo = "for(;;) { if (done) break; }";
    expect(() => sanitizeCode(codigo)).not.toThrow();
  });

  it("não lança para for(;;) com return", () => {
    const codigo = "function f() { for(;;) { return; } }";
    expect(() => sanitizeCode(codigo)).not.toThrow();
  });

  it("não lança para um for loop normal", () => {
    const codigo = "for(let i = 0; i < 10; i++) { doSomething(); }";
    expect(() => sanitizeCode(codigo)).not.toThrow();
  });
});

describe("sanitizeCode — regra excessive_nesting", () => {
  it("lança quando a profundidade de chavetas excede 15", () => {
    const codigo = "{".repeat(16) + "}".repeat(16);
    expect(() => sanitizeCode(codigo)).toThrow(
      expect.objectContaining({ rule: "excessive_nesting" }),
    );
  });

  it("não lança com exatamente 15 níveis de profundidade", () => {
    const codigo = "{".repeat(15) + "}".repeat(15);
    expect(() => sanitizeCode(codigo)).not.toThrow();
  });
});

describe("sanitizeCode — código válido", () => {
  it("não lança para uma função normal", () => {
    const codigo = `
      function fatorial(n) {
        if (n <= 1) return 1;
        return n * fatorial(n - 1);
      }
      fatorial(5);
    `;
    expect(() => sanitizeCode(codigo)).not.toThrow();
  });
});

describe("sanitizeCode — regras extra", () => {
  it("aplica regras extra depois das padrão", () => {
    const regraPersonalizada = {
      id: "sem_alert",
      check: (codigo) => /\balert\s*\(/.test(codigo),
      message: "alert() não é permitido.",
    };
    expect(() => sanitizeCode("alert('oi');", [regraPersonalizada])).toThrow(
      expect.objectContaining({ rule: "sem_alert" }),
    );
  });

  it("não aciona a regra extra quando a condição não se verifica", () => {
    const regraPersonalizada = {
      id: "sem_alert",
      check: (codigo) => /\balert\s*\(/.test(codigo),
      message: "alert() não é permitido.",
    };
    expect(() =>
      sanitizeCode("console.log('oi');", [regraPersonalizada]),
    ).not.toThrow();
  });
});

describe("DEFAULT_RULES", () => {
  it("exporta um array de regras com id, check e message", () => {
    expect(Array.isArray(DEFAULT_RULES)).toBe(true);
    for (const regra of DEFAULT_RULES) {
      expect(typeof regra.id).toBe("string");
      expect(typeof regra.check).toBe("function");
      expect(typeof regra.message).toBe("string");
    }
  });
});
