import { vi } from "vitest";
import { AuthenticationManager } from "@/client/managers/AuthenticationManager.js";
import { AuthenticationError } from "@/types/client.js";

describe("AuthenticationManager", () => {
  let authManager;
  let mockConfig;

  beforeEach(() => {
    mockConfig = {
      siteUrl: "https://example.com",
      username: "testuser",
      appPassword: "test-app-password",
      authMethod: "app-password",
    };

    authManager = new AuthenticationManager(mockConfig);
  });

  describe("constructor", () => {
    it("should initialize with valid config", () => {
      expect(authManager.config).toEqual(mockConfig);
    });

    it("should throw error with invalid config", () => {
      expect(() => {
        new AuthenticationManager({});
      }).toThrow("Site URL is required");
    });

    it("should validate authentication method", () => {
      expect(() => {
        new AuthenticationManager({
          siteUrl: "https://example.com",
          authMethod: "invalid-method",
        });
      }).toThrow("Invalid authentication method");
    });
  });

  describe("getAuthHeaders", () => {
    it("should return app-password headers", () => {
      const headers = authManager.getAuthHeaders();

      expect(headers).toEqual({
        Authorization: `Basic ${Buffer.from("testuser:test-app-password").toString("base64")}`,
      });
    });

    it("should return JWT headers", () => {
      authManager.config.authMethod = "jwt";
      authManager.config.jwtToken = "test-jwt-token";

      const headers = authManager.getAuthHeaders();

      expect(headers).toEqual({
        Authorization: "Bearer test-jwt-token",
      });
    });

    it("should return API key headers", () => {
      authManager.config.authMethod = "api-key";
      authManager.config.apiKey = "test-api-key";

      const headers = authManager.getAuthHeaders();

      expect(headers).toEqual({
        "X-API-Key": "test-api-key",
      });
    });

    it("should return basic auth headers", () => {
      authManager.config.authMethod = "basic";
      authManager.config.password = "test-password";

      const headers = authManager.getAuthHeaders();

      expect(headers).toEqual({
        Authorization: `Basic ${Buffer.from("testuser:test-password").toString("base64")}`,
      });
    });

    it("should throw error for JWT without token", () => {
      authManager.config.authMethod = "jwt";
      authManager.config.jwtToken = null;

      expect(() => {
        authManager.getAuthHeaders();
      }).toThrow(AuthenticationError);
    });

    it("should throw error for API key without key", () => {
      authManager.config.authMethod = "api-key";
      authManager.config.apiKey = null;

      expect(() => {
        authManager.getAuthHeaders();
      }).toThrow(AuthenticationError);
    });
  });

  describe("validateCredentials", () => {
    it("should validate app-password credentials", () => {
      expect(() => {
        authManager.validateCredentials();
      }).not.toThrow();
    });

    it("should validate JWT credentials", () => {
      authManager.config.authMethod = "jwt";
      authManager.config.jwtToken = "valid-token";

      expect(() => {
        authManager.validateCredentials();
      }).not.toThrow();
    });

    it("should throw error for missing username", () => {
      authManager.config.username = null;

      expect(() => {
        authManager.validateCredentials();
      }).toThrow("Username is required");
    });

    it("should throw error for missing app password", () => {
      authManager.config.appPassword = null;

      expect(() => {
        authManager.validateCredentials();
      }).toThrow("App password is required");
    });

    it("should throw error for missing JWT token", () => {
      authManager.config.authMethod = "jwt";
      authManager.config.jwtToken = null;

      expect(() => {
        authManager.validateCredentials();
      }).toThrow("JWT token is required");
    });

    it("should throw error for missing API key", () => {
      authManager.config.authMethod = "api-key";
      authManager.config.apiKey = null;

      expect(() => {
        authManager.validateCredentials();
      }).toThrow("API key is required");
    });

    it("should throw error for missing basic password", () => {
      authManager.config.authMethod = "basic";
      authManager.config.password = null;

      expect(() => {
        authManager.validateCredentials();
      }).toThrow("Password is required");
    });
  });

  describe("updateAuthMethod", () => {
    it("should update to JWT authentication", () => {
      authManager.updateAuthMethod("jwt", {
        jwtToken: "new-jwt-token",
      });

      expect(authManager.config.authMethod).toBe("jwt");
      expect(authManager.config.jwtToken).toBe("new-jwt-token");
    });

    it("should update to API key authentication", () => {
      authManager.updateAuthMethod("api-key", {
        apiKey: "new-api-key",
      });

      expect(authManager.config.authMethod).toBe("api-key");
      expect(authManager.config.apiKey).toBe("new-api-key");
    });

    it("should update to basic authentication", () => {
      authManager.updateAuthMethod("basic", {
        username: "newuser",
        password: "newpass",
      });

      expect(authManager.config.authMethod).toBe("basic");
      expect(authManager.config.username).toBe("newuser");
      expect(authManager.config.password).toBe("newpass");
    });

    it("should throw error for invalid method", () => {
      expect(() => {
        authManager.updateAuthMethod("invalid", {});
      }).toThrow("Invalid authentication method");
    });

    it("should throw error for missing JWT token", () => {
      expect(() => {
        authManager.updateAuthMethod("jwt", {});
      }).toThrow("JWT token is required");
    });

    it("should throw error for missing API key", () => {
      expect(() => {
        authManager.updateAuthMethod("api-key", {});
      }).toThrow("API key is required");
    });
  });

  describe("refreshToken", () => {
    it("should refresh JWT token", async () => {
      authManager.config.authMethod = "jwt";
      authManager.config.jwtToken = "old-token";
      authManager.config.refreshToken = "refresh-token";

      // Mock the refresh API call
      const mockRefresh = vi.fn().mockResolvedValue({
        token: "new-jwt-token",
        expires_in: 3600,
      });
      authManager.refreshJwtToken = mockRefresh;

      await authManager.refreshToken();

      expect(mockRefresh).toHaveBeenCalled();
      expect(authManager.config.jwtToken).toBe("new-jwt-token");
    });

    it("should handle refresh token errors", async () => {
      authManager.config.authMethod = "jwt";
      authManager.config.refreshToken = "invalid-refresh-token";

      const mockRefresh = vi.fn().mockRejectedValue(new Error("Invalid refresh token"));
      authManager.refreshJwtToken = mockRefresh;

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

    it("should not refresh non-JWT auth methods", async () => {
      authManager.config.authMethod = "app-password";

      await expect(authManager.refreshToken()).rejects.toThrow("Token refresh not supported");
    });
  });

  describe("isTokenExpired", () => {
    it("should check if JWT token is expired", () => {
      authManager.config.authMethod = "jwt";
      authManager.config.jwtToken = "token";
      authManager.config.tokenExpiry = Date.now() - 1000; // Expired 1 second ago

      expect(authManager.isTokenExpired()).toBe(true);
    });

    it("should check if JWT token is not expired", () => {
      authManager.config.authMethod = "jwt";
      authManager.config.jwtToken = "token";
      authManager.config.tokenExpiry = Date.now() + 60000; // Expires in 1 minute

      expect(authManager.isTokenExpired()).toBe(false);
    });

    it("should return false for non-JWT auth methods", () => {
      authManager.config.authMethod = "app-password";

      expect(authManager.isTokenExpired()).toBe(false);
    });

    it("should return true if no expiry set for JWT", () => {
      authManager.config.authMethod = "jwt";
      authManager.config.jwtToken = "token";
      authManager.config.tokenExpiry = null;

      expect(authManager.isTokenExpired()).toBe(true);
    });
  });

  describe("getAuthStatus", () => {
    it("should return auth status for app-password", () => {
      const status = authManager.getAuthStatus();

      expect(status).toEqual({
        method: "app-password",
        isAuthenticated: true,
      });
    });

    it("should return auth status for JWT", () => {
      authManager.config.authMethod = "jwt";
      authManager.config.jwtToken = "valid-token";
      authManager.config.tokenExpiry = Date.now() + 60000;

      const status = authManager.getAuthStatus();

      expect(status.method).toBe("jwt");
      expect(status.isAuthenticated).toBe(true);
      expect(status.tokenExpiry).toBeInstanceOf(Date);
    });

    it("should return status for expired JWT", () => {
      authManager.config.authMethod = "jwt";
      authManager.config.jwtToken = "expired-token";
      authManager.config.tokenExpiry = Date.now() - 1000;

      const status = authManager.getAuthStatus();

      expect(status.isAuthenticated).toBe(false);
      expect(status.method).toBe("jwt");
      // tokenExpiry should still be present for expired tokens
      expect(status.tokenExpiry).toBeInstanceOf(Date);
    });
  });

  describe("edge cases", () => {
    it("should handle empty site URL", () => {
      expect(() => {
        new AuthenticationManager({
          siteUrl: "",
          username: "user",
          appPassword: "pass",
        });
      }).toThrow("Site URL is required");
    });

    it("should handle malformed site URL", () => {
      expect(() => {
        new AuthenticationManager({
          siteUrl: "not-a-url",
          username: "user",
          appPassword: "pass",
        });
      }).toThrow("Invalid site URL");
    });

    it("should handle null credentials", () => {
      const manager = new AuthenticationManager({
        siteUrl: "https://example.com",
        username: null,
        appPassword: null,
        authMethod: "app-password",
      });

      expect(() => {
        manager.validateCredentials();
      }).toThrow("Username is required");
    });

    it("should handle very long credentials", () => {
      const longPassword = "a".repeat(10000);
      const manager = new AuthenticationManager({
        siteUrl: "https://example.com",
        username: "user",
        appPassword: longPassword,
        authMethod: "app-password",
      });

      const headers = manager.getAuthHeaders();
      expect(headers.Authorization).toBeDefined();
    });

    it("should handle special characters in credentials", () => {
      const specialPassword = "pass@#$%^&*()_+-=[]{}|;:,.<>?";
      const manager = new AuthenticationManager({
        siteUrl: "https://example.com",
        username: "user",
        appPassword: specialPassword,
        authMethod: "app-password",
      });

      const headers = manager.getAuthHeaders();
      const decoded = Buffer.from(headers.Authorization.split(" ")[1], "base64").toString();
      expect(decoded).toBe(`user:${specialPassword}`);
    });

    it("should handle unicode characters in credentials", () => {
      const unicodePassword = "pässwörd123";
      const manager = new AuthenticationManager({
        siteUrl: "https://example.com",
        username: "user",
        appPassword: unicodePassword,
        authMethod: "app-password",
      });

      const headers = manager.getAuthHeaders();
      expect(headers.Authorization).toBeDefined();
    });

    it("should handle concurrent auth header requests", () => {
      const promises = [];
      for (let i = 0; i < 100; i++) {
        promises.push(Promise.resolve(authManager.getAuthHeaders()));
      }

      return Promise.all(promises).then((results) => {
        expect(results).toHaveLength(100);
        results.forEach((headers) => {
          expect(headers.Authorization).toBeDefined();
        });
      });
    });
  });
});
