import { describe, expect, it } from "vitest"
import type { Conditional } from "@/definitions/shared/conditions"
import { shouldRender, __conditionsTesting } from "@/engine"

const { applyLogic, readPath, lengthOf, toNumber } = __conditionsTesting

describe("shouldRender", () => {
  it("returns true when cfg is undefined", () => {
    expect(shouldRender(undefined, {})).toBe(true)
  })

  it("short-circuits on a `condition` function", () => {
    const cfg: Conditional = { condition: (item) => item.value === 42 }
    expect(shouldRender(cfg, { value: 42 })).toBe(true)
    expect(shouldRender(cfg, { value: 1 })).toBe(false)
  })

  it("gates on features", () => {
    const cfg: Conditional = { features: ["beta"] }
    expect(shouldRender(cfg, {}, { availableFeatures: ["beta"] })).toBe(true)
    expect(shouldRender(cfg, {}, { availableFeatures: [] })).toBe(false)
  })

  it("gates on permissions", () => {
    const cfg: Conditional = { permissions: ["admin"] }
    expect(
      shouldRender(cfg, {}, { availablePermissions: ["admin"] }),
    ).toBe(true)
    expect(shouldRender(cfg, {}, { availablePermissions: ["user"] })).toBe(
      false,
    )
  })

  it("AND-combines conditionParams by default", () => {
    const cfg: Conditional = {
      conditionParams: [
        { name: "status", logic: "==", value: "active" },
        { name: "score", logic: ">", value: 10 },
      ],
    }
    expect(shouldRender(cfg, { status: "active", score: 20 })).toBe(true)
    expect(shouldRender(cfg, { status: "active", score: 5 })).toBe(false)
    expect(shouldRender(cfg, { status: "draft", score: 20 })).toBe(false)
  })

  it("ORs when conditionGroupLogic is OR", () => {
    const cfg: Conditional = {
      conditionGroupLogic: "OR",
      conditionParams: [
        { name: "status", logic: "==", value: "active" },
        { name: "score", logic: ">", value: 10 },
      ],
    }
    expect(shouldRender(cfg, { status: "active", score: 5 })).toBe(true)
    expect(shouldRender(cfg, { status: "draft", score: 20 })).toBe(true)
    expect(shouldRender(cfg, { status: "draft", score: 5 })).toBe(false)
  })

  it("supports nested groups via subCondition", () => {
    const cfg: Conditional = {
      conditionGroupLogic: "AND",
      conditionParams: [
        { name: "status", logic: "==", value: "active" },
        {
          name: "_group",
          logic: "==",
          subCondition: true,
          conditionGroupLogic: "OR",
          conditionParams: [
            { name: "role", logic: "==", value: "admin" },
            { name: "tier", logic: ">=", value: 3 },
          ],
        },
      ],
    }
    expect(shouldRender(cfg, { status: "active", role: "admin", tier: 1 })).toBe(true)
    expect(shouldRender(cfg, { status: "active", role: "user", tier: 3 })).toBe(true)
    expect(shouldRender(cfg, { status: "active", role: "user", tier: 1 })).toBe(false)
    expect(shouldRender(cfg, { status: "draft", role: "admin", tier: 3 })).toBe(false)
  })

  it("supports nested-path lookups", () => {
    const cfg: Conditional = {
      conditionParams: [{ name: "user.role", logic: "==", value: "admin" }],
    }
    expect(shouldRender(cfg, { user: { role: "admin" } })).toBe(true)
    expect(shouldRender(cfg, { user: { role: "user" } })).toBe(false)
  })

  it("resolves model-typed values from the data path", () => {
    const cfg: Conditional = {
      conditionParams: [
        { name: "owner_id", logic: "==", valueType: "model", value: "current_user.id" },
      ],
    }
    expect(
      shouldRender(cfg, { owner_id: 7, current_user: { id: 7 } }),
    ).toBe(true)
    expect(
      shouldRender(cfg, { owner_id: 7, current_user: { id: 99 } }),
    ).toBe(false)
  })
})

describe("applyLogic operators", () => {
  it("handles arithmetic comparisons", () => {
    expect(applyLogic(">", 5, 3)).toBe(true)
    expect(applyLogic(">=", 5, 5)).toBe(true)
    expect(applyLogic("<", 5, 3)).toBe(false)
    expect(applyLogic("<=", 5, 5)).toBe(true)
  })

  it("handles array containment", () => {
    expect(applyLogic("in-array", "x", ["x", "y"])).toBe(true)
    expect(applyLogic("not-in-array", "x", ["y", "z"])).toBe(true)
  })

  it("handles string matchers", () => {
    expect(applyLogic("starts-with", "hello", "he")).toBe(true)
    expect(applyLogic("ends-with", "hello", "lo")).toBe(true)
    expect(applyLogic("contains", "hello world", "lo w")).toBe(true)
    expect(applyLogic("matches", "abc123", "^abc")).toBe(true)
    expect(applyLogic("matches", "abc", "[")).toBe(false) // invalid regex => false
  })

  it("handles parity + nullability", () => {
    expect(applyLogic("is-odd", 3, null)).toBe(true)
    expect(applyLogic("is-even", 4, null)).toBe(true)
    expect(applyLogic("is-truthy", "x", null)).toBe(true)
    expect(applyLogic("is-falsy", "", null)).toBe(true)
    expect(applyLogic("is-null", null, null)).toBe(true)
    expect(applyLogic("is-not-null", "x", null)).toBe(true)
  })

  it("handles length-based logic", () => {
    expect(applyLogic("g0length", "abc", null)).toBe(true)
    expect(applyLogic("zero-length", "", null)).toBe(true)
    expect(applyLogic("length-gt-value", [1, 2, 3], 2)).toBe(true)
    expect(applyLogic("length-lt-value", [1], 2)).toBe(true)
    expect(applyLogic("length-eq-value", [1, 2], 2)).toBe(true)
  })
})

describe("helpers", () => {
  it("readPath traverses dot paths", () => {
    expect(readPath({ a: { b: { c: 1 } } }, "a.b.c")).toBe(1)
    expect(readPath({ a: 1 }, "missing")).toBeUndefined()
    expect(readPath({}, "")).toBeUndefined()
  })

  it("lengthOf handles strings, arrays, and objects", () => {
    expect(lengthOf("abc")).toBe(3)
    expect(lengthOf([1, 2])).toBe(2)
    expect(lengthOf({ a: 1, b: 2 })).toBe(2)
    expect(lengthOf(null)).toBe(0)
    expect(lengthOf(undefined)).toBe(0)
    expect(lengthOf(42)).toBe(0)
  })

  it("toNumber coerces strings and ignores garbage", () => {
    expect(toNumber("12")).toBe(12)
    expect(toNumber(7)).toBe(7)
    expect(toNumber("")).toBeNaN()
    expect(toNumber("abc")).toBeNaN()
  })
})
