import {
  BadGatewayException,
  BadRequestException,
  ConflictException,
  Injectable,
  Logger,
} from "@nestjs/common";
import { HttpService } from "@nestjs/axios";
import { AxiosError } from "axios";
import { firstValueFrom } from "rxjs";
import {
  InitiatePaymentRequest,
  PaymentIntentSnapshot,
  PaymentReferenceType,
  PaymentService,
  ProviderStatus,
} from "@edr/types";

/** Side-by-side DB row + live provider status from the payment service diagnostic endpoints. */
export interface PaymentDiagnostic {
  db: Record<string, unknown> | null;
  provider: ProviderStatus | null;
}

/**
 * Thin HTTP client for the payment microservice (apps/edr-payment-api) — the passenger app's
 * side of the Phase 6 cutover (docs/payment-service §10). Domain validation stays here;
 * provider calls, intents, and webhooks live in the payment service.
 */
@Injectable()
export class PaymentClientService {
  private readonly logger = new Logger(PaymentClientService.name);
  private readonly baseUrl = (
    process.env.PAYMENT_API_URL ?? "http://localhost:3003"
  ).replace(/\/$/, "");
  private readonly serviceToken = process.env.SERVICE_AUTH_TOKEN ?? "";

  constructor(private readonly http: HttpService) {}

  /** POST /payments/initiate — idempotent per (service, referenceType, referenceId). */
  async initiate(
    request: InitiatePaymentRequest,
  ): Promise<PaymentIntentSnapshot> {
    return this.call("POST", "/payments/initiate", request);
  }

  /** GET /payments/intents?… — active intent by domain reference; null when none exists. */
  async getIntentByReference(
    referenceType: PaymentReferenceType,
    referenceId: string,
  ): Promise<PaymentIntentSnapshot | null> {
    const query = new URLSearchParams({
      service: PaymentService.PASSENGER,
      referenceType,
      referenceId,
    });
    try {
      return await this.call("GET", `/payments/intents?${query.toString()}`);
    } catch (err) {
      if (err instanceof AxiosError && err.response?.status === 404)
        return null;
      throw err;
    }
  }

  /**
   * GET /payments/diagnostic?… — DB intent row + live provider status for a domain reference,
   * side by side. Returns { db: null, provider: null } when the payment service has no intent.
   */
  async getDiagnosticByReference(
    referenceType: PaymentReferenceType,
    referenceId: string,
  ): Promise<PaymentDiagnostic> {
    const query = new URLSearchParams({
      service: PaymentService.PASSENGER,
      referenceType,
      referenceId,
    });
    try {
      return await this.call("GET", `/payments/diagnostic?${query.toString()}`);
    } catch (err) {
      if (err instanceof AxiosError && err.response?.status === 404)
        return { db: null, provider: null };
      throw err;
    }
  }

  /**
   * POST /payments/intents/:id/confirm — submit an OTP for a COLLECT_OTP provider (CAC Bank).
   * A wrong/expired OTP comes back as 400 from the payment service; surface that as a
   * BadRequest (retryable) rather than a 502, so the payer can re-enter the code.
   */
  async confirmOtp(
    intentId: string,
    otp: string,
  ): Promise<PaymentIntentSnapshot> {
    const url = `${this.baseUrl}/payments/intents/${intentId}/confirm`;
    try {
      const response = await firstValueFrom(
        this.http.post<PaymentIntentSnapshot>(
          url,
          { otp },
          {
            headers: this.serviceToken
              ? { "x-service-token": this.serviceToken }
              : {},
          },
        ),
      );
      return response.data;
    } catch (err) {
      if (err instanceof AxiosError && err.response) {
        const detail =
          (err.response.data as { message?: string | string[] })?.message ??
          err.message;
        // 400 = wrong/expired OTP, 404 = unknown intent → both are client-fixable.
        if (err.response.status === 400 || err.response.status === 404) {
          throw new BadRequestException(detail);
        }
        this.logger.error(
          `payment service confirm ${intentId} → ${err.response.status}: ${detail}`,
        );
        throw new BadGatewayException(`Payment service error: ${detail}`);
      }
      this.logger.error(
        `payment service unreachable (confirm ${intentId}): ${
          err instanceof Error ? err.message : String(err)
        }`,
      );
      throw new BadGatewayException("Payment service unreachable");
    }
  }

  private async call<T>(
    method: "GET" | "POST",
    path: string,
    body?: unknown,
  ): Promise<T> {
    const url = `${this.baseUrl}${path}`;
    this.logger.log("=====================================================================");
    this.logger.log(`URL ${url}`);
    this.logger.log("=====================================================================");
    try {
      const response = await firstValueFrom(
        this.http.request<T>({
          method,
          url,
          data: body,
          headers: this.serviceToken
            ? { "x-service-token": this.serviceToken }
            : {},
        }),
      );
      return response.data;
    } catch (err) {
      if (err instanceof AxiosError && err.response) {
        // 4xx/5xx from the payment service: propagate 404 to callers that handle it;
        // 409 = a legitimate conflict (e.g. another provider's payment is already in
        // flight for this booking) — surface its message as-is rather than masking it as
        // a gateway failure; everything else is a genuine gateway-level failure.
        if (err.response.status === 404) throw err;
        const detail =
          (err.response.data as { message?: string | string[] })?.message ??
          err.message;
        if (err.response.status === 409) {
          throw new ConflictException(detail);
        }
        this.logger.error(
          `payment service ${method} ${path} → ${err.response.status}: ${detail}`,
        );
        throw new BadGatewayException(`Payment service error: ${detail}`);
      }
      const message =
        err instanceof Error && err.message ? err.message : String(err);
      this.logger.error(
        `payment service unreachable (${method} ${path}): ${message}`,
      );
      throw new BadGatewayException("Payment service unreachable");
    }
  }
}
