import { BadRequestException } from "@nestjs/common";
import { MerchantProviderConfig, PaymentMethod, Provider } from "@prisma/client";
import {
  ProviderAdapter,
  ProviderCreatePaymentInput,
  ProviderCreatePaymentResult,
  ProviderGetPaymentResult,
  ProviderPaymentStatus,
  ProviderRefundInput,
  ProviderRefundResult,
} from "../../src/providers/provider-adapter.interface";

interface ProgrammedResponses {
  createStatus: ProviderPaymentStatus;
  refundStatus: "pending" | "approved" | "rejected" | "error";
  redirectUrl: string | null;
}

/**
 * Fake adapter determinístico: responde con lo que `FakeProviderRouterService.program(...)` configure.
 * No hace HTTP. El `providerPaymentId` se genera con timestamp para que cada call sea único.
 */
export class FakeAdapter implements ProviderAdapter {
  readonly provider: Provider = Provider.dlocal;
  constructor(private readonly responses: ProgrammedResponses) {}

  async createPayment(input: ProviderCreatePaymentInput): Promise<ProviderCreatePaymentResult> {
    return {
      providerPaymentId: `fake_pay_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
      status: this.responses.createStatus,
      method: PaymentMethod.card,
      redirectUrl: this.responses.redirectUrl,
      responseCode: "FAKE_OK",
      responseMessage: "fake-adapter response",
      rawRequest: { input },
      rawResponse: { status: this.responses.createStatus },
    };
  }

  async getPayment(providerPaymentId: string): Promise<ProviderGetPaymentResult> {
    return {
      providerPaymentId,
      status: this.responses.createStatus,
      responseCode: "FAKE_OK",
      responseMessage: "fake-adapter response",
      rawResponse: { providerPaymentId, status: this.responses.createStatus },
    };
  }

  async refund(input: ProviderRefundInput): Promise<ProviderRefundResult> {
    return {
      providerRefundId: `fake_re_${Date.now()}`,
      status: this.responses.refundStatus,
      responseCode: "FAKE_OK",
      responseMessage: "fake-adapter refund",
      rawRequest: { input },
      rawResponse: { status: this.responses.refundStatus },
    };
  }

  parseWebhook(rawBody: string, headers: Record<string, string>) {
    // Convención del fake: header `x-fake-signature: valid` simula firma OK.
    // Cualquier otro valor (o ausente) → null como si la firma no validase.
    if (headers["x-fake-signature"] !== "valid") return null;
    try {
      const parsed = JSON.parse(rawBody) as { id: string; status: string };
      return {
        providerPaymentId: parsed.id,
        status: (parsed.status as ProviderPaymentStatus) ?? "pending",
        raw: parsed,
      };
    } catch {
      return null;
    }
  }
}

/**
 * Override de ProviderRouterService. Los tests llaman `program({...})` para configurar la
 * próxima respuesta y opcionalmente registran un providerConfig fake para que el router lo encuentre.
 */
export class FakeProviderRouterService {
  private responses: ProgrammedResponses = {
    createStatus: "approved",
    refundStatus: "approved",
    redirectUrl: null,
  };
  private config: MerchantProviderConfig | null = null;

  program(partial: Partial<ProgrammedResponses>): void {
    this.responses = { ...this.responses, ...partial };
  }

  registerConfig(config: MerchantProviderConfig): void {
    this.config = config;
  }

  reset(): void {
    this.responses = { createStatus: "approved", refundStatus: "approved", redirectUrl: null };
    this.config = null;
  }

  async route(): Promise<{ config: MerchantProviderConfig; adapter: ProviderAdapter }> {
    if (!this.config) throw new BadRequestException("No provider config registered in fake router");
    return { config: this.config, adapter: new FakeAdapter(this.responses) };
  }

  async getById(): Promise<{ config: MerchantProviderConfig; adapter: ProviderAdapter }> {
    return this.route();
  }
}
