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

vi.mock("axios");

const access_token = 'test_access_token';
const refresh_token = 'test_refresh_token';
const new_access_token = 'new_access_token';
const new_refresh_token = 'new_refresh_token';

describe("TokenManager", () => {
  let mockLocation: Partial<Location>;
  let TokenManagerImpl: any;
  let createTokenManager: any;

  beforeEach(async () => {
    // Reset module state by requiring a fresh instance
    vi.resetModules();
    mockLocation = {
      search: `?access_token=${access_token}&refresh_token=${refresh_token}`
    };
    vi.spyOn(window, 'location', 'get').mockReturnValue(mockLocation as Location);

    // Import fresh module
    const module = await import("./token-manager");
    TokenManagerImpl = module.TokenManagerImpl;
    createTokenManager = module.createTokenManager;
  });

  afterEach(() => {
    vi.clearAllMocks();
  });

  describe("getToken", () => {
    it("should initialize token from URL params on first call", () => {
      const tokenManager = new TokenManagerImpl();
      const token = tokenManager.getToken();
      expect(token).toBe(access_token);
    });

    it("should return empty string if URL params are missing", () => {
      mockLocation.search = "";
      const tokenManager = new TokenManagerImpl();
      const token = tokenManager.getToken();
      expect(token).toBe("");
    });

    it("should return the same token on subsequent calls", () => {
      const tokenManager = new TokenManagerImpl();
      const token1 = tokenManager.getToken();
      const token2 = tokenManager.getToken();
      expect(token1).toBe(token2);
      expect(token1).toBe(access_token);
    });
  });

  describe("setInitialTokens", () => {
    it("should set tokens manually", () => {
      const tokenManager = new TokenManagerImpl();
      tokenManager.setInitialTokens("manual_access", "manual_refresh");
      expect(tokenManager.getToken()).toBe("manual_access");
    });

    it("should throw error if tokens are already initialized", () => {
      const tokenManager = new TokenManagerImpl();
      tokenManager.setInitialTokens("manual_access", "manual_refresh");
      expect(() => tokenManager.setInitialTokens("another_access", "another_refresh"))
        .toThrow("Token already initialized");
    });

    it("should throw error if tokens were already initialized from URL", () => {
      const tokenManager = new TokenManagerImpl();
      tokenManager.getToken(); // Initialize from URL
      expect(() => tokenManager.setInitialTokens("manual_access", "manual_refresh"))
        .toThrow("Token already initialized");
    });
  });

  describe("refreshToken", () => {
    it("should refresh token successfully", async () => {
      vi.mocked(axios.post).mockResolvedValue({
        data: {
          access_token: new_access_token,
          refresh_token: new_refresh_token
        }
      });

      const tokenManager = new TokenManagerImpl();
      tokenManager.setInitialTokens(access_token, refresh_token);

      const refreshedToken = await tokenManager.refreshToken();

      expect(axios.post).toHaveBeenCalledWith('/api/token/refresh', { token: refresh_token });
      expect(refreshedToken).toBe(new_access_token);
      expect(tokenManager.getToken()).toBe(new_access_token);
    });

    it("should initialize tokens from URL before refreshing if not initialized", async () => {
      vi.mocked(axios.post).mockResolvedValue({
        data: {
          access_token: new_access_token,
          refresh_token: new_refresh_token
        }
      });

      const tokenManager = new TokenManagerImpl();
      await tokenManager.refreshToken();

      expect(axios.post).toHaveBeenCalledWith('/api/token/refresh', { token: refresh_token });
      expect(tokenManager.getToken()).toBe(new_access_token);
    });

    it("should throw error if refresh response doesn't contain access_token", async () => {
      vi.mocked(axios.post).mockResolvedValue({
        data: {}
      });

      const tokenManager = new TokenManagerImpl();
      tokenManager.setInitialTokens(access_token, refresh_token);

      await expect(tokenManager.refreshToken())
        .rejects.toThrow("Invalid refresh token response");
    });

    it("should handle axios errors", async () => {
      const error = new Error("Network error");
      vi.mocked(axios.post).mockRejectedValue(error);

      const tokenManager = new TokenManagerImpl();
      tokenManager.setInitialTokens(access_token, refresh_token);

      await expect(tokenManager.refreshToken()).rejects.toThrow("Network error");
    });
  });

  describe("createTokenManager", () => {
    it("should return a singleton instance", () => {
      const tokenManager1 = createTokenManager();
      const tokenManager2 = createTokenManager();
      expect(tokenManager1).toBe(tokenManager2);
    });
  });
});
