/**
 * Unit tests for the shared i18n module.
 *
 * Covers the label dictionary (en/es), the unit display mapping,
 * the formatter helpers, and the lang-detection function. The
 * vitest env is "node" (no jsdom), so we stub `document` and
 * `navigator` directly via property assignment.
 */
import { describe, it, expect, beforeEach, afterEach } from "vitest";
import {
  detectInitialLang,
  displayUnit,
  getT,
  LABELS,
  setLangCookie,
  WL_LANG_COOKIE,
  type Lang,
} from "@/lib/water-log/i18n";

// ── Minimal DOM stub ─────────────────────────────────────────────────

type CookieStore = Record<string, string>;

function installDomStub(initialCookies: CookieStore = {}) {
  const cookies: CookieStore = { ...initialCookies };
  const document = {
    get cookie() {
      return Object.entries(cookies)
        .map(([k, v]) => `${k}=${v}`)
        .join("; ");
    },
    set cookie(value: string) {
      // Parse "name=value; ..." — keep just the first pair.
      const [pair] = value.split(";");
      const eq = pair.indexOf("=");
      if (eq < 0) return;
      const name = pair.slice(0, eq).trim();
      const val = pair.slice(eq + 1).trim();
      // Treat "name=; expires=..." as a delete.
      if (
        val === "" ||
        /expires=Thu, 01 Jan 1970/i.test(value) ||
        /max-age=0/i.test(value)
      ) {
        delete cookies[name];
      } else {
        cookies[name] = val;
      }
    },
  };
  const originalDoc = (globalThis as { document?: unknown }).document;
  const originalNav = (globalThis as { navigator?: unknown }).navigator;
  Object.defineProperty(globalThis, "document", {
    value: document,
    writable: true,
    configurable: true,
  });
  return {
    setNav(nav: { language: string } | undefined) {
      Object.defineProperty(globalThis, "navigator", {
        value: nav,
        writable: true,
        configurable: true,
      });
    },
    restore() {
      Object.defineProperty(globalThis, "document", {
        value: originalDoc,
        writable: true,
        configurable: true,
      });
      Object.defineProperty(globalThis, "navigator", {
        value: originalNav,
        writable: true,
        configurable: true,
      });
    },
  };
}

// ── Lang dictionary ────────────────────────────────────────────────────

describe("LABELS dictionary", () => {
  it("has both en and es keys", () => {
    expect(LABELS.en).toBeDefined();
    expect(LABELS.es).toBeDefined();
  });

  it("en and es share the same top-level screen keys", () => {
    const enKeys = Object.keys(LABELS.en).sort();
    const esKeys = Object.keys(LABELS.es).sort();
    expect(esKeys).toEqual(enKeys);
  });

  it("every screen's label has matching en and es inner keys", () => {
    for (const screen of Object.keys(LABELS.en) as Array<
      keyof typeof LABELS.en
    >) {
      const enScreen = LABELS.en[screen] as Record<string, unknown>;
      const esScreen = LABELS.es[screen] as Record<string, unknown>;
      const enInner = Object.keys(enScreen).sort();
      const esInner = Object.keys(esScreen).sort();
      expect(esInner, `screen "${String(screen)}"`).toEqual(enInner);
    }
  });

  it("PIN screen labels are non-empty and translated", () => {
    expect(LABELS.en.pin.title.length).toBeGreaterThan(0);
    expect(LABELS.es.pin.title.length).toBeGreaterThan(0);
    expect(LABELS.en.pin.title).not.toBe(LABELS.es.pin.title);
  });
});

// ── getT ──────────────────────────────────────────────────────────────

describe("getT()", () => {
  it("returns the en dict for 'en'", () => {
    expect(getT("en")).toBe(LABELS.en);
  });

  it("returns the es dict for 'es'", () => {
    expect(getT("es")).toBe(LABELS.es);
  });
});

// ── displayUnit ──────────────────────────────────────────────────────

describe("displayUnit()", () => {
  it("translates 'holes' to 'hoyos' in Spanish", () => {
    expect(displayUnit("holes", "es")).toBe("hoyos");
  });

  it("leaves 'holes' as 'holes' in English", () => {
    expect(displayUnit("holes", "en")).toBe("holes");
  });

  it("leaves CFS, GPM, gal, ac-in, ac-ft unchanged in both langs", () => {
    const passThrough: Lang[] = ["en", "es"];
    for (const lang of passThrough) {
      expect(displayUnit("CFS", lang)).toBe("CFS");
      expect(displayUnit("GPM", lang)).toBe("GPM");
      expect(displayUnit("gal", lang)).toBe("gal");
      expect(displayUnit("ac-in", lang)).toBe("ac-in");
      expect(displayUnit("ac-ft", lang)).toBe("ac-ft");
    }
  });

  it("passes through unknown units unchanged", () => {
    expect(displayUnit("custom-unit", "en")).toBe("custom-unit");
    expect(displayUnit("custom-unit", "es")).toBe("custom-unit");
  });
});

// ── detectInitialLang ───────────────────────────────────────────────

describe("detectInitialLang()", () => {
  let stub: ReturnType<typeof installDomStub>;

  beforeEach(() => {
    stub = installDomStub();
  });

  afterEach(() => {
    stub.restore();
  });

  it("returns 'es' when document is undefined (SSR)", () => {
    Object.defineProperty(globalThis, "document", {
      value: undefined,
      writable: true,
      configurable: true,
    });
    expect(detectInitialLang()).toBe("es");
  });

  it("returns 'en' when wl_lang cookie is 'en'", () => {
    document.cookie = `${WL_LANG_COOKIE}=en; path=/`;
    expect(detectInitialLang()).toBe("en");
  });

  it("returns 'es' when wl_lang cookie is 'es'", () => {
    document.cookie = `${WL_LANG_COOKIE}=es; path=/`;
    expect(detectInitialLang()).toBe("es");
  });

  it("ignores a malformed wl_lang cookie and falls back to navigator", () => {
    document.cookie = `${WL_LANG_COOKIE}=fr; path=/`;
    stub.setNav({ language: "en-US" });
    expect(detectInitialLang()).toBe("en");
  });

  it("falls back to 'es' when no cookie and navigator is non-English", () => {
    stub.setNav({ language: "fr-FR" });
    expect(detectInitialLang()).toBe("es");
  });

  it("ignores a wl_lang value that contains a JS payload (injection guard)", () => {
    // Important: the regex in `detectInitialLang` only matches the
    // exact 2-letter codes. Anything else must fall through to the
    // navigator/default. This locks in that we never feed untrusted
    // cookie contents into React state.
    document.cookie = `${WL_LANG_COOKIE}=<script>alert(1)</script>; path=/`;
    stub.setNav({ language: "en-US" });
    expect(detectInitialLang()).toBe("en");
  });
});

// ── setLangCookie ───────────────────────────────────────────────────

describe("setLangCookie()", () => {
  let stub: ReturnType<typeof installDomStub>;

  beforeEach(() => {
    stub = installDomStub();
  });

  afterEach(() => {
    stub.restore();
  });

  it("writes the wl_lang cookie with the chosen lang", () => {
    setLangCookie("en");
    expect(document.cookie).toMatch(/wl_lang=en/);
  });

  it("overwrites a previous value", () => {
    setLangCookie("en");
    setLangCookie("es");
    expect(document.cookie).toMatch(/wl_lang=es/);
  });

  it("is a no-op when document is undefined (SSR)", () => {
    Object.defineProperty(globalThis, "document", {
      value: undefined,
      writable: true,
      configurable: true,
    });
    expect(() => setLangCookie("en")).not.toThrow();
  });
});

// ── Mobile label interpolation ─────────────────────────────────────

describe("Mobile label helpers (interpolation)", () => {
  it("headgates.signedInAs interpolates the name", () => {
    expect(LABELS.en.headgates.signedInAs("Tyler")).toBe(
      "Signed in as Tyler",
    );
    expect(LABELS.es.headgates.signedInAs("Tyler")).toBe(
      "Sesión iniciada como Tyler",
    );
  });

  it("log.normalRange interpolates the bounds + unit", () => {
    expect(LABELS.en.log.normalRange(2, 8, "CFS")).toBe(
      "Normal range: 2 – 8 CFS",
    );
    expect(LABELS.es.log.normalRange(2, 8, "CFS")).toBe(
      "Rango normal: 2 – 8 CFS",
    );
  });

  it("log.alertAbove / alertBelow interpolate single bound + unit", () => {
    expect(LABELS.en.log.alertAbove(10, "CFS")).toBe("Alert above 10 CFS");
    expect(LABELS.es.log.alertAbove(10, "CFS")).toBe(
      "Alerta arriba de 10 CFS",
    );
    expect(LABELS.en.log.alertBelow(1, "CFS")).toBe("Alert below 1 CFS");
    expect(LABELS.es.log.alertBelow(1, "CFS")).toBe("Alerta abajo de 1 CFS");
  });

  it("success.loggedBy interpolates the irrigator name", () => {
    expect(LABELS.en.success.loggedBy("Maria")).toBe("Logged by Maria");
    expect(LABELS.es.success.loggedBy("Maria")).toBe("Registrado por Maria");
  });

  it("format.neverLogged / justNow are non-empty and translated", () => {
    expect(LABELS.en.format.neverLogged.length).toBeGreaterThan(0);
    expect(LABELS.es.format.neverLogged.length).toBeGreaterThan(0);
    expect(LABELS.en.format.justNow).not.toBe(LABELS.es.format.justNow);
  });

  it("format.minutesAgo / hoursAgo interpolate the count", () => {
    expect(LABELS.en.format.minutesAgo(5)).toBe("5m ago");
    expect(LABELS.es.format.minutesAgo(5)).toBe("hace 5m");
    expect(LABELS.en.format.hoursAgo(3)).toBe("3h ago");
    expect(LABELS.es.format.hoursAgo(3)).toBe("hace 3h");
  });
});

// ── Unit catalog (regression guard for "holes") ─────────────────────

describe("Unit catalog includes 'holes'", () => {
  it("'holes' is in the en catalog and translates to 'hoyos' in es", () => {
    expect(LABELS.en.units.holes).toBe("holes");
    expect(LABELS.es.units.holes).toBe("hoyos");
  });

  it("all six MOBILE_UNITS keys exist in the units dict for both langs", () => {
    const expected = ["CFS", "GPM", "gal", "ac-in", "ac-ft", "holes"];
    for (const unit of expected) {
      expect(LABELS.en.units).toHaveProperty(unit);
      expect(LABELS.es.units).toHaveProperty(unit);
    }
  });
});