import { describe, expect, it, beforeAll, afterAll, vi } from "vitest";
import IORedis from "ioredis";
import BRAND from "../../../brand.config.json" with { type: "json" };
import { pushOpsSummaryToEoc } from "../src/processors/eoc-push.js";

const redis = new IORedis(process.env.REDIS_URL ?? "redis://localhost:6379", {
  maxRetriesPerRequest: null,
});

describe("pushOpsSummaryToEoc", () => {
  beforeAll(async () => {
    await redis.set(
      "ops:summary:cache",
      JSON.stringify({
        app: { app: "cibello", generated_at: new Date().toISOString() },
        as_of: new Date().toISOString(),
      }),
      "EX",
      60,
    );
  });

  afterAll(async () => {
    await redis.del("ops:summary:cache");
    await redis.quit();
  });

  it("maps feedback.created_at to skapad in the POST body", async () => {
    process.env.EOC_URL = "https://eoc.example.com";
    process.env.EOC_PUSH_TOKEN = "tok";
    const ts = "2026-08-11T10:00:00.000Z";
    await redis.set(
      "ops:summary:cache",
      JSON.stringify({
        as_of: ts,
        app: { app: "cibello" },
        feedback: {
          oppna: 1,
          senaste: [{ typ: "bug", rubrik: "exempel", status: "oppen", created_at: ts }],
        },
      }),
      "EX",
      60,
    );

    const originalFetch = globalThis.fetch;
    const fetchMock = vi.fn().mockResolvedValue(new Response(null, { status: 204 }));
    globalThis.fetch = fetchMock as unknown as typeof fetch;

    try {
      const result = await pushOpsSummaryToEoc(redis);
      expect(result.ok).toBe(true);
      expect(fetchMock).toHaveBeenCalledTimes(1);

      const init = fetchMock.mock.calls[0][1] as RequestInit;
      const body = JSON.parse(init.body as string);
      expect(body.feedback.senaste[0].skapad).toBe(ts);
      expect(body.feedback.senaste[0]).not.toHaveProperty("created_at");
      expect(body.as_of).toBe(ts);
    } finally {
      globalThis.fetch = originalFetch;
    }
  });

  it("returns disabled result when EOC_URL is missing", async () => {
    delete process.env.EOC_URL;
    process.env.EOC_PUSH_TOKEN = "tok";
    const result = await pushOpsSummaryToEoc(redis);
    expect(result.ok).toBe(false);
    expect(result.error).toMatch(/EOC_URL or EOC_PUSH_TOKEN not configured/);
  });

  it("returns disabled result when EOC_PUSH_TOKEN is missing", async () => {
    process.env.EOC_URL = "https://eoc.example.com";
    delete process.env.EOC_PUSH_TOKEN;
    const result = await pushOpsSummaryToEoc(redis);
    expect(result.ok).toBe(false);
    expect(result.error).toMatch(/EOC_URL or EOC_PUSH_TOKEN not configured/);
  });

  it("returns cache-miss when cache is empty", async () => {
    process.env.EOC_URL = "https://eoc.example.com";
    process.env.EOC_PUSH_TOKEN = "tok";
    await redis.del("ops:summary:cache");
    const result = await pushOpsSummaryToEoc(redis);
    expect(result.ok).toBe(false);
    expect(result.error).toMatch(/cache miss/);
  });

  it("transforms wall titles and tones for EOC", async () => {
    process.env.EOC_URL = "https://eoc.example.com";
    process.env.EOC_PUSH_TOKEN = "tok";
    await redis.set(
      "ops:summary:cache",
      JSON.stringify({
        as_of: new Date().toISOString(),
        app: { app: "cibello" },
        jobb: { workers_ok: false },
        sakerhet: { allergen_invariant_brott: 1 },
        wall: {
          boards: [
            {
              title: "{brand} Overview",
              tiles: [
                { label: "MRR", value: "0 kr", tone: "ok" },
                { label: "Workers", value: "NERE", tone: "warn" },
                { label: "Allergen-brott", value: "1", tone: "warn" },
              ],
            },
          ],
        },
      }),
      "EX",
      60,
    );

    const originalFetch = globalThis.fetch;
    const fetchMock = vi.fn().mockResolvedValue(new Response(null, { status: 204 }));
    globalThis.fetch = fetchMock as unknown as typeof fetch;

    try {
      const result = await pushOpsSummaryToEoc(redis);
      expect(result.ok).toBe(true);

      const init = fetchMock.mock.calls[0][1] as RequestInit;
      const body = JSON.parse(init.body as string);
      const board = body.wall.boards[0];
      expect(board.title).toBe(`${BRAND.name} Overview`);
      expect(board.title).not.toMatch(/\{brand\}/);

      const tones = board.tiles.map((t: { tone?: string }) => t.tone);
      for (const tone of tones) {
        expect(["good", "warn", "crit", ""]).toContain(tone);
      }
      expect(board.tiles.find((t: { label: string }) => t.label === "MRR")?.tone).toBe("good");
      expect(board.tiles.find((t: { label: string }) => t.label === "Workers")?.tone).toBe("crit");
      expect(board.tiles.find((t: { label: string }) => t.label === "Allergen-brott")?.tone).toBe(
        "crit",
      );
    } finally {
      globalThis.fetch = originalFetch;
    }
  });

  it("does not throw when payload mapping fails", async () => {
    process.env.EOC_URL = "https://eoc.example.com";
    process.env.EOC_PUSH_TOKEN = "tok";
    await redis.set(
      "ops:summary:cache",
      JSON.stringify({
        as_of: new Date().toISOString(),
        app: { app: "cibello" },
        wall: { boards: [{ title: 123, tiles: "nej" }] },
      }),
      "EX",
      60,
    );

    await expect(pushOpsSummaryToEoc(redis)).resolves.toEqual({
      ok: false,
      status: null,
      error: expect.stringMatching(/payload mapping failed/),
    });
  });
});
