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

import createMockRes from "test-utils/create-mock-res";

const { httpProxy, getServiceWidget, getPrivateWidgetOptions, cache, cookieJar, logger } = vi.hoisted(() => {
  const store = new Map();
  return {
    httpProxy: vi.fn(),
    getServiceWidget: vi.fn(),
    getPrivateWidgetOptions: vi.fn(),
    cache: {
      get: vi.fn((k) => (store.has(k) ? store.get(k) : null)),
      put: vi.fn((k, v) => store.set(k, v)),
      del: vi.fn((k) => store.delete(k)),
      _reset: () => store.clear(),
    },
    cookieJar: {
      addCookieToJar: vi.fn(),
      setCookieHeader: vi.fn(),
    },
    logger: { debug: vi.fn(), error: vi.fn() },
  };
});

vi.mock("memory-cache", () => ({
  default: cache,
  ...cache,
}));
vi.mock("utils/logger", () => ({
  default: () => logger,
}));
vi.mock("utils/config/service-helpers", () => ({
  default: getServiceWidget,
}));
vi.mock("utils/config/widget-helpers", () => ({
  getPrivateWidgetOptions,
}));
vi.mock("utils/proxy/http", () => ({
  httpProxy,
}));
vi.mock("utils/proxy/cookie-jar", () => cookieJar);
vi.mock("widgets/widgets", () => ({
  default: {
    unifi: {
      api: "{url}{prefix}/api/{endpoint}",
    },
  },
}));

import unifiProxyHandler from "./proxy";

describe("widgets/unifi/proxy", () => {
  beforeEach(() => {
    vi.clearAllMocks();
    cache._reset();
  });

  it("auto-detects prefix, logs in on 401, and retries the request", async () => {
    getServiceWidget.mockResolvedValue({
      type: "unifi",
      url: "http://unifi",
      username: "u",
      password: "p",
    });

    httpProxy
      // autodetect call -> csrf header indicates udmp prefix
      .mockResolvedValueOnce([200, "text/html", Buffer.from(""), { "x-csrf-token": "csrf" }])
      // initial api call -> unauthorized
      .mockResolvedValueOnce([401, "application/json", Buffer.from("nope"), { "x-csrf-token": "csrf2" }])
      // login -> ok
      .mockResolvedValueOnce([
        200,
        "application/json",
        Buffer.from(JSON.stringify({ meta: { rc: "ok" } })),
        { "set-cookie": ["sid=1"] },
      ])
      // retry api call -> ok
      .mockResolvedValueOnce([200, "application/json", Buffer.from("data"), {}]);

    const req = { query: { group: "g", service: "svc", endpoint: "self", index: "0" } };
    const res = createMockRes();

    await unifiProxyHandler(req, res);

    expect(httpProxy).toHaveBeenCalledTimes(4);
    expect(httpProxy.mock.calls[1][0].toString()).toContain("/proxy/network/api/self");
    expect(cookieJar.addCookieToJar).toHaveBeenCalled();
    expect(cookieJar.setCookieHeader).toHaveBeenLastCalledWith(expect.any(URL), expect.any(Object), {
      overwrite: true,
    });
    expect(res.statusCode).toBe(200);
    expect(res.body).toEqual(Buffer.from("data"));
  });

  it("uses unifi_console private widget config for the info widget path", async () => {
    getPrivateWidgetOptions.mockResolvedValue({
      url: "http://console",
      username: "u",
      password: "p",
    });

    httpProxy
      .mockResolvedValueOnce([200, "text/html", Buffer.from(""), { "x-csrf-token": "csrf" }])
      .mockResolvedValueOnce([200, "application/json", Buffer.from("data"), {}]);

    const req = {
      query: {
        group: "unifi_console",
        service: "unifi_console",
        endpoint: "self",
        query: JSON.stringify({ index: 2 }),
      },
    };
    const res = createMockRes();

    await unifiProxyHandler(req, res);

    expect(getPrivateWidgetOptions).toHaveBeenCalledWith("unifi_console", 2);
    expect(httpProxy.mock.calls[1][0].toString()).toContain("/proxy/network/api/self");
    expect(res.statusCode).toBe(200);
  });

  it("uses the API key flow without attempting login", async () => {
    getServiceWidget.mockResolvedValue({
      type: "unifi",
      key: "secret",
      url: "http://unifi",
    });

    httpProxy.mockResolvedValueOnce([200, "application/json", Buffer.from("data"), {}]);

    const req = { query: { group: "g", service: "svc", endpoint: "self", index: "0" } };
    const res = createMockRes();

    await unifiProxyHandler(req, res);

    expect(httpProxy).toHaveBeenCalledTimes(1);
    expect(httpProxy.mock.calls[0][0].toString()).toContain("/proxy/network/api/self");
    expect(httpProxy.mock.calls[0][1]).toMatchObject({
      headers: {
        Accept: "application/json",
        "X-API-KEY": "secret",
      },
      method: "GET",
    });
    expect(res.statusCode).toBe(200);
  });
});
