import { Injectable, Inject } from "@nestjs/common";
import { Logger } from "nestjs-pino";
import { getErrorMessage } from "@bff/core/utils/error.util";
import type { WhmcsResponse } from "@customer-portal/domain/common";
import type {
  WhmcsApiConfig,
  WhmcsRequestOptions,
  WhmcsConnectionStats,
} from "../types/connection.types";

/**
 * Service for handling HTTP requests to WHMCS API
 * Manages timeouts, retries, and response parsing
 */
@Injectable()
export class WhmcsHttpClientService {
  private stats: WhmcsConnectionStats = {
    totalRequests: 0,
    successfulRequests: 0,
    failedRequests: 0,
    averageResponseTime: 0,
  };

  constructor(@Inject(Logger) private readonly logger: Logger) {}

  /**
   * Make HTTP request to WHMCS API
   */
  async makeRequest<T>(
    config: WhmcsApiConfig,
    action: string,
    params: Record<string, unknown>,
    options: WhmcsRequestOptions = {}
  ): Promise<WhmcsResponse<T>> {
    const startTime = Date.now();
    this.stats.totalRequests++;
    this.stats.lastRequestTime = new Date();

    try {
      const response = await this.executeRequest<T>(config, action, params, options);

      const responseTime = Date.now() - startTime;
      this.updateSuccessStats(responseTime);

      return response;
    } catch (error) {
      this.stats.failedRequests++;
      this.stats.lastErrorTime = new Date();

      this.logger.error(`WHMCS HTTP request failed [${action}]`, {
        error: getErrorMessage(error),
        action,
        params: this.sanitizeLogParams(params),
        responseTime: Date.now() - startTime,
      });

      throw error;
    }
  }

  /**
   * Get connection statistics
   */
  getStats(): WhmcsConnectionStats {
    return { ...this.stats };
  }

  /**
   * Reset connection statistics
   */
  resetStats(): void {
    this.stats = {
      totalRequests: 0,
      successfulRequests: 0,
      failedRequests: 0,
      averageResponseTime: 0,
    };
  }

  /**
   * Execute the actual HTTP request with retry logic
   */
  private async executeRequest<T>(
    config: WhmcsApiConfig,
    action: string,
    params: Record<string, unknown>,
    options: WhmcsRequestOptions
  ): Promise<WhmcsResponse<T>> {
    const maxAttempts = options.retryAttempts ?? config.retryAttempts ?? 3;
    let lastError: Error;

    for (let attempt = 1; attempt <= maxAttempts; attempt++) {
      try {
        return await this.performSingleRequest<T>(config, action, params, options);
      } catch (error) {
        lastError = error as Error;

        if (attempt === maxAttempts) {
          break;
        }

        // Don't retry on certain error types
        if (this.shouldNotRetry(error)) {
          break;
        }

        const delay = this.calculateRetryDelay(attempt, config.retryDelay ?? 1000);
        this.logger.warn(`WHMCS request failed, retrying in ${delay}ms`, {
          action,
          attempt,
          maxAttempts,
          error: getErrorMessage(error),
        });

        await this.sleep(delay);
      }
    }

    throw lastError!;
  }

  /**
   * Perform a single HTTP request
   */
  private async performSingleRequest<T>(
    config: WhmcsApiConfig,
    action: string,
    params: Record<string, unknown>,
    options: WhmcsRequestOptions
  ): Promise<WhmcsResponse<T>> {
    const timeout = options.timeout ?? config.timeout ?? 30000;
    const controller = new AbortController();
    const timeoutId = setTimeout(() => controller.abort(), timeout);

    try {
      const requestBody = this.buildRequestBody(config, action, params);
      const url = `${config.baseUrl}/includes/api.php`;

      const response = await fetch(url, {
        method: "POST",
        headers: {
          "Content-Type": "application/x-www-form-urlencoded",
          "User-Agent": "CustomerPortal/1.0",
        },
        body: requestBody,
        signal: controller.signal,
      });

      clearTimeout(timeoutId);

      const responseText = await response.text();

      if (!response.ok) {
        const snippet = responseText?.slice(0, 300);
        throw new Error(
          `HTTP ${response.status}: ${response.statusText}${snippet ? ` | Body: ${snippet}` : ""}`
        );
      }

      return this.parseResponse<T>(responseText, action, params);
    } finally {
      clearTimeout(timeoutId);
    }
  }

  /**
   * Build request body for WHMCS API
   * Uses API key authentication (identifier + secret)
   */
  private buildRequestBody(
    config: WhmcsApiConfig,
    action: string,
    params: Record<string, unknown>
  ): string {
    const formData = new URLSearchParams();

    // Add API key authentication
    formData.append("identifier", config.identifier);
    formData.append("secret", config.secret);

    // Add action and response format
    formData.append("action", action);
    formData.append("responsetype", "json");

    // Add parameters
    for (const [key, value] of Object.entries(params)) {
      if (value === undefined || value === null) {
        continue;
      }

      this.appendFormParam(formData, key, value);
    }

    return formData.toString();
  }

  private appendFormParam(formData: URLSearchParams, key: string, value: unknown): void {
    if (Array.isArray(value)) {
      value.forEach((entry, index) => {
        this.appendFormParam(formData, `${key}[${index}]`, entry);
      });
      return;
    }

    if (value && typeof value === "object" && !(value instanceof Date)) {
      // WHMCS does not accept nested objects; serialize to JSON for logging/debug.
      try {
        formData.append(key, JSON.stringify(value));
        return;
      } catch {
        formData.append(key, Object.prototype.toString.call(value));
        return;
      }
    }

    formData.append(key, this.serializeScalarParam(value));
  }

  private serializeScalarParam(value: unknown): string {
    if (typeof value === "string") {
      return value;
    }
    if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
      return String(value);
    }
    if (value instanceof Date) {
      return value.toISOString();
    }
    if (typeof value === "symbol") {
      return value.description ? `Symbol(${value.description})` : "Symbol()";
    }
    if (typeof value === "function") {
      return value.name ? `[Function ${value.name}]` : "[Function anonymous]";
    }
    return Object.prototype.toString.call(value);
  }

  /**
   * Parse WHMCS API response according to official documentation
   */
  private parseResponse<T>(
    responseText: string,
    action: string,
    params: Record<string, unknown>
  ): WhmcsResponse<T> {
    let parsedResponse: unknown;

    try {
      parsedResponse = JSON.parse(responseText);
    } catch (parseError) {
      this.logger.error(`Invalid JSON response from WHMCS API [${action}]`, {
        responseText: responseText.substring(0, 500),
        parseError: getErrorMessage(parseError),
        params: this.sanitizeLogParams(params),
      });
      throw new Error("Invalid JSON response from WHMCS API");
    }

    // Validate basic response structure
    if (!this.isWhmcsResponse(parsedResponse)) {
      this.logger.error(`WHMCS API returned invalid response structure [${action}]`, {
        responseType: typeof parsedResponse,
        responseText: responseText.substring(0, 500),
        params: this.sanitizeLogParams(params),
      });
      throw new Error("Invalid response structure from WHMCS API");
    }

    // Handle error responses according to WHMCS API documentation
    if (parsedResponse.result === "error") {
      const { message, error, errorcode, ...additionalFields } = parsedResponse;
      const errorMessage = this.toDisplayString(message ?? error, "Unknown WHMCS API error");
      const errorCode = this.toDisplayString(errorcode, "unknown");

      this.logger.error(`WHMCS API returned error [${action}]`, {
        errorMessage,
        errorCode,
        additionalFields: Object.keys(additionalFields).length > 0 ? additionalFields : undefined,
        params: this.sanitizeLogParams(params),
        fullResponse: parsedResponse,
      });

      // Include full context in the error for better debugging
      const errorContext =
        Object.keys(additionalFields).length > 0
          ? ` | Additional details: ${JSON.stringify(additionalFields)}`
          : "";

      throw new Error(`WHMCS API Error: ${errorMessage} (${errorCode})${errorContext}`);
    }

    // For successful responses, WHMCS API returns data directly at the root level
    // The response structure is: { "result": "success", ...actualData }
    // We return the parsed response directly as T since it contains the actual data
    const { result, message, ...rest } = parsedResponse;
    return {
      result,
      message: typeof message === "string" ? message : undefined,
      data: rest as T,
    } satisfies WhmcsResponse<T>;
  }

  private isWhmcsResponse(value: unknown): value is {
    result: "success" | "error";
    message?: unknown;
    error?: unknown;
    errorcode?: unknown;
  } & Record<string, unknown> {
    if (!value || typeof value !== "object") {
      return false;
    }

    const record = value as Record<string, unknown>;
    const rawResult = record.result;
    return rawResult === "success" || rawResult === "error";
  }

  private toDisplayString(value: unknown, fallback: string): string {
    if (typeof value === "string" && value.trim().length > 0) {
      return value;
    }
    if (typeof value === "number" || typeof value === "boolean") {
      return String(value);
    }
    if (value && typeof value === "object") {
      try {
        return JSON.stringify(value);
      } catch {
        return fallback;
      }
    }
    return fallback;
  }

  /**
   * Check if error should not be retried
   */
  private shouldNotRetry(error: unknown): boolean {
    const message = getErrorMessage(error).toLowerCase();

    // Don't retry authentication errors
    if (message.includes("authentication") || message.includes("unauthorized")) {
      return true;
    }

    // Don't retry validation errors
    if (message.includes("invalid") || message.includes("required")) {
      return true;
    }

    // Don't retry not found errors
    if (message.includes("not found")) {
      return true;
    }

    return false;
  }

  /**
   * Calculate retry delay with exponential backoff
   */
  private calculateRetryDelay(attempt: number, baseDelay: number): number {
    const exponentialDelay = baseDelay * Math.pow(2, attempt - 1);
    const jitter = Math.random() * 0.1 * exponentialDelay;
    return Math.min(exponentialDelay + jitter, 10000); // Max 10 seconds
  }

  /**
   * Sleep for specified milliseconds
   */
  private sleep(ms: number): Promise<void> {
    return new Promise(resolve => setTimeout(resolve, ms));
  }

  /**
   * Update success statistics
   */
  private updateSuccessStats(responseTime: number): void {
    this.stats.successfulRequests++;

    // Update average response time
    const totalSuccessful = this.stats.successfulRequests;
    this.stats.averageResponseTime =
      (this.stats.averageResponseTime * (totalSuccessful - 1) + responseTime) / totalSuccessful;
  }

  /**
   * Sanitize parameters for logging (remove sensitive data)
   */
  private sanitizeLogParams(params: Record<string, unknown>): Record<string, unknown> {
    const sensitiveKeys = [
      "password",
      "secret",
      "token",
      "key",
      "auth",
      "credit_card",
      "cvv",
      "ssn",
      "social_security",
    ];

    const sanitized: Record<string, unknown> = {};

    for (const [key, value] of Object.entries(params)) {
      const keyLower = key.toLowerCase();
      const isSensitive = sensitiveKeys.some(sensitive => keyLower.includes(sensitive));

      if (isSensitive) {
        sanitized[key] = "[REDACTED]";
      } else {
        sanitized[key] = value;
      }
    }

    return sanitized;
  }
}
