import { describe, expect, it } from "vitest";

import { buildHighlightConfig, evaluateHighlight, getHighlightClass } from "./highlights";

describe("utils/highlights", () => {
  it("returns null when there are no levels and no fields to evaluate", () => {
    const cfg = buildHighlightConfig(
      null,
      {
        levels: { good: null, warn: null, danger: null },
      },
      "x",
    );

    expect(cfg).toBeNull();
  });

  it("buildHighlightConfig merges levels and namespaces unqualified field keys", () => {
    const cfg = buildHighlightConfig(
      { levels: { warn: "global-warn" } },
      {
        levels: { warn: "widget-warn", custom: "widget-custom" },
        cpu: { numeric: { when: "gt", value: 80, level: "warn" } },
      },
      "resources",
    );

    expect(cfg).not.toBeNull();
    expect(cfg.levels.warn).toBe("widget-warn");
    expect(cfg.levels.custom).toBe("widget-custom");

    // Field keys get normalized + namespaced.
    expect(cfg.fields.cpu).toBeTruthy();
    expect(cfg.fields["resources.cpu"]).toBeTruthy();
  });

  it("normalizes field keys by trimming and skipping blank/null entries", () => {
    const cfg = buildHighlightConfig(
      null,
      {
        levels: { good: null, warn: null, danger: null, custom: "x" },
        "  cpu  ": { numeric: { when: "gt", value: 1, level: "custom" } },
        "": { numeric: { when: "gt", value: 1, level: "danger" } },
        empty: null,
      },
      "resources",
    );

    expect(cfg.fields.cpu).toBeTruthy();
    expect(cfg.fields["resources.cpu"]).toBeTruthy();
    expect(cfg.fields.empty).toBeUndefined();
  });

  it("evaluateHighlight returns matching numeric rule with valueOnly metadata", () => {
    const cfg = buildHighlightConfig(
      null,
      {
        // valueOnly should propagate through the result so Block can apply styling.
        cpu: { valueOnly: true, numeric: { when: "gte", value: 90, level: "danger" } },
      },
      "resources",
    );

    const hit = evaluateHighlight("resources.cpu", " 90 ", cfg);
    expect(hit).toMatchObject({ level: "danger", source: "numeric", valueOnly: true });
  });

  it("evaluateHighlight stringifies booleans and applies case-sensitive string rules", () => {
    const cfg = buildHighlightConfig(null, {
      enabled: { string: { when: "equals", value: "true", caseSensitive: true, level: "good" } },
      suffix: { string: { when: "endsWith", value: "World", caseSensitive: true, level: "warn" } },
    });

    expect(evaluateHighlight("enabled", true, cfg)).toMatchObject({ level: "good", source: "string" });
    expect(evaluateHighlight("suffix", "HelloWorld", cfg)).toMatchObject({ level: "warn", source: "string" });
    expect(evaluateHighlight("suffix", "helloworld", cfg)).toBeNull();
  });

  it("evaluateHighlight supports string rules (case-insensitive includes)", () => {
    const cfg = buildHighlightConfig(null, { status: { string: { when: "includes", value: "down", level: "warn" } } });

    const hit = evaluateHighlight("status", "Service DOWN", cfg);
    expect(hit).toMatchObject({ level: "warn", source: "string" });
  });

  it("getHighlightClass returns configured class for a level", () => {
    const cfg = buildHighlightConfig({ levels: { danger: "danger-class" } }, {}, "x");
    expect(getHighlightClass("danger", cfg)).toBe("danger-class");
    expect(getHighlightClass("missing", cfg)).toBeUndefined();
  });

  it("supports localized numeric parsing and between/outside operators (with negate)", () => {
    const cfg = buildHighlightConfig(null, {
      temp: {
        numeric: [
          { when: "between", min: 1000.5, max: 1500.5, level: "warn" },
          { when: "outside", value: { min: 1234.5, max: 2234.5 }, level: "danger", negate: true },
        ],
      },
    });

    // "1.234,56" should parse as 1234.56 and hit the between rule.
    expect(evaluateHighlight("temp", "1.234,56", cfg)).toMatchObject({ level: "warn", source: "numeric" });

    // Negated outside => inside the range should match.
    expect(evaluateHighlight("temp", "2.000,00", cfg)).toMatchObject({ level: "danger", source: "numeric" });
  });

  it("supports numeric parsing for dot/comma thousands formats", () => {
    const cfg = buildHighlightConfig(null, {
      num: { numeric: { when: "eq", value: 1234.56, level: "good" } },
      grouped: { numeric: { when: "eq", value: 1234567, level: "warn" } },
    });

    expect(evaluateHighlight("num", "1,234.56", cfg)).toMatchObject({ level: "good", source: "numeric" });
    expect(evaluateHighlight("grouped", "1.234.567", cfg)).toMatchObject({ level: "warn", source: "numeric" });
  });

  it("supports regex string rules, including invalid regex patterns (ignored)", () => {
    const cfg = buildHighlightConfig(null, {
      status: {
        string: [
          { when: "regex", value: "^up$", level: "good" },
          { when: "regex", value: "(", level: "danger" }, // invalid; should be ignored
          { when: "equals", value: "DOWN", level: "warn", caseSensitive: true },
        ],
      },
    });

    expect(evaluateHighlight("status", "Up", cfg)).toMatchObject({ level: "good", source: "string" });
    expect(evaluateHighlight("status", "DOWN", cfg)).toMatchObject({ level: "warn", source: "string" });
    expect(evaluateHighlight("status", "Down", cfg)).toBeNull();
  });

  it("parses numeric strings with commas/dots/spaces and supports stringified numeric rule values", () => {
    const cfg = buildHighlightConfig(null, {
      // string numeric rule values go through toNumber()
      gt: { numeric: { when: "gt", value: "5", level: "warn" } },
      withUnitSuffix: { numeric: { when: "gt", value: 5, level: "warn" } },
      withUnitPrefix: { numeric: { when: "gt", value: 5, level: "warn" } },
      localizedUnitSuffix: { numeric: { when: "gt", value: 0.5, level: "warn" } },
      commaGrouped: { numeric: { when: "eq", value: 1234, level: "good" } },
      commaDecimal: { numeric: { when: "eq", value: 12.34, level: "good" } },
      dotDecimal: { numeric: { when: "eq", value: 12.34, level: "good" } },
      spaceGrouped: { numeric: { when: "eq", value: 1234, level: "good" } },
    });

    expect(evaluateHighlight("gt", "6", cfg)).toMatchObject({ level: "warn", source: "numeric" });
    expect(evaluateHighlight("withUnitSuffix", "5.2 ms", cfg)).toMatchObject({ level: "warn", source: "numeric" });
    expect(evaluateHighlight("withUnitPrefix", "ms 5.2", cfg)).toMatchObject({ level: "warn", source: "numeric" });
    expect(evaluateHighlight("localizedUnitSuffix", "0,71\u202Fms", cfg)).toMatchObject({
      level: "warn",
      source: "numeric",
    });
    expect(evaluateHighlight("commaGrouped", "1,234", cfg)).toMatchObject({ level: "good", source: "numeric" });
    expect(evaluateHighlight("commaDecimal", "12,34", cfg)).toMatchObject({ level: "good", source: "numeric" });
    // Include a space so Number(trimmed) fails and we exercise the dot parsing branch.
    expect(evaluateHighlight("dotDecimal", "12 .34", cfg)).toMatchObject({ level: "good", source: "numeric" });
    expect(evaluateHighlight("spaceGrouped", "1 234", cfg)).toMatchObject({ level: "good", source: "numeric" });
  });

  it("treats unparseable numeric formats as non-numeric", () => {
    const cfg = buildHighlightConfig(null, {
      num: { numeric: { when: "gt", value: 0, level: "warn" } },
    });

    // Invalid comma grouping should not be treated as numeric.
    expect(evaluateHighlight("num", "1,2,3", cfg)).toBeNull();

    // "1.2.3" is not a valid grouped or decimal number for our parser.
    expect(evaluateHighlight("num", "1.2.3", cfg)).toBeNull();

    // Multiple numbers in one string should not be treated as a single numeric value.
    expect(evaluateHighlight("num", "5/10 ms", cfg)).toBeNull();

    // JSX-ish values should not be treated as numeric.
    expect(evaluateHighlight("num", { props: { children: "x" } }, cfg)).toBeNull();
  });

  it("falls through numeric evaluation when numeric rules do not match", () => {
    const cfg = buildHighlightConfig(null, {
      status: {
        numeric: { when: "gte", value: 100, level: "danger" },
        string: { when: "includes", value: "ok", level: "good" },
      },
    });

    // Numeric rule doesn't match, string rule does.
    expect(evaluateHighlight("status", "ok", cfg)).toMatchObject({ level: "good", source: "string" });
  });

  it("stringifies numbers/bigints for string evaluation and ignores unknown numeric operators", () => {
    const cfg = buildHighlightConfig(null, {
      // unknown numeric operator should not match
      weird: { numeric: { when: "nope", value: 1, level: "warn" } },
      // bigint should stringify to match a string rule
      big: { string: { when: "equals", value: "9007199254740993", level: "good", caseSensitive: true } },
    });

    expect(evaluateHighlight("weird", "10", cfg)).toBeNull();
    expect(evaluateHighlight("big", 9007199254740993n, cfg)).toMatchObject({ level: "good", source: "string" });
  });
});
