import { describe, expect, it } from "vitest";
import {
  extractAssistantTextContent,
  extractObjective5Metrics,
  extractModelOptions,
  extractSvgMarkup,
  getOllamaChatEndpointCandidates,
  getModelListEndpointCandidates,
  isLocalEndpoint,
  normalizeOllamaChatEndpoint,
  normalizeUpstreamChatEndpoint,
  sanitizeSvgDocument,
} from "~/lib/lab2-chat";

describe("normalizeUpstreamChatEndpoint", () => {
  it("appends the chat completions path to a base api endpoint", () => {
    expect(normalizeUpstreamChatEndpoint("https://ai.zuccaro.me/api")).toBe(
      "https://ai.zuccaro.me/api/v1/chat/completions",
    );
  });

  it("preserves endpoints that already include the full chat completions path", () => {
    expect(
      normalizeUpstreamChatEndpoint(
        "https://ai.zuccaro.me/api/v1/chat/completions",
      ),
    ).toBe("https://ai.zuccaro.me/api/v1/chat/completions");
  });
});

describe("extractSvgMarkup", () => {
  it("extracts fenced svg output", () => {
    const markup = extractSvgMarkup(
      "```svg\n<svg viewBox=\"0 0 10 10\"></svg>\n```",
    );

    expect(markup).toBe("<svg viewBox=\"0 0 10 10\"></svg>");
  });
});

describe("normalizeOllamaChatEndpoint", () => {
  it("prefers the native ollama chat path for a bare ollama endpoint", () => {
    expect(normalizeOllamaChatEndpoint("http://127.0.0.1:11434")).toBe(
      "http://127.0.0.1:11434/api/chat",
    );
  });
});

describe("getModelListEndpointCandidates", () => {
  it("tries native ollama tags before openai model listings for bare local endpoints", () => {
    expect(getModelListEndpointCandidates("http://127.0.0.1:1234")).toEqual([
      "http://127.0.0.1:1234/api/tags",
      "http://127.0.0.1:1234/v1/models",
    ]);
  });
});

describe("getOllamaChatEndpointCandidates", () => {
  it("tries native and proxied ollama chat routes", () => {
    expect(getOllamaChatEndpointCandidates("http://127.0.0.1:8080")).toEqual([
      "http://127.0.0.1:8080/api/chat",
      "http://127.0.0.1:8080/ollama/api/chat",
    ]);
  });
});

describe("isLocalEndpoint", () => {
  it("detects localhost endpoints", () => {
    expect(isLocalEndpoint("http://127.0.0.1:1234")).toBe(true);
    expect(isLocalEndpoint("https://ai.zuccaro.me/api")).toBe(false);
  });
});

describe("extractAssistantTextContent", () => {
  it("reads ollama-style chat responses", () => {
    expect(
      extractAssistantTextContent({
        message: {
          content: "hello from gemma",
          role: "assistant",
        },
      }),
    ).toBe("hello from gemma");
  });

  it("falls back to reasoning content for local reasoning models", () => {
    expect(
      extractAssistantTextContent({
        choices: [
          {
            message: {
              content: "",
              reasoning_content: "Thinking only output",
            },
          },
        ],
      }),
    ).toBe("Thinking only output");
  });
});

describe("extractObjective5Metrics", () => {
  it("computes tokens per second for ollama-style responses", () => {
    expect(
      extractObjective5Metrics({
        eval_count: 451,
        eval_duration: 31_332_836_667,
        prompt_eval_count: 16,
        prompt_eval_duration: 96_516_186,
        total_duration: 33_471_213_310,
      }),
    ).toEqual({
      completionTokens: 451,
      evalDurationMs: 31332.8,
      promptEvalDurationMs: 96.5,
      promptTokens: 16,
      tokensPerSecond: 14.4,
      totalDurationMs: 33471.2,
    });
  });
});

describe("extractModelOptions", () => {
  it("maps model list payloads into dropdown options", () => {
    expect(
      extractModelOptions({
        data: [
          { id: "qwen3.5-9b-mlx", object: "model" },
          { id: "batiai/gemma4-e2b:q4", name: "Gemma 4 E2B Q4" },
        ],
      }),
    ).toEqual([
      { label: "qwen3.5-9b-mlx", value: "qwen3.5-9b-mlx" },
      { label: "Gemma 4 E2B Q4", value: "batiai/gemma4-e2b:q4" },
    ]);
  });

  it("maps ollama tag payloads into dropdown options", () => {
    expect(
      extractModelOptions({
        models: [
          { model: "batiai/gemma4-e2b:q4" },
          { name: "batiai/gemma4-e2b:q6" },
        ],
      }),
    ).toEqual([
      {
        label: "Gemma 4 E2B Q4",
        value: "batiai/gemma4-e2b:q4",
      },
      {
        label: "Gemma 4 E2B Q6",
        value: "batiai/gemma4-e2b:q6",
      },
    ]);
  });
});

describe("sanitizeSvgDocument", () => {
  it("accepts a simple safe svg", () => {
    const result = sanitizeSvgDocument(
      "<svg viewBox=\"0 0 10 10\"><rect x=\"1\" y=\"1\" width=\"8\" height=\"8\" fill=\"#0f4f76\" /></svg>",
    );

    expect(result.ok).toBe(true);
    if (!result.ok) {
      throw new Error(result.error);
    }

    expect(result.svg).toContain("<svg");
    expect(result.svg).toContain("xmlns=\"http://www.w3.org/2000/svg\"");
  });

  it("accepts an explicit safe xmlns on the root svg element", () => {
    const result = sanitizeSvgDocument(
      "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"><rect width=\"512\" height=\"512\" fill=\"#87CEEB\" /></svg>",
    );

    expect(result.ok).toBe(true);
    if (!result.ok) {
      throw new Error(result.error);
    }

    expect(result.svg).toContain("xmlns=\"http://www.w3.org/2000/svg\"");
  });

  it("rejects malicious event handlers", () => {
    const result = sanitizeSvgDocument(
      "<svg viewBox=\"0 0 10 10\"><rect x=\"1\" y=\"1\" width=\"8\" height=\"8\" onload=\"alert(1)\" /></svg>",
    );

    expect(result.ok).toBe(false);
    if (result.ok) {
      throw new Error("Expected unsafe SVG to fail sanitization.");
    }

    expect(result.error).toMatch(/blocked attribute|blocked event/i);
  });

  it("rejects foreignObject", () => {
    const result = sanitizeSvgDocument(
      "<svg viewBox=\"0 0 10 10\"><foreignObject width=\"10\" height=\"10\"></foreignObject></svg>",
    );

    expect(result.ok).toBe(false);
    if (result.ok) {
      throw new Error("Expected blocked SVG element to fail sanitization.");
    }

    expect(result.error).toMatch(/blocked element/i);
  });

  it("rejects malformed xml", () => {
    const result = sanitizeSvgDocument("<svg><g></svg>");

    expect(result.ok).toBe(false);
    if (result.ok) {
      throw new Error("Expected malformed SVG to fail sanitization.");
    }

    expect(result.error).toMatch(/malformed/i);
  });
});
