jest.mock("../../src/services/apiClient", () => ({
  __esModule: true,
  default: {
    get: jest.fn(),
    post: jest.fn(),
  },
  ApiClientError: class ApiClientError extends Error {
    status?: number;
    constructor(message: string, status?: number) {
      super(message);
      this.name = "ApiClientError";
      this.status = status;
    }
  },
}));

import apiClient, { ApiClientError } from "../../src/services/apiClient";
import { login, logout, me, refresh } from "../../src/services/authApi";

const post = apiClient.post as jest.Mock;
const get = apiClient.get as jest.Mock;

const userData = { id: 1, email: "a@b.c", name: "A", role: "tech", user_type: "technicien" };
const loginData = {
  access_token: "acc",
  refresh_token: "ref",
  token_type: "Bearer",
  expires_in: 3600,
  refresh_expires_in: 86400,
  user: userData,
};

describe("authApi", () => {
  beforeEach(() => jest.clearAllMocks());

  describe("login", () => {
    it("retourne les donnees quand succes", async () => {
      post.mockResolvedValue({ data: { success: true, data: loginData } });
      const res = await login("a@b.c", "pw");
      expect(res).toEqual(loginData);
      expect(post).toHaveBeenCalledWith(
        "/mobile/login.php",
        expect.objectContaining({ email: "a@b.c", password: "pw" }),
        expect.objectContaining({ headers: expect.objectContaining({ "X-Skip-Auth": "1" }) })
      );
    });

    it("injecte invite_token quand fourni", async () => {
      post.mockResolvedValue({ data: { success: true, data: loginData } });
      await login("a@b.c", "pw", "tok");
      expect(post.mock.calls[0][1]).toEqual(expect.objectContaining({ invite_token: "tok" }));
    });

    it("n'injecte pas invite_token quand null", async () => {
      post.mockResolvedValue({ data: { success: true, data: loginData } });
      await login("a@b.c", "pw", null);
      expect(post.mock.calls[0][1].invite_token).toBeUndefined();
    });

    it("fallback sur endpoint sans .php quand premier renvoie 404", async () => {
      post
        .mockRejectedValueOnce(new ApiClientError("not found", 404))
        .mockResolvedValueOnce({ data: { success: true, data: loginData } });
      const res = await login("a@b.c", "pw");
      expect(res).toEqual(loginData);
      expect(post).toHaveBeenCalledTimes(2);
    });

    it("re-throw si premier endpoint echoue avec autre code", async () => {
      post.mockRejectedValue(new ApiClientError("bad", 400));
      await expect(login("a@b.c", "pw")).rejects.toMatchObject({ status: 400 });
      expect(post).toHaveBeenCalledTimes(1);
    });

    it("re-throw si 404 mais deja sur le dernier endpoint", async () => {
      post
        .mockRejectedValueOnce(new ApiClientError("not found", 404))
        .mockRejectedValueOnce(new ApiClientError("not found", 404));
      await expect(login("a@b.c", "pw")).rejects.toMatchObject({ status: 404 });
    });

    it("leve si payload.success=false", async () => {
      post.mockResolvedValue({ data: { success: false, message: "bad creds" } });
      await expect(login("a@b.c", "pw")).rejects.toThrow("bad creds");
    });

    it("message par defaut si payload sans message", async () => {
      post.mockResolvedValue({ data: { success: false } });
      await expect(login("a@b.c", "pw")).rejects.toThrow("Login failed");
    });
  });

  describe("me", () => {
    it("retourne l'utilisateur quand succes", async () => {
      get.mockResolvedValue({ data: { success: true, data: { user: userData } } });
      expect(await me()).toEqual(userData);
    });

    it("fallback endpoint sans .php sur 404", async () => {
      get
        .mockRejectedValueOnce(new ApiClientError("x", 404))
        .mockResolvedValueOnce({ data: { success: true, data: { user: userData } } });
      expect(await me()).toEqual(userData);
    });

    it("re-throw si autre erreur", async () => {
      get.mockRejectedValue(new ApiClientError("boom", 500));
      await expect(me()).rejects.toMatchObject({ status: 500 });
    });

    it("leve Invalid session si pas de user", async () => {
      get.mockResolvedValue({ data: { success: true, data: {} } });
      await expect(me()).rejects.toThrow("Invalid session");
    });

    it("leve avec message serveur si present", async () => {
      get.mockResolvedValue({ data: { success: false, message: "bye" } });
      await expect(me()).rejects.toThrow("bye");
    });
  });

  describe("refresh", () => {
    it("retourne les donnees", async () => {
      post.mockResolvedValue({ data: { success: true, data: loginData } });
      expect(await refresh("ref")).toEqual(loginData);
    });

    it("leve message par defaut si echec", async () => {
      post.mockResolvedValue({ data: { success: false } });
      await expect(refresh("ref")).rejects.toThrow("Refresh failed");
    });
  });

  describe("logout", () => {
    it("appelle post et ne renvoie rien", async () => {
      post.mockResolvedValue({ data: { success: true, data: null } });
      await expect(logout("ref")).resolves.toBeUndefined();
      expect(post).toHaveBeenCalledWith(
        "/mobile/logout.php",
        { refresh_token: "ref" },
        expect.any(Object)
      );
    });
  });

  describe("lastError non-Error dans post fallback", () => {
    it("enveloppe dans Error si lastError pas un Error", async () => {
      post
        .mockRejectedValueOnce("string-err") // not an Error
        .mockRejectedValueOnce("string-err");
      await expect(login("a@b.c", "pw")).rejects.toBeTruthy();
    });
  });
});
