import { fetchMobileConfig } from "../../src/services/mobileConfigApi";

function ok(body: unknown): Response {
  return {
    ok: true,
    status: 200,
    json: async () => body,
  } as unknown as Response;
}

function bad(status: number, body: unknown = {}): Response {
  return {
    ok: false,
    status,
    json: async () => body,
  } as unknown as Response;
}

function badJson(status = 200): Response {
  return {
    ok: status >= 200 && status < 300,
    status,
    json: async () => {
      throw new Error("parse");
    },
  } as unknown as Response;
}

const config = {
  app_name: "Custom",
  primary_color: "#f00",
  logo_url: "https://a.b/logo.png",
  tenant_id: 42,
};

const fallback = {
  app_name: "MissioFlow Mobile",
  primary_color: "#1E56A8",
  logo_url: null,
  tenant_id: null,
  support_email: null,
  support_phone: null,
};

describe("mobileConfigApi.fetchMobileConfig", () => {
  beforeEach(() => {
    (globalThis as unknown as { fetch: jest.Mock }).fetch = jest.fn();
  });

  it("retourne la config quand le premier endpoint reussit", async () => {
    (globalThis.fetch as jest.Mock).mockResolvedValueOnce(
      ok({ success: true, data: config })
    );
    expect(await fetchMobileConfig("https://api.test/api")).toEqual(config);
    expect(globalThis.fetch).toHaveBeenCalledWith(
      "https://api.test/api/mobile-config",
      expect.any(Object)
    );
  });

  it("utilise la variante -config pour les baseUrl en /api/mobile", async () => {
    (globalThis.fetch as jest.Mock).mockResolvedValueOnce(ok({ success: true, data: config }));
    await fetchMobileConfig("https://api.test/api/mobile");
    expect(globalThis.fetch).toHaveBeenCalledWith(
      "https://api.test/api/mobile-config",
      expect.any(Object)
    );
  });

  it("essaie les 2 endpoints dans l'ordre", async () => {
    (globalThis.fetch as jest.Mock)
      .mockResolvedValueOnce(bad(404))
      .mockResolvedValueOnce(ok({ success: true, data: config }));
    await fetchMobileConfig("https://api.test/api");
    expect((globalThis.fetch as jest.Mock).mock.calls[0][0]).toBe("https://api.test/api/mobile-config");
    expect((globalThis.fetch as jest.Mock).mock.calls[1][0]).toBe("https://api.test/api/mobile-config.php");
  });

  it("retourne le fallback quand serveur joignable mais aucun endpoint valide", async () => {
    (globalThis.fetch as jest.Mock)
      .mockResolvedValueOnce(bad(404, { message: "missing" }))
      .mockResolvedValueOnce(bad(404));
    const res = await fetchMobileConfig("https://api.test/api");
    expect(res).toEqual(fallback);
  });

  it("saute les fetch qui throw (continue)", async () => {
    (globalThis.fetch as jest.Mock)
      .mockRejectedValueOnce(new Error("net"))
      .mockResolvedValueOnce(ok({ success: true, data: config }));
    expect(await fetchMobileConfig("https://api.test/api")).toEqual(config);
  });

  it("saute les reponses non-JSON et continue", async () => {
    (globalThis.fetch as jest.Mock)
      .mockResolvedValueOnce(badJson(200))
      .mockResolvedValueOnce(ok({ success: true, data: config }));
    expect(await fetchMobileConfig("https://api.test/api")).toEqual(config);
  });

  it("utilise fallback via isServerReachable quand tous les endpoints throw mais baseUrl OK", async () => {
    (globalThis.fetch as jest.Mock)
      .mockRejectedValueOnce(new Error("net"))
      .mockRejectedValueOnce(new Error("net"))
      .mockResolvedValueOnce(bad(404));
    expect(await fetchMobileConfig("https://api.test/api")).toEqual(fallback);
  });

  it("leve Instance inaccessible quand baseUrl aussi unreachable", async () => {
    (globalThis.fetch as jest.Mock)
      .mockRejectedValueOnce(new Error("net"))
      .mockRejectedValueOnce(new Error("net"))
      .mockRejectedValueOnce(new Error("net"));
    await expect(fetchMobileConfig("https://api.test/api")).rejects.toThrow(
      "Instance inaccessible ou invalide"
    );
  });

  it("leve le message API si retenu", async () => {
    (globalThis.fetch as jest.Mock)
      .mockRejectedValueOnce(new Error("net"))
      .mockRejectedValueOnce(new Error("net"))
      .mockRejectedValueOnce(new Error("net"));
    // Rien ne met lastApiMessage (succes=false sans passage sur response.json hors endpoint) : fallback message
    await expect(fetchMobileConfig("https://api.test/api")).rejects.toThrow(/Instance inaccessible/);
  });

  it("remonte lastApiMessage quand defini via payload.message + 500 final", async () => {
    (globalThis.fetch as jest.Mock)
      .mockResolvedValueOnce(bad(400, { success: false, message: "API erreur" }))
      .mockResolvedValueOnce(bad(500, { success: false }));
    // serveur joignable: return fallback, pas d'erreur (serverWasReachable=true)
    const res = await fetchMobileConfig("https://api.test/api");
    expect(res).toEqual(fallback);
  });

  it("strip les / finaux du baseUrl", async () => {
    (globalThis.fetch as jest.Mock).mockResolvedValueOnce(ok({ success: true, data: config }));
    await fetchMobileConfig("https://api.test/api///");
    expect((globalThis.fetch as jest.Mock).mock.calls[0][0]).toBe("https://api.test/api/mobile-config");
  });

  it("isServerReachable renvoie false si status >= 500", async () => {
    (globalThis.fetch as jest.Mock)
      .mockRejectedValueOnce(new Error("net"))
      .mockRejectedValueOnce(new Error("net"))
      .mockResolvedValueOnce(bad(503));
    await expect(fetchMobileConfig("https://api.test/api")).rejects.toThrow(
      "Instance inaccessible ou invalide"
    );
  });

  it("remonte lastApiMessage quand tous endpoints fail avec payload.message et baseUrl unreachable", async () => {
    (globalThis.fetch as jest.Mock)
      .mockResolvedValueOnce(bad(400, { success: false, message: "erreur metier" }))
      .mockResolvedValueOnce(bad(500, { success: false }));
    // serveur joignable (status 400) donc fallback pas d'erreur
    const res = await fetchMobileConfig("https://api.test/api");
    expect(res).toEqual(fallback);
  });
});
