/**
 * Tests for AuthenticationManager
 *
 * Comprehensive test coverage for all authentication methods,
 * configuration validation, and error handling.
 */

import { describe, it, expect, beforeEach, afterAll, vi } from "vitest";
import { AuthenticationManager } from "@/client/managers/AuthenticationManager.js";
import { AuthenticationError } from "@/types/client.js";
import { AUTH_METHODS } from "@/types/wordpress.js";
import { config } from "../../dist/config/Config.js";

// Mock the config module
vi.mock("../../dist/config/Config.js", () => {
  return {
    config: vi.fn(() => ({
      wordpress: {},
      error: { legacyLogsEnabled: false },
      debug: { enabled: false },
    })),
    ConfigHelpers: {
      shouldDebug: vi.fn(() => false),
    },
  };
});

describe("AuthenticationManager", () => {
  let authManager;
  let testConfig;
  const originalFetch = global.fetch;
  let fetchMock;

  // Helper function to create complete mock config
  const createMockConfig = (wordpress = {}) => ({
    wordpress,
    error: { legacyLogsEnabled: false },
    debug: { enabled: false },
  });

  beforeEach(() => {
    // Reset mocks
    vi.clearAllMocks();
    fetchMock = vi.fn();
    global.fetch = fetchMock;

    // Default valid config
    testConfig = {
      siteUrl: "https://example.wordpress.com",
      authMethod: AUTH_METHODS.APP_PASSWORD,
      username: "testuser",
      appPassword: "test-app-password",
      timeout: 30000,
    };

    // Reset the config mock to return default config
    vi.mocked(config).mockReturnValue(createMockConfig({}));

    authManager = new AuthenticationManager(testConfig);
  });

  afterAll(() => {
    global.fetch = originalFetch;
  });

  describe("Constructor", () => {
    it("should initialize with valid config", () => {
      expect(authManager).toBeDefined();
      expect(authManager.getConfig()).toEqual(
        expect.objectContaining({
          siteUrl: "https://example.wordpress.com",
          authMethod: AUTH_METHODS.APP_PASSWORD,
          username: "testuser",
          appPassword: "test-app-password",
        }),
      );
    });

    it("should throw error for missing site URL", () => {
      expect(() => {
        new AuthenticationManager({ ...testConfig, siteUrl: "" });
      }).toThrow(AuthenticationError);
    });

    it("should throw error for invalid site URL format", () => {
      expect(() => {
        new AuthenticationManager({ ...testConfig, siteUrl: "not-a-url" });
      }).toThrow(AuthenticationError);
    });

    it("should throw error for invalid auth method", () => {
      expect(() => {
        new AuthenticationManager({ ...testConfig, authMethod: "invalid-method" });
      }).toThrow(AuthenticationError);
    });

    it("should normalize site URL by removing trailing slash", () => {
      const manager = new AuthenticationManager({
        ...testConfig,
        siteUrl: "https://example.com/",
      });

      expect(manager.getConfig().siteUrl).toBe("https://example.com");
    });
  });

  describe("Environment Configuration", () => {
    it("should get app password auth from env", () => {
      vi.mocked(config).mockReturnValue(
        createMockConfig({
          authMethod: AUTH_METHODS.APP_PASSWORD,
          username: "env-user",
          appPassword: "env-password",
        }),
      );

      const authConfig = AuthenticationManager.getAuthFromEnv();

      expect(authConfig).toEqual({
        method: AUTH_METHODS.APP_PASSWORD,
        username: "env-user",
        appPassword: "env-password",
      });
    });

    it("should get JWT auth from env", () => {
      vi.mocked(config).mockReturnValue(
        createMockConfig({
          authMethod: AUTH_METHODS.JWT,
          username: "jwt-user",
          password: "jwt-password",
          jwtSecret: "jwt-secret",
        }),
      );

      const authConfig = AuthenticationManager.getAuthFromEnv();

      expect(authConfig).toEqual({
        method: AUTH_METHODS.JWT,
        username: "jwt-user",
        password: "jwt-password",
        secret: "jwt-secret",
      });
    });

    it("should get basic auth from env", () => {
      vi.mocked(config).mockReturnValue(
        createMockConfig({
          authMethod: AUTH_METHODS.BASIC,
          username: "basic-user",
          password: "basic-password",
        }),
      );

      const authConfig = AuthenticationManager.getAuthFromEnv();

      expect(authConfig).toEqual({
        method: AUTH_METHODS.BASIC,
        username: "basic-user",
        password: "basic-password",
      });
    });

    it("should get API key auth from env", () => {
      vi.mocked(config).mockReturnValue(
        createMockConfig({
          authMethod: AUTH_METHODS.API_KEY,
          apiKey: "test-api-key",
        }),
      );

      const authConfig = AuthenticationManager.getAuthFromEnv();

      expect(authConfig).toEqual({
        method: AUTH_METHODS.API_KEY,
        apiKey: "test-api-key",
      });
    });

    it("should throw error for unsupported auth method", () => {
      vi.mocked(config).mockReturnValue(
        createMockConfig({
          authMethod: "unsupported-method",
        }),
      );

      expect(() => {
        AuthenticationManager.getAuthFromEnv();
      }).toThrow(AuthenticationError);
    });
  });

  describe("Authentication Headers", () => {
    it("should generate app password headers", () => {
      const headers = authManager.getAuthHeaders();

      expect(headers).toHaveProperty("Authorization");
      expect(headers.Authorization).toMatch(/^Basic /);

      // Verify base64 encoding
      const encoded = headers.Authorization.replace("Basic ", "");
      const decoded = Buffer.from(encoded, "base64").toString("utf-8");
      expect(decoded).toBe("testuser:test-app-password");
    });

    it("should generate JWT headers", () => {
      const jwtConfig = {
        ...testConfig,
        authMethod: AUTH_METHODS.JWT,
        jwtToken: "test-jwt-token",
      };

      const jwtManager = new AuthenticationManager(jwtConfig);
      const headers = jwtManager.getAuthHeaders();

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

    it("should generate basic auth headers", () => {
      const basicConfig = {
        ...testConfig,
        authMethod: AUTH_METHODS.BASIC,
        username: "basicuser",
        password: "basicpass",
      };

      const basicManager = new AuthenticationManager(basicConfig);
      const headers = basicManager.getAuthHeaders();

      expect(headers).toHaveProperty("Authorization");
      expect(headers.Authorization).toMatch(/^Basic /);

      const encoded = headers.Authorization.replace("Basic ", "");
      const decoded = Buffer.from(encoded, "base64").toString("utf-8");
      expect(decoded).toBe("basicuser:basicpass");
    });

    it("should generate API key headers", () => {
      const apiConfig = {
        ...testConfig,
        authMethod: AUTH_METHODS.API_KEY,
        apiKey: "test-api-key",
      };

      const apiManager = new AuthenticationManager(apiConfig);
      const headers = apiManager.getAuthHeaders();

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

    it("should generate empty headers for cookie auth", () => {
      const cookieConfig = {
        ...testConfig,
        authMethod: AUTH_METHODS.COOKIE,
      };

      const cookieManager = new AuthenticationManager(cookieConfig);
      const headers = cookieManager.getAuthHeaders();

      expect(headers).toEqual({});
    });

    it("should throw error for missing app password credentials", () => {
      const invalidConfig = {
        ...testConfig,
        appPassword: undefined,
      };

      const invalidManager = new AuthenticationManager(invalidConfig);

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

    it("should throw error for missing JWT token", () => {
      const jwtConfig = {
        ...testConfig,
        authMethod: AUTH_METHODS.JWT,
      };

      const jwtManager = new AuthenticationManager(jwtConfig);

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

    it("should throw error for unsupported auth method", () => {
      // Force invalid method by modifying config after creation
      authManager.config.authMethod = "invalid-method";

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

  describe("JWT network authentication", () => {
    it("should authenticate using JWT endpoint", async () => {
      fetchMock.mockResolvedValueOnce({
        ok: true,
        statusText: "OK",
        json: async () => ({ token: "jwt-token", expires_in: 3600 }),
      });

      const jwtManager = new AuthenticationManager({
        ...testConfig,
        authMethod: AUTH_METHODS.JWT,
        username: "jwt-user",
        password: "jwt-pass",
      });

      await jwtManager.authenticateJWT();

      expect(fetchMock).toHaveBeenCalledWith(
        "https://example.wordpress.com/wp-json/jwt-auth/v1/token",
        expect.objectContaining({ method: "POST" }),
      );
      expect(jwtManager.getAuthHeaders()).toEqual({
        Authorization: "Bearer jwt-token",
      });
      expect(jwtManager.config.tokenExpiry).toBeGreaterThan(Date.now());
    });

    it("should throw if JWT endpoint returns error", async () => {
      fetchMock.mockResolvedValueOnce({
        ok: false,
        statusText: "Forbidden",
        json: async () => ({}),
      });

      const jwtManager = new AuthenticationManager({
        ...testConfig,
        authMethod: AUTH_METHODS.JWT,
        username: "jwt-user",
        password: "jwt-pass",
      });

      await expect(jwtManager.authenticateJWT()).rejects.toThrow(AuthenticationError);
    });

    it("should validate token before re-auth during refresh", async () => {
      fetchMock.mockResolvedValueOnce({
        ok: true,
        statusText: "OK",
        json: async () => ({}),
      });

      const jwtManager = new AuthenticationManager({
        ...testConfig,
        authMethod: AUTH_METHODS.JWT,
        username: "jwt-user",
        password: "jwt-pass",
        jwtToken: "existing-token",
      });

      await jwtManager.refreshToken();

      expect(fetchMock).toHaveBeenCalledTimes(1);
      expect(fetchMock).toHaveBeenCalledWith(
        "https://example.wordpress.com/wp-json/jwt-auth/v1/token/validate",
        expect.objectContaining({
          method: "POST",
          headers: expect.objectContaining({ Authorization: "Bearer existing-token" }),
        }),
      );
    });
  });

  describe("Configuration Validation", () => {
    it("should validate app password config successfully", () => {
      expect(() => {
        authManager.validateAuthConfig();
      }).not.toThrow();
    });

    it("should validate JWT config successfully", () => {
      const jwtConfig = {
        ...testConfig,
        authMethod: AUTH_METHODS.JWT,
        username: "jwtuser",
        password: "jwtpass",
        jwtSecret: "jwtsecret",
      };

      const jwtManager = new AuthenticationManager(jwtConfig);

      expect(() => {
        jwtManager.validateAuthConfig();
      }).not.toThrow();
    });

    it("should validate basic auth config successfully", () => {
      const basicConfig = {
        ...testConfig,
        authMethod: AUTH_METHODS.BASIC,
        username: "basicuser",
        password: "basicpass",
      };

      const basicManager = new AuthenticationManager(basicConfig);

      expect(() => {
        basicManager.validateAuthConfig();
      }).not.toThrow();
    });

    it("should validate API key config successfully", () => {
      const apiConfig = {
        ...testConfig,
        authMethod: AUTH_METHODS.API_KEY,
        apiKey: "test-api-key",
      };

      const apiManager = new AuthenticationManager(apiConfig);

      expect(() => {
        apiManager.validateAuthConfig();
      }).not.toThrow();
    });

    it("should throw error for missing auth method", () => {
      const invalidConfig = { ...testConfig };
      delete invalidConfig.authMethod;

      const invalidManager = new AuthenticationManager(invalidConfig);

      expect(() => {
        invalidManager.validateAuthConfig();
      }).toThrow(AuthenticationError);
    });

    it("should throw error for incomplete app password config", () => {
      const invalidConfig = {
        ...testConfig,
        appPassword: undefined,
      };

      const invalidManager = new AuthenticationManager(invalidConfig);

      expect(() => {
        invalidManager.validateAuthConfig();
      }).toThrow(AuthenticationError);
    });

    it("should throw error for incomplete JWT config", () => {
      const invalidConfig = {
        ...testConfig,
        authMethod: AUTH_METHODS.JWT,
        username: "user",
        // Missing password and secret
      };

      const invalidManager = new AuthenticationManager(invalidConfig);

      expect(() => {
        invalidManager.validateAuthConfig();
      }).toThrow(AuthenticationError);
    });
  });

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

    it("should validate JWT credentials", () => {
      const jwtConfig = {
        ...testConfig,
        authMethod: AUTH_METHODS.JWT,
        jwtToken: "valid-jwt-token",
      };

      const jwtManager = new AuthenticationManager(jwtConfig);

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

    it("should throw error for missing username", () => {
      const invalidConfig = {
        ...testConfig,
        username: undefined,
      };

      const invalidManager = new AuthenticationManager(invalidConfig);

      expect(() => {
        invalidManager.validateCredentials();
      }).toThrow(AuthenticationError);
    });

    it("should throw error for missing JWT token", () => {
      const jwtConfig = {
        ...testConfig,
        authMethod: AUTH_METHODS.JWT,
      };

      const jwtManager = new AuthenticationManager(jwtConfig);

      expect(() => {
        jwtManager.validateCredentials();
      }).toThrow(AuthenticationError);
    });

    it("should throw error for invalid auth method", () => {
      authManager.config.authMethod = "invalid";

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

  describe("Authentication Method Updates", () => {
    it("should update to app password authentication", () => {
      const credentials = {
        username: "newuser",
        appPassword: "new-app-password",
      };

      authManager.updateAuthMethod(AUTH_METHODS.APP_PASSWORD, credentials);

      expect(authManager.config.authMethod).toBe(AUTH_METHODS.APP_PASSWORD);
      expect(authManager.config.username).toBe("newuser");
      expect(authManager.config.appPassword).toBe("new-app-password");
    });

    it("should update to JWT authentication", () => {
      const credentials = {
        jwtToken: "new-jwt-token",
        username: "jwtuser",
      };

      authManager.updateAuthMethod(AUTH_METHODS.JWT, credentials);

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

    it("should update to basic authentication", () => {
      const credentials = {
        username: "basicuser",
        password: "basicpass",
      };

      authManager.updateAuthMethod(AUTH_METHODS.BASIC, credentials);

      expect(authManager.config.authMethod).toBe(AUTH_METHODS.BASIC);
      expect(authManager.config.username).toBe("basicuser");
      expect(authManager.config.password).toBe("basicpass");
    });

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

      authManager.updateAuthMethod(AUTH_METHODS.API_KEY, credentials);

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

    it("should clear previous credentials when updating method", () => {
      // Start with app password
      expect(authManager.config.appPassword).toBeDefined();

      // Update to JWT
      const jwtCredentials = { jwtToken: "jwt-token" };
      authManager.updateAuthMethod(AUTH_METHODS.JWT, jwtCredentials);

      // App password should be cleared
      expect(authManager.config.appPassword).toBeUndefined();
      expect(authManager.config.jwtToken).toBe("jwt-token");
    });

    it("should throw error for invalid auth method", () => {
      const credentials = { username: "user" };

      expect(() => {
        authManager.updateAuthMethod("invalid-method", credentials);
      }).toThrow(AuthenticationError);
    });

    it("should throw error for incomplete credentials", () => {
      const incompleteCredentials = {}; // Missing required fields

      expect(() => {
        authManager.updateAuthMethod(AUTH_METHODS.APP_PASSWORD, incompleteCredentials);
      }).toThrow(AuthenticationError);
    });
  });

  describe("Token Management", () => {
    it("should detect expired JWT tokens", () => {
      const jwtConfig = {
        ...testConfig,
        authMethod: AUTH_METHODS.JWT,
        jwtToken: "expired-token",
        tokenExpiry: Date.now() - 1000, // Expired 1 second ago
      };

      const jwtManager = new AuthenticationManager(jwtConfig);

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

    it("should detect valid JWT tokens", () => {
      const jwtConfig = {
        ...testConfig,
        authMethod: AUTH_METHODS.JWT,
        jwtToken: "valid-token",
        tokenExpiry: Date.now() + 60 * 60 * 1000, // Valid for 1 hour
      };

      const jwtManager = new AuthenticationManager(jwtConfig);

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

    it("should treat missing expiry as expired", () => {
      const jwtConfig = {
        ...testConfig,
        authMethod: AUTH_METHODS.JWT,
        jwtToken: "token-without-expiry",
      };

      const jwtManager = new AuthenticationManager(jwtConfig);

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

    it("should return false for non-JWT auth methods", () => {
      expect(authManager.isTokenExpired()).toBe(false);
    });

    it("should refresh JWT token with mock implementation", async () => {
      const jwtConfig = {
        ...testConfig,
        authMethod: AUTH_METHODS.JWT,
        jwtToken: "old-token",
      };

      const jwtManager = new AuthenticationManager(jwtConfig);

      // Mock refresh method for testing
      jwtManager.refreshJwtToken = vi.fn().mockResolvedValue({
        token: "new-token",
        expires_in: 3600,
      });

      await jwtManager.refreshToken();

      expect(jwtManager.config.jwtToken).toBe("new-token");
      expect(jwtManager.config.tokenExpiry).toBeGreaterThan(Date.now());
    });

    it("should throw error when refreshing non-JWT tokens", async () => {
      await expect(authManager.refreshToken()).rejects.toThrow(AuthenticationError);
    });
  });

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

      expect(status).toEqual({
        method: AUTH_METHODS.APP_PASSWORD,
        isAuthenticated: true,
      });
    });

    it("should return authentication status for JWT", () => {
      const jwtConfig = {
        ...testConfig,
        authMethod: AUTH_METHODS.JWT,
        jwtToken: "valid-token",
        tokenExpiry: Date.now() + 60 * 60 * 1000,
      };

      const jwtManager = new AuthenticationManager(jwtConfig);
      const status = jwtManager.getAuthStatus();

      expect(status).toEqual({
        method: AUTH_METHODS.JWT,
        isAuthenticated: true,
        tokenExpiry: expect.any(Date),
      });
    });

    it("should return expired status for JWT with expired token", () => {
      const jwtConfig = {
        ...testConfig,
        authMethod: AUTH_METHODS.JWT,
        jwtToken: "expired-token",
        tokenExpiry: Date.now() - 1000,
      };

      const jwtManager = new AuthenticationManager(jwtConfig);
      const status = jwtManager.getAuthStatus();

      expect(status).toEqual({
        method: AUTH_METHODS.JWT,
        isAuthenticated: false,
        tokenExpiry: expect.any(Date),
      });
    });
  });

  describe("Test Authentication", () => {
    it("should pass test authentication with valid headers", async () => {
      const result = await authManager.testAuthentication();
      expect(result).toBe(true);
      expect(authManager.isAuthenticated()).toBe(true);
    });

    it("should handle authentication test failure", async () => {
      // Simulate invalid credentials
      authManager.config.appPassword = undefined;

      const result = await authManager.testAuthentication();
      expect(result).toBe(false);
      expect(authManager.isAuthenticated()).toBe(false);
    });
  });

  describe("Clear Authentication", () => {
    it("should clear authentication state", () => {
      authManager.clearAuthentication();

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

    it("should clear JWT token", () => {
      const jwtConfig = {
        ...testConfig,
        authMethod: AUTH_METHODS.JWT,
        jwtToken: "test-token",
      };

      const jwtManager = new AuthenticationManager(jwtConfig);
      jwtManager.clearAuthentication();

      expect(jwtManager.isAuthenticated()).toBe(false);
    });
  });

  describe("Error Handling", () => {
    it("should handle configuration errors gracefully", () => {
      expect(() => {
        new AuthenticationManager({
          siteUrl: "",
          authMethod: AUTH_METHODS.APP_PASSWORD,
        });
      }).toThrow(AuthenticationError);
    });

    it("should handle missing credentials gracefully", () => {
      const invalidManager = new AuthenticationManager({
        siteUrl: "https://example.com",
        authMethod: AUTH_METHODS.APP_PASSWORD,
        username: "user",
        // Missing appPassword
      });

      expect(() => {
        invalidManager.validateCredentials();
      }).toThrow(AuthenticationError);
    });

    it("should handle invalid method gracefully", () => {
      expect(() => {
        authManager.updateAuthMethod("invalid", {});
      }).toThrow(AuthenticationError);
    });
  });
});
