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";

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

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

  /**
   * Make an authenticated request to Freebit API with retry logic
   */
  async makeAuthenticatedRequest<TResponse extends FreebitResponseBase, TPayload extends object>(
    endpoint: string,
    payload: TPayload
  ): Promise<TResponse> {
    const config = this.authService.getConfig();
    const authKey = await this.authService.getAuthKey();

    const url = this.buildUrl(config.baseUrl, endpoint);
    const requestPayload = { ...payload, authKey };

    let attempt = 0;
    return withRetry(
      async () => {
        attempt += 1;

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

        const controller = new AbortController();
        const timeoutId = setTimeout(() => controller.abort(), config.timeout);
        try {
          const response = await fetch(url, {
            method: "POST",
            headers: { "Content-Type": "application/x-www-form-urlencoded" },
            body: `json=${JSON.stringify(requestPayload)}`,
            signal: controller.signal,
          });

          if (!response.ok) {
            const isProd = process.env["NODE_ENV"] === "production";
            const bodySnippet = isProd ? undefined : await this.safeReadBodySnippet(response);
            this.logger.error("Freebit API 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";
            this.logger.warn("Freebit API returned error response", {
              url,
              resultCode,
              statusCode,
              statusMessage: responseData.status?.message,
              ...(isProd ? {} : { response: redactForLogs(responseData as unknown) }),
            });

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

          this.logger.debug("Freebit API 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("Freebit auth error detected, clearing cache and retrying", { url });
              this.authService.clearAuthCache();
              return true;
            }
            return error.isRetryable();
          }
          return RetryableErrors.isTransientError(error);
        },
        logger: this.logger,
        logContext: "Freebit API request",
      }
    );
  }

  /**
   * Make an authenticated JSON request to Freebit API (for PA05-41)
   */
  async makeAuthenticatedJsonRequest<
    TResponse extends FreebitResponseBase,
    TPayload extends object,
  >(endpoint: string, payload: TPayload): Promise<TResponse> {
    const config = this.authService.getConfig();
    const url = this.buildUrl(config.baseUrl, endpoint);

    let attempt = 0;
    return withRetry(
      async () => {
        attempt += 1;
        this.logger.debug("Freebit JSON API request", {
          url,
          attempt,
          maxAttempts: config.retryAttempts,
          payload: redactForLogs(payload),
        });

        const controller = new AbortController();
        const timeoutId = setTimeout(() => controller.abort(), config.timeout);
        try {
          const response = await fetch(url, {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify(payload),
            signal: controller.signal,
          });

          if (!response.ok) {
            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";
            this.logger.error("Freebit API returned error result code", {
              url,
              resultCode,
              statusCode,
              message: responseData.status?.message,
              ...(isProd ? {} : { response: redactForLogs(responseData as unknown) }),
              attempt,
            });
            throw new FreebitError(
              `API Error: ${responseData.status?.message || "Unknown error"}`,
              resultCode,
              statusCode,
              responseData.status?.message
            );
          }

          this.logger.debug("Freebit JSON API 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("Freebit auth error detected, clearing cache and retrying", { url });
              this.authService.clearAuthCache();
              return true;
            }
            return error.isRetryable();
          }
          return RetryableErrors.isTransientError(error);
        },
        logger: this.logger,
        logContext: "Freebit JSON API request",
      }
    );
  }

  /**
   * 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;
  }
}
