import { Injectable, Inject } from "@nestjs/common";
import { Logger } from "nestjs-pino";
import { extractErrorMessage } from "@bff/core/utils/error.util.js";
import { RetryableErrors, withRetry } from "@bff/core/utils/retry.util.js";
import { redactForLogs } from "@bff/core/logging/redaction.util.js";
import { FreebitAuthService } from "./freebit-auth.service.js";
import { FreebitError } from "./freebit-error.service.js";
import { FreebitTestTrackerService } from "./freebit-test-tracker.service.js";

interface FreebitResponseBase {
  resultCode?: string | number;
  status?: {
    message?: string;
    statusCode?: string | number;
  };
}

@Injectable()
export class FreebitClientService {
  constructor(
    private readonly authService: FreebitAuthService,
    private readonly testTracker: FreebitTestTrackerService,
    @Inject(Logger) private readonly logger: Logger
  ) {}

  /**
   * Make an authenticated form-encoded request to Freebit API with retry logic
   */
  async makeAuthenticatedRequest<TResponse extends FreebitResponseBase, TPayload extends object>(
    endpoint: string,
    payload: TPayload
  ): Promise<TResponse> {
    return this.makeRequest<TResponse, TPayload>(endpoint, payload, "form");
  }

  /**
   * Make an authenticated JSON request to Freebit API (for PA05-38, PA05-41, etc.)
   */
  async makeAuthenticatedJsonRequest<
    TResponse extends FreebitResponseBase,
    TPayload extends object,
  >(endpoint: string, payload: TPayload): Promise<TResponse> {
    return this.makeRequest<TResponse, TPayload>(endpoint, payload, "json");
  }

  /**
   * Core authenticated request handler shared by form-encoded and JSON variants.
   */
  private async makeRequest<TResponse extends FreebitResponseBase, TPayload extends object>(
    endpoint: string,
    payload: TPayload,
    contentType: "form" | "json"
  ): Promise<TResponse> {
    const config = this.authService.getConfig();
    const authKey = await this.authService.getAuthKey();
    const url = this.buildUrl(config.baseUrl, endpoint);
    const requestPayload = { ...payload, authKey };
    const logLabel = contentType === "json" ? "Freebit JSON API" : "Freebit API";

    let attempt = 0;
    try {
      const responseData = await withRetry(
        async () => {
          attempt += 1;

          this.logger.debug(`${logLabel} request`, {
            url,
            attempt,
            maxAttempts: config.retryAttempts,
            payload: redactForLogs(requestPayload),
          });

          const controller = new AbortController();
          const timeoutId = setTimeout(() => controller.abort(), config.timeout);
          try {
            const headers =
              contentType === "json"
                ? { "Content-Type": "application/json" }
                : { "Content-Type": "application/x-www-form-urlencoded" };

            const body =
              contentType === "json"
                ? JSON.stringify(requestPayload)
                : `json=${JSON.stringify(requestPayload)}`;

            const response = await fetch(url, {
              method: "POST",
              headers,
              body,
              signal: controller.signal,
            });

            if (!response.ok) {
              const isProd = process.env["NODE_ENV"] === "production";
              const bodySnippet = isProd ? undefined : await this.safeReadBodySnippet(response);
              this.logger.error(`${logLabel} HTTP error`, {
                url,
                status: response.status,
                statusText: response.statusText,
                ...(bodySnippet ? { responseBodySnippet: bodySnippet } : {}),
                attempt,
              });
              throw new FreebitError(
                `HTTP ${response.status}: ${response.statusText}`,
                response.status.toString()
              );
            }

            const responseData = (await response.json()) as TResponse;

            const resultCode = this.normalizeResultCode(responseData.resultCode);
            const statusCode = this.normalizeResultCode(responseData.status?.statusCode);

            if (resultCode && resultCode !== "100") {
              const isProd = process.env["NODE_ENV"] === "production";
              const errorDetails = {
                url,
                resultCode,
                statusCode,
                statusMessage: responseData.status?.message,
                ...(isProd ? {} : { response: redactForLogs(responseData as unknown) }),
                attempt,
              };
              this.logger.error(`${logLabel} returned error response`, errorDetails);
              this.logger.debug({ errorDetails }, `${logLabel} error details`);

              throw new FreebitError(
                `API Error: ${responseData.status?.message || "Unknown error"}`,
                resultCode,
                statusCode,
                responseData.status?.message
              );
            }

            this.logger.debug(`${logLabel} request successful`, { url, resultCode });
            return responseData;
          } finally {
            clearTimeout(timeoutId);
          }
        },
        {
          maxAttempts: config.retryAttempts,
          baseDelayMs: 1000,
          maxDelayMs: 10000,
          isRetryable: error => {
            if (error instanceof FreebitError) {
              if (error.isAuthError() && attempt === 1) {
                this.logger.warn(`${logLabel} auth error detected, clearing cache and retrying`, {
                  url,
                });
                this.authService.clearAuthCache();
                return true;
              }
              return error.isRetryable();
            }
            return RetryableErrors.isTransientError(error);
          },
          logger: this.logger,
          logContext: `${logLabel} request`,
        }
      );

      // Track successful API call
      this.trackApiCall(endpoint, payload, responseData, null).catch((error: unknown) => {
        this.logger.debug("Failed to track API call", {
          error: error instanceof Error ? error.message : String(error),
        });
      });

      return responseData;
    } catch (error: unknown) {
      // Track failed API call
      this.trackApiCall(endpoint, payload, null, error).catch((trackError: unknown) => {
        this.logger.debug("Failed to track API call error", {
          error: trackError instanceof Error ? trackError.message : String(trackError),
        });
      });
      throw error;
    }
  }

  /**
   * Make a simple request without authentication (for health checks)
   */
  async makeSimpleRequest(endpoint: string): Promise<boolean> {
    const config = this.authService.getConfig();
    const url = this.buildUrl(config.baseUrl, endpoint);

    try {
      const controller = new AbortController();
      const timeout = setTimeout(() => controller.abort(), config.timeout);

      const response = await fetch(url, {
        method: "GET",
        signal: controller.signal,
      });

      clearTimeout(timeout);

      return response.ok;
    } catch (error) {
      this.logger.debug("Simple request failed", {
        url,
        error: extractErrorMessage(error),
      });
      return false;
    }
  }

  /**
   * Ensure proper URL construction - remove double slashes
   */
  private buildUrl(baseUrl: string, endpoint: string): string {
    const cleanBase = baseUrl.replace(/\/$/, "");
    const cleanEndpoint = endpoint.startsWith("/") ? endpoint : `/${endpoint}`;
    return `${cleanBase}${cleanEndpoint}`;
  }

  private async safeReadBodySnippet(response: Response): Promise<string | undefined> {
    try {
      const text = await response.text();
      return text ? text.slice(0, 300) : undefined;
    } catch {
      return undefined;
    }
  }

  private normalizeResultCode(code?: string | number | null): string | undefined {
    if (code === undefined || code === null) {
      return undefined;
    }

    const normalized = String(code).trim();
    return normalized.length > 0 ? normalized : undefined;
  }

  /**
   * Track API call for testing purposes
   */
  private async trackApiCall(
    endpoint: string,
    payload: unknown,
    response: FreebitResponseBase | null,
    error: unknown
  ): Promise<void> {
    const payloadObj = payload as Record<string, unknown>;
    const phoneNumber = this.testTracker.extractPhoneNumber(
      (payloadObj["account"] as string) || "",
      payload
    );

    // Only track if we have a phone number (SIM-related calls)
    if (!phoneNumber) {
      return;
    }

    const timestamp = this.testTracker.getCurrentTimestamp();

    let resultCode: string;
    if (response?.resultCode) {
      resultCode = String(response.resultCode);
    } else if (error instanceof FreebitError) {
      resultCode = String(error.resultCode || "ERROR");
    } else {
      resultCode = "ERROR";
    }

    let statusMessage: string;
    if (response?.status?.message) {
      statusMessage = response.status.message;
    } else if (error instanceof FreebitError) {
      statusMessage = error.message;
    } else if (error) {
      statusMessage = extractErrorMessage(error);
    } else {
      statusMessage = "Success";
    }

    await this.testTracker.logApiCall({
      timestamp,
      apiEndpoint: endpoint,
      apiMethod: "POST",
      phoneNumber,
      simIdentifier: (payloadObj["account"] as string) || phoneNumber,
      requestPayload: JSON.stringify(redactForLogs(payload)),
      responseStatus: resultCode === "100" ? "Success" : `Error: ${resultCode}`,
      ...(error ? { error: extractErrorMessage(error) } : {}),
      additionalInfo: statusMessage,
    });
  }
}
