import { describe, expect, it } from "vitest";
import { validateJsonSchemaValue } from "./schema-validator.js";

describe("schema validator", () => {
  it("includes allowed values in enum validation errors", () => {
    const res = validateJsonSchemaValue({
      cacheKey: "schema-validator.test.enum",
      schema: {
        type: "object",
        properties: {
          fileFormat: {
            type: "string",
            enum: ["markdown", "html", "json"],
          },
        },
        required: ["fileFormat"],
      },
      value: { fileFormat: "txt" },
    });

    expect(res.ok).toBe(false);
    if (!res.ok) {
      const issue = res.errors.find((entry) => entry.path === "fileFormat");
      expect(issue?.message).toContain("(allowed:");
      expect(issue?.allowedValues).toEqual(["markdown", "html", "json"]);
      expect(issue?.allowedValuesHiddenCount).toBe(0);
    }
  });

  it("includes allowed value in const validation errors", () => {
    const res = validateJsonSchemaValue({
      cacheKey: "schema-validator.test.const",
      schema: {
        type: "object",
        properties: {
          mode: {
            const: "strict",
          },
        },
        required: ["mode"],
      },
      value: { mode: "relaxed" },
    });

    expect(res.ok).toBe(false);
    if (!res.ok) {
      const issue = res.errors.find((entry) => entry.path === "mode");
      expect(issue?.message).toContain("(allowed:");
      expect(issue?.allowedValues).toEqual(["strict"]);
      expect(issue?.allowedValuesHiddenCount).toBe(0);
    }
  });

  it("truncates long allowed-value hints", () => {
    const values = [
      "v1",
      "v2",
      "v3",
      "v4",
      "v5",
      "v6",
      "v7",
      "v8",
      "v9",
      "v10",
      "v11",
      "v12",
      "v13",
    ];
    const res = validateJsonSchemaValue({
      cacheKey: "schema-validator.test.enum.truncate",
      schema: {
        type: "object",
        properties: {
          mode: {
            type: "string",
            enum: values,
          },
        },
        required: ["mode"],
      },
      value: { mode: "not-listed" },
    });

    expect(res.ok).toBe(false);
    if (!res.ok) {
      const issue = res.errors.find((entry) => entry.path === "mode");
      expect(issue?.message).toContain("(allowed:");
      expect(issue?.message).toContain("... (+1 more)");
      expect(issue?.allowedValues).toEqual([
        "v1",
        "v2",
        "v3",
        "v4",
        "v5",
        "v6",
        "v7",
        "v8",
        "v9",
        "v10",
        "v11",
        "v12",
      ]);
      expect(issue?.allowedValuesHiddenCount).toBe(1);
    }
  });

  it("appends missing required property to the structured path", () => {
    const res = validateJsonSchemaValue({
      cacheKey: "schema-validator.test.required.path",
      schema: {
        type: "object",
        properties: {
          settings: {
            type: "object",
            properties: {
              mode: { type: "string" },
            },
            required: ["mode"],
          },
        },
        required: ["settings"],
      },
      value: { settings: {} },
    });

    expect(res.ok).toBe(false);
    if (!res.ok) {
      const issue = res.errors.find((entry) => entry.path === "settings.mode");
      expect(issue).toBeDefined();
      expect(issue?.allowedValues).toBeUndefined();
    }
  });

  it("appends missing dependency property to the structured path", () => {
    const res = validateJsonSchemaValue({
      cacheKey: "schema-validator.test.dependencies.path",
      schema: {
        type: "object",
        properties: {
          settings: {
            type: "object",
            dependencies: {
              mode: ["format"],
            },
          },
        },
      },
      value: { settings: { mode: "strict" } },
    });

    expect(res.ok).toBe(false);
    if (!res.ok) {
      const issue = res.errors.find((entry) => entry.path === "settings.format");
      expect(issue).toBeDefined();
      expect(issue?.allowedValues).toBeUndefined();
    }
  });

  it("truncates oversized allowed value entries", () => {
    const oversizedAllowed = "a".repeat(300);
    const res = validateJsonSchemaValue({
      cacheKey: "schema-validator.test.enum.long-value",
      schema: {
        type: "object",
        properties: {
          mode: {
            type: "string",
            enum: [oversizedAllowed],
          },
        },
        required: ["mode"],
      },
      value: { mode: "not-listed" },
    });

    expect(res.ok).toBe(false);
    if (!res.ok) {
      const issue = res.errors.find((entry) => entry.path === "mode");
      expect(issue).toBeDefined();
      expect(issue?.message).toContain("(allowed:");
      expect(issue?.message).toContain("... (+");
    }
  });

  it("sanitizes terminal text while preserving structured fields", () => {
    const maliciousProperty = "evil\nkey\t\x1b[31mred\x1b[0m";
    const res = validateJsonSchemaValue({
      cacheKey: "schema-validator.test.terminal-sanitize",
      schema: {
        type: "object",
        properties: {},
        required: [maliciousProperty],
      },
      value: {},
    });

    expect(res.ok).toBe(false);
    if (!res.ok) {
      const issue = res.errors[0];
      expect(issue).toBeDefined();
      expect(issue?.path).toContain("\n");
      expect(issue?.message).toContain("\n");
      expect(issue?.text).toContain("\\n");
      expect(issue?.text).toContain("\\t");
      expect(issue?.text).not.toContain("\n");
      expect(issue?.text).not.toContain("\t");
      expect(issue?.text).not.toContain("\x1b");
    }
  });
});
