/**
 * Composed Authentication Manager
 * Uses composition instead of inheritance for better flexibility and testability
 */

import type {
  WordPressClientConfig,
  AuthStatus,
  AppPasswordCredentials,
  JwtCredentials,
  BasicCredentials,
} from "@/types/client.js";
import { AuthenticationError } from "@/types/client.js";
import { AUTH_METHODS, type AuthMethod } from "@/types/wordpress.js";
import { LoggerFactory } from "@/utils/logger.js";

const log = LoggerFactory.client("COMPOSED_AUTH");

import type {
  ConfigurationProvider,
  ErrorHandler,
  ParameterValidator,
  AuthenticationProvider,
} from "./interfaces/ManagerInterfaces.js";

import { ConfigurationProviderImpl } from "./implementations/ConfigurationProviderImpl.js";
import { ErrorHandlerImpl } from "./implementations/ErrorHandlerImpl.js";
import { ParameterValidatorImpl } from "./implementations/ParameterValidatorImpl.js";

interface AuthenticationDependencies {
  configProvider: ConfigurationProvider;
  errorHandler: ErrorHandler;
  validator: ParameterValidator;
}

export class ComposedAuthenticationManager implements AuthenticationProvider {
  private isAuth: boolean = false;
  private authToken: string | null = null;
  private tokenExpiry: Date | null = null;
  private lastAuthAttempt: Date | null = null;
  private authMethod: AuthMethod;

  constructor(private dependencies: AuthenticationDependencies) {
    this.authMethod = this.getAuthMethodFromConfig();
    this.validateAuthConfiguration();
  }

  /**
   * Factory method to create ComposedAuthenticationManager with default implementations
   */
  static create(clientConfig: WordPressClientConfig): ComposedAuthenticationManager {
    const configProvider = new ConfigurationProviderImpl(clientConfig);
    const errorHandler = new ErrorHandlerImpl(configProvider);
    const validator = new ParameterValidatorImpl();

    return new ComposedAuthenticationManager({
      configProvider,
      errorHandler,
      validator,
    });
  }

  /**
   * Authenticate with the configured method
   */
  async authenticate(): Promise<boolean> {
    try {
      this.lastAuthAttempt = new Date();

      switch (this.authMethod) {
        case "app-password":
          return await this.authenticateAppPassword();
        case "jwt":
          return await this.authenticateJWT();
        case "basic":
          return await this.authenticateBasic();
        case "api-key":
          return await this.authenticateApiKey();
        default:
          throw new AuthenticationError(`Unsupported authentication method: ${this.authMethod}`, this.authMethod);
      }
    } catch (error) {
      this.isAuth = false;
      this.authToken = null;
      this.dependencies.errorHandler.handleError(error, "authentication");
    }
  }

  /**
   * Check if currently authenticated
   */
  isAuthenticated(): boolean {
    if (!this.isAuth) {
      return false;
    }

    // Check token expiry for JWT
    if (this.authMethod === "jwt" && this.tokenExpiry) {
      if (new Date() > this.tokenExpiry) {
        this.isAuth = false;
        this.authToken = null;
        return false;
      }
    }

    return true;
  }

  /**
   * Get authentication headers for requests
   */
  getAuthHeaders(): Record<string, string> {
    const headers: Record<string, string> = {};

    switch (this.authMethod) {
      case "app-password":
      case "basic":
        headers.Authorization = this.getBasicAuthHeader();
        break;
      case "jwt":
        if (this.authToken) {
          headers.Authorization = `Bearer ${this.authToken}`;
        }
        break;
      case "api-key":
        const apiKey = this.getApiKeyFromConfig();
        if (apiKey) {
          headers["X-API-Key"] = apiKey;
        }
        break;
    }

    return headers;
  }

  /**
   * Handle authentication failure
   */
  async handleAuthFailure(error: unknown): Promise<boolean> {
    log.debug("Handling authentication failure", { error: String(error), method: this.authMethod });

    this.isAuth = false;
    this.authToken = null;

    // For JWT, try to refresh token
    if (this.authMethod === "jwt") {
      try {
        return await this.refreshJWTToken();
      } catch (_refreshError) {
        // If refresh fails, try full re-authentication
        return await this.authenticate();
      }
    }

    // For other methods, try full re-authentication
    return await this.authenticate();
  }

  /**
   * Get authentication status
   */
  getAuthStatus(): AuthStatus {
    return {
      isAuthenticated: this.isAuthenticated(),
      method: this.authMethod,
      lastAuthAttempt: this.lastAuthAttempt,
      tokenExpiry: this.tokenExpiry,
    };
  }

  /**
   * App Password authentication
   */
  private async authenticateAppPassword(): Promise<boolean> {
    const credentials = this.getAppPasswordCredentials();

    // App passwords are stateless - just validate credentials format
    this.dependencies.validator.validateString(credentials.username, "username", { required: true });
    this.dependencies.validator.validateString(credentials.appPassword, "appPassword", { required: true });

    this.isAuth = true;
    log.debug("App password authentication configured");
    return true;
  }

  /**
   * JWT authentication
   */
  private async authenticateJWT(): Promise<boolean> {
    const credentials = this.getJWTCredentials();
    const tokenEndpoint = "/jwt-auth/v1/token";

    try {
      const response = await fetch(`${this.dependencies.configProvider.config.baseUrl}/wp-json${tokenEndpoint}`, {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          username: credentials.username,
          password: credentials.password,
        }),
      });

      if (!response.ok) {
        throw new AuthenticationError(`JWT authentication failed: ${response.statusText}`, "jwt");
      }

      const data = (await response.json()) as { token?: string; expires_in?: number };

      if (!data.token) {
        throw new AuthenticationError("No token received from JWT authentication", "jwt");
      }

      this.authToken = data.token;
      this.isAuth = true;

      // Set token expiry (default to 24 hours if not provided)
      const expiresIn = data.expires_in || 86400; // 24 hours in seconds
      this.tokenExpiry = new Date(Date.now() + expiresIn * 1000);

      log.debug("JWT authentication successful", {
        expiresIn,
        expiry: this.tokenExpiry.toISOString(),
      });

      return true;
    } catch (error) {
      throw new AuthenticationError(`JWT authentication failed: ${String(error)}`, "jwt");
    }
  }

  /**
   * Basic authentication
   */
  private async authenticateBasic(): Promise<boolean> {
    const credentials = this.getBasicCredentials();

    this.dependencies.validator.validateString(credentials.username, "username", { required: true });
    this.dependencies.validator.validateString(credentials.password, "password", { required: true });

    this.isAuth = true;
    log.debug("Basic authentication configured");
    return true;
  }

  /**
   * API Key authentication
   */
  private async authenticateApiKey(): Promise<boolean> {
    const apiKey = this.getApiKeyFromConfig();

    this.dependencies.validator.validateString(apiKey, "apiKey", { required: true });

    this.isAuth = true;
    log.debug("API key authentication configured");
    return true;
  }

  /**
   * Refresh JWT token
   */
  private async refreshJWTToken(): Promise<boolean> {
    if (!this.authToken) {
      throw new AuthenticationError("No token to refresh", "jwt");
    }

    const refreshEndpoint = "/jwt-auth/v1/token/validate";

    try {
      const response = await fetch(`${this.dependencies.configProvider.config.baseUrl}/wp-json${refreshEndpoint}`, {
        method: "POST",
        headers: {
          Authorization: `Bearer ${this.authToken}`,
          "Content-Type": "application/json",
        },
      });

      if (response.ok) {
        // Token is still valid
        log.debug("JWT token validated successfully");
        return true;
      }

      // Token invalid, need to re-authenticate
      this.authToken = null;
      return await this.authenticateJWT();
    } catch (error) {
      throw new AuthenticationError(`JWT token refresh failed: ${String(error)}`, "jwt");
    }
  }

  /**
   * Get Basic auth header
   */
  private getBasicAuthHeader(): string {
    let credentials: { username: string; password?: string; appPassword?: string };

    if (this.authMethod === "app-password") {
      credentials = this.getAppPasswordCredentials();
      const auth = `${credentials.username}:${credentials.appPassword}`;
      return `Basic ${Buffer.from(auth).toString("base64")}`;
    } else {
      credentials = this.getBasicCredentials();
      const auth = `${credentials.username}:${credentials.password}`;
      return `Basic ${Buffer.from(auth).toString("base64")}`;
    }
  }

  /**
   * Extract auth method from configuration
   */
  private getAuthMethodFromConfig(): AuthMethod {
    const authConfig = this.dependencies.configProvider.config.auth;

    if (authConfig.method && Object.values(AUTH_METHODS).includes(authConfig.method as AuthMethod)) {
      return authConfig.method as AuthMethod;
    }

    // Auto-detect based on available credentials
    if (authConfig.appPassword) return "app-password";
    if (authConfig.secret || authConfig.password) return "jwt";
    if (authConfig.apiKey) return "api-key";

    return "basic"; // fallback
  }

  /**
   * Validate authentication configuration
   */
  private validateAuthConfiguration(): void {
    const authConfig = this.dependencies.configProvider.config.auth;

    if (!authConfig) {
      throw new AuthenticationError("No authentication configuration provided", this.authMethod);
    }

    switch (this.authMethod) {
      case "app-password":
        if (!authConfig.username || !authConfig.appPassword) {
          throw new AuthenticationError(
            "App password authentication requires username and appPassword",
            this.authMethod,
          );
        }
        break;
      case "jwt":
        if (!authConfig.username || !authConfig.password) {
          throw new AuthenticationError("JWT authentication requires username and password", this.authMethod);
        }
        break;
      case "basic":
        if (!authConfig.username || !authConfig.password) {
          throw new AuthenticationError("Basic authentication requires username and password", this.authMethod);
        }
        break;
      case "api-key":
        if (!authConfig.apiKey) {
          throw new AuthenticationError("API key authentication requires apiKey", this.authMethod);
        }
        break;
    }
  }

  /**
   * Get app password credentials from config
   */
  private getAppPasswordCredentials(): AppPasswordCredentials {
    const authConfig = this.dependencies.configProvider.config.auth;
    return {
      username: authConfig.username!,
      appPassword: authConfig.appPassword!,
    };
  }

  /**
   * Get JWT credentials from config
   */
  private getJWTCredentials(): JwtCredentials {
    const authConfig = this.dependencies.configProvider.config.auth;
    const credentials: JwtCredentials = {
      username: authConfig.username!,
      password: authConfig.password!,
    };

    if (authConfig.secret) {
      credentials.jwtSecret = authConfig.secret;
    }

    return credentials;
  }

  /**
   * Get basic credentials from config
   */
  private getBasicCredentials(): BasicCredentials {
    const authConfig = this.dependencies.configProvider.config.auth;
    return {
      username: authConfig.username!,
      password: authConfig.password!,
    };
  }

  /**
   * Get API key from config
   */
  private getApiKeyFromConfig(): string {
    const authConfig = this.dependencies.configProvider.config.auth;
    return authConfig.apiKey || "";
  }
}
