import * as SecureStore from "expo-secure-store";
import {
  clearEnvironmentConfig,
  getEnvironmentBaseUrl,
  getInviteToken,
  getRequestTimeoutMs,
  requireEnvironmentBaseUrl,
  saveInviteToken,
  setEnvironmentBaseUrl,
} from "../../src/core/environmentService";

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

  describe("getRequestTimeoutMs", () => {
    it("retourne 15000", () => {
      expect(getRequestTimeoutMs()).toBe(15000);
    });
  });

  describe("setEnvironmentBaseUrl", () => {
    it("rejette une URL vide", async () => {
      await expect(setEnvironmentBaseUrl("   ")).rejects.toThrow("L'URL de l'instance est requise.");
    });

    it("rejette une URL invalide", async () => {
      await expect(setEnvironmentBaseUrl("http://")).rejects.toThrow("URL d'instance invalide.");
    });

    it("rejette un protocole non-http(s) (branche defensive)", async () => {
      const origURL = globalThis.URL;
      (globalThis as unknown as { URL: unknown }).URL = jest
        .fn()
        .mockImplementation(() => ({ protocol: "ftp:", toString: () => "ftp://a.b" }));
      try {
        await expect(setEnvironmentBaseUrl("http://whatever")).rejects.toThrow(
          "L'URL doit utiliser https://"
        );
      } finally {
        (globalThis as unknown as { URL: typeof URL }).URL = origURL;
      }
    });

    it("rejette http:// hors developpement (anti-MITM)", async () => {
      const origDev = (globalThis as unknown as { __DEV__?: boolean }).__DEV__;
      (globalThis as unknown as { __DEV__?: boolean }).__DEV__ = false;
      try {
        await expect(setEnvironmentBaseUrl("http://example.com")).rejects.toThrow(
          "L'URL doit utiliser https://"
        );
      } finally {
        (globalThis as unknown as { __DEV__?: boolean }).__DEV__ = origDev;
      }
    });

    it("tolere http:// en developpement (backend local)", async () => {
      const origDev = (globalThis as unknown as { __DEV__?: boolean }).__DEV__;
      (globalThis as unknown as { __DEV__?: boolean }).__DEV__ = true;
      try {
        const result = await setEnvironmentBaseUrl("http://localhost:8080");
        expect(result).toBe("http://localhost:8080");
      } finally {
        (globalThis as unknown as { __DEV__?: boolean }).__DEV__ = origDev;
      }
    });

    it("prefixe https:// quand le protocole est absent", async () => {
      const result = await setEnvironmentBaseUrl("example.com");
      expect(result).toBe("https://example.com");
      expect(SecureStore.setItemAsync).toHaveBeenCalledWith("mf_api_base_url", "https://example.com");
    });

    it("strip les / finaux", async () => {
      const result = await setEnvironmentBaseUrl("https://example.com///");
      expect(result).toBe("https://example.com");
    });

    it("strip le suffixe /api/mobile", async () => {
      const result = await setEnvironmentBaseUrl("https://example.com/api/mobile");
      expect(result).toBe("https://example.com/api");
    });
  });

  describe("getEnvironmentBaseUrl", () => {
    it("retourne null si rien stocke", async () => {
      (SecureStore.getItemAsync as jest.Mock).mockResolvedValue(null);
      expect(await getEnvironmentBaseUrl()).toBeNull();
    });

    it("retourne la valeur normalisee", async () => {
      (SecureStore.getItemAsync as jest.Mock).mockResolvedValue("https://a.b/");
      expect(await getEnvironmentBaseUrl()).toBe("https://a.b");
    });

    it("retourne null si la valeur stockee est invalide (catch normalizeUrl)", async () => {
      (SecureStore.getItemAsync as jest.Mock).mockResolvedValue("http://");
      expect(await getEnvironmentBaseUrl()).toBeNull();
    });
  });

  describe("requireEnvironmentBaseUrl", () => {
    it("retourne la valeur quand presente", async () => {
      (SecureStore.getItemAsync as jest.Mock).mockResolvedValue("https://a.b");
      expect(await requireEnvironmentBaseUrl()).toBe("https://a.b");
    });

    it("throw quand absente", async () => {
      (SecureStore.getItemAsync as jest.Mock).mockResolvedValue(null);
      await expect(requireEnvironmentBaseUrl()).rejects.toThrow("Aucune instance configuree");
    });
  });

  describe("clearEnvironmentConfig", () => {
    it("supprime base_url et invite_token", async () => {
      await clearEnvironmentConfig();
      expect(SecureStore.deleteItemAsync).toHaveBeenCalledWith("mf_api_base_url");
      expect(SecureStore.deleteItemAsync).toHaveBeenCalledWith("mf_invite_token");
    });
  });

  describe("saveInviteToken", () => {
    it("supprime la cle quand token null/vide", async () => {
      await saveInviteToken(null);
      expect(SecureStore.deleteItemAsync).toHaveBeenCalledWith("mf_invite_token");
      jest.clearAllMocks();
      await saveInviteToken("   ");
      expect(SecureStore.deleteItemAsync).toHaveBeenCalledWith("mf_invite_token");
    });

    it("stocke la valeur trimee", async () => {
      await saveInviteToken("  abc  ");
      expect(SecureStore.setItemAsync).toHaveBeenCalledWith("mf_invite_token", "abc");
    });
  });

  describe("getInviteToken", () => {
    it("retourne la valeur brute quand presente et non vide", async () => {
      (SecureStore.getItemAsync as jest.Mock).mockResolvedValue("tok");
      expect(await getInviteToken()).toBe("tok");
    });

    it("retourne null quand absent", async () => {
      (SecureStore.getItemAsync as jest.Mock).mockResolvedValue(null);
      expect(await getInviteToken()).toBeNull();
    });

    it("retourne null quand valeur vide / espaces", async () => {
      (SecureStore.getItemAsync as jest.Mock).mockResolvedValue("   ");
      expect(await getInviteToken()).toBeNull();
    });
  });
});
