import {
  BadRequestException,
  Injectable,
  NotFoundException,
  UnauthorizedException,
} from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { OrderStatus, PaymentMethod, PaymentStatus, Prisma } from '@prisma/client';
import { createHmac, randomUUID } from 'crypto';
import { PrismaService } from '../database/prisma.service';
import { MessagingService } from '../messaging/messaging.service';
import { CreatePublicPaymentIntentDto } from './dto/create-public-payment-intent.dto';

type ProviderStatus = {
  provider: string;
  mode: 'ready' | 'pending';
  publicKeyConfigured: boolean;
  secretConfigured: boolean;
  webhookConfigured: boolean;
};

type GatewaySettingsState = {
  mercadoPagoEnabled: boolean;
  mercadoPagoEnvironment: 'production' | 'sandbox';
  mercadoPagoAccessToken: string;
  mercadoPagoPublicKey: string;
  mercadoPagoWebhookSecret: string;
  stripePublicKey: string;
  pixProvider: string;
  webhookBaseUrl: string;
  firebaseProjectId: string;
  settlementMode: string;
  settlementFlow: string;
  platformReceivesAllOnlinePayments: boolean;
  restaurantPayoutDelayDays: number;
  defaultCashOnDeliveryEnabled: boolean;
  autoConfirmOnPaidWebhook: boolean;
  autoRefundOnRefundWebhook: boolean;
  wascriptEnabled: boolean;
  wascriptBaseUrl: string;
  wascriptInstanceToken: string;
  wascriptFallbackToWeb: boolean;
  wascriptWebhookEnabled: boolean;
  wascriptWebhookSecret: string;
  wascriptWebhookCallbackUrl: string;
};

type PublicIntentPayload = {
  provider: string;
  mode: string;
  paymentMethod: string;
  amount: number;
  currency: string;
  customer: {
    fullName: string;
    phone: string;
    email: string | null;
  };
  restaurant: {
    id: string;
    name: string;
    city: string | null;
    neighborhood: string | null;
  };
  settlementPolicy: Awaited<ReturnType<PaymentsService['getSettlementPolicy']>>;
  pix: {
    qrCodeText: string;
    qrCodeImageUrl: string;
    expiresAt: string;
    ticketUrl?: string | null;
  } | null;
  card: {
    checkoutMode: string;
    provider: string;
    last4Preview?: string;
    checkoutUrl?: string | null;
    sandboxCheckoutUrl?: string | null;
    preferenceId?: string | null;
  } | null;
  wallet: {
    available: boolean;
    settlement: string;
  } | null;
  cash: {
    changeSupported: boolean;
    settlement: string;
  } | null;
};

const defaultGatewaySettings: GatewaySettingsState = {
  mercadoPagoEnabled: false,
  mercadoPagoEnvironment: 'production',
  mercadoPagoAccessToken: '',
  mercadoPagoPublicKey: '',
  mercadoPagoWebhookSecret: '',
  stripePublicKey: '',
  pixProvider: 'PIX_INTERNO',
  webhookBaseUrl: 'http://127.0.0.1:3001/api/payments/webhooks',
  firebaseProjectId: '',
  settlementMode: 'homologacao',
  settlementFlow: 'platform_collects_and_repasses',
  platformReceivesAllOnlinePayments: true,
  restaurantPayoutDelayDays: 7,
  defaultCashOnDeliveryEnabled: false,
  autoConfirmOnPaidWebhook: true,
  autoRefundOnRefundWebhook: true,
  wascriptEnabled: false,
  wascriptBaseUrl: 'https://api-whatsapp.wascript.com.br',
  wascriptInstanceToken: '',
  wascriptFallbackToWeb: true,
  wascriptWebhookEnabled: false,
  wascriptWebhookSecret: '',
  wascriptWebhookCallbackUrl: '',
};

@Injectable()
export class PaymentsService {
  constructor(
    private readonly prisma: PrismaService,
    private readonly configService: ConfigService,
    private readonly messagingService: MessagingService,
  ) {}

  async listTransactions() {
    return this.prisma.transaction.findMany({
      take: 120,
      orderBy: { createdAt: 'desc' },
      include: {
        payment: {
          include: {
            order: {
              include: {
                customer: { include: { user: true } },
                restaurant: true,
              },
            },
          },
        },
      },
    });
  }

  async getOverview() {
    const [payments, transactions] = await Promise.all([
      this.prisma.payment.findMany({ include: { order: true } }),
      this.prisma.transaction.findMany(),
    ]);

    const paid = payments.filter((payment) => payment.status === PaymentStatus.PAID);
    const pending = payments.filter((payment) => payment.status === PaymentStatus.PENDING);
    const failed = payments.filter((payment) => payment.status === PaymentStatus.FAILED);
    const refunded = payments.filter((payment) => payment.status === PaymentStatus.REFUNDED);

    return {
      totalVolume: payments.reduce((sum, payment) => sum + Number(payment.amount), 0),
      paidCount: paid.length,
      pendingCount: pending.length,
      failedCount: failed.length,
      refundedCount: refunded.length,
      paidVolume: paid.reduce((sum, payment) => sum + Number(payment.amount), 0),
      pixCount: payments.filter((payment) => payment.method === 'PIX').length,
      cardCount: payments.filter((payment) => ['CREDIT_CARD', 'DEBIT_CARD', 'STRIPE', 'MERCADO_PAGO'].includes(payment.method)).length,
      walletCount: payments.filter((payment) => payment.method === 'WALLET').length,
      transactionCount: transactions.length,
    };
  }

  async getProvidersStatus(): Promise<ProviderStatus[]> {
    const settings = await this.readGatewaySettings();
    const wascript = await this.messagingService.getWascriptProviderStatus();

    return [
      {
        provider: 'Mercado Pago',
        mode:
          settings.mercadoPagoEnabled && settings.mercadoPagoAccessToken
            ? 'ready'
            : 'pending',
        publicKeyConfigured: Boolean(settings.mercadoPagoPublicKey),
        secretConfigured: Boolean(settings.mercadoPagoAccessToken),
        webhookConfigured: Boolean(settings.mercadoPagoWebhookSecret),
      },
      {
        provider: 'Stripe',
        mode: this.configService.get('STRIPE_SECRET_KEY') ? 'ready' : 'pending',
        publicKeyConfigured: Boolean(settings.stripePublicKey || this.configService.get('STRIPE_PUBLIC_KEY')),
        secretConfigured: Boolean(this.configService.get('STRIPE_SECRET_KEY')),
        webhookConfigured: Boolean(this.configService.get('STRIPE_WEBHOOK_SECRET')),
      },
      wascript,
      {
        provider: 'Firebase Push',
        mode: settings.firebaseProjectId ? 'ready' : 'pending',
        publicKeyConfigured: false,
        secretConfigured: Boolean(this.configService.get('FIREBASE_PRIVATE_KEY')),
        webhookConfigured: false,
      },
    ];
  }

  async getGatewaySettings() {
    const settings = await this.readGatewaySettings();

    return {
      ...settings,
      providers: await this.getProvidersStatus(),
    };
  }

  async getSettlementPolicy() {
    const settings = await this.readGatewaySettings();
    return {
      settlementFlow: settings.settlementFlow,
      platformReceivesAllOnlinePayments: settings.platformReceivesAllOnlinePayments,
      restaurantPayoutDelayDays: settings.restaurantPayoutDelayDays,
      settlementMode: settings.settlementMode,
      defaultCashOnDeliveryEnabled: settings.defaultCashOnDeliveryEnabled,
      policyLabel:
        settings.platformReceivesAllOnlinePayments
          ? `Todos os pagamentos online entram primeiro na conta da plataforma e o repasse para a loja ocorre em ${settings.restaurantPayoutDelayDays} dias.`
          : 'Os pagamentos online podem liquidar diretamente no parceiro conforme a configuracao operacional.',
    };
  }

  async updateGatewaySettings(value: Record<string, unknown>) {
    const current = await this.readGatewaySettings();
    const next: GatewaySettingsState = {
      mercadoPagoEnabled:
        typeof value.mercadoPagoEnabled === 'boolean'
          ? value.mercadoPagoEnabled
          : current.mercadoPagoEnabled,
      mercadoPagoEnvironment:
        value.mercadoPagoEnvironment === 'sandbox' ? 'sandbox' : 'production',
      mercadoPagoAccessToken:
        typeof value.mercadoPagoAccessToken === 'string'
          ? value.mercadoPagoAccessToken.trim()
          : current.mercadoPagoAccessToken,
      mercadoPagoPublicKey:
        typeof value.mercadoPagoPublicKey === 'string'
          ? value.mercadoPagoPublicKey.trim()
          : current.mercadoPagoPublicKey,
      mercadoPagoWebhookSecret:
        typeof value.mercadoPagoWebhookSecret === 'string'
          ? value.mercadoPagoWebhookSecret.trim()
          : current.mercadoPagoWebhookSecret,
      stripePublicKey:
        typeof value.stripePublicKey === 'string'
          ? value.stripePublicKey.trim()
          : current.stripePublicKey,
      pixProvider:
        typeof value.pixProvider === 'string'
          ? value.pixProvider.trim()
          : current.pixProvider,
      webhookBaseUrl:
        typeof value.webhookBaseUrl === 'string'
          ? value.webhookBaseUrl.trim()
          : current.webhookBaseUrl,
      firebaseProjectId:
        typeof value.firebaseProjectId === 'string'
          ? value.firebaseProjectId.trim()
          : current.firebaseProjectId,
      settlementMode:
        typeof value.settlementMode === 'string'
          ? value.settlementMode.trim()
          : current.settlementMode,
      settlementFlow:
        typeof value.settlementFlow === 'string'
          ? value.settlementFlow.trim()
          : current.settlementFlow,
      platformReceivesAllOnlinePayments:
        typeof value.platformReceivesAllOnlinePayments === 'boolean'
          ? value.platformReceivesAllOnlinePayments
          : current.platformReceivesAllOnlinePayments,
      restaurantPayoutDelayDays:
        typeof value.restaurantPayoutDelayDays === 'number'
          ? Math.max(0, Math.round(value.restaurantPayoutDelayDays))
          : current.restaurantPayoutDelayDays,
      defaultCashOnDeliveryEnabled:
        typeof value.defaultCashOnDeliveryEnabled === 'boolean'
          ? value.defaultCashOnDeliveryEnabled
          : current.defaultCashOnDeliveryEnabled,
      autoConfirmOnPaidWebhook:
        typeof value.autoConfirmOnPaidWebhook === 'boolean'
          ? value.autoConfirmOnPaidWebhook
          : current.autoConfirmOnPaidWebhook,
      autoRefundOnRefundWebhook:
        typeof value.autoRefundOnRefundWebhook === 'boolean'
          ? value.autoRefundOnRefundWebhook
          : current.autoRefundOnRefundWebhook,
      wascriptEnabled:
        typeof value.wascriptEnabled === 'boolean'
          ? value.wascriptEnabled
          : current.wascriptEnabled,
      wascriptBaseUrl:
        typeof value.wascriptBaseUrl === 'string'
          ? value.wascriptBaseUrl.trim()
          : current.wascriptBaseUrl,
      wascriptInstanceToken:
        typeof value.wascriptInstanceToken === 'string'
          ? value.wascriptInstanceToken.trim()
          : current.wascriptInstanceToken,
      wascriptFallbackToWeb:
        typeof value.wascriptFallbackToWeb === 'boolean'
          ? value.wascriptFallbackToWeb
          : current.wascriptFallbackToWeb,
      wascriptWebhookEnabled:
        typeof value.wascriptWebhookEnabled === 'boolean'
          ? value.wascriptWebhookEnabled
          : current.wascriptWebhookEnabled,
      wascriptWebhookSecret:
        typeof value.wascriptWebhookSecret === 'string'
          ? value.wascriptWebhookSecret.trim()
          : current.wascriptWebhookSecret,
      wascriptWebhookCallbackUrl:
        typeof value.wascriptWebhookCallbackUrl === 'string'
          ? value.wascriptWebhookCallbackUrl.trim()
          : current.wascriptWebhookCallbackUrl,
    };

    const saved = await this.prisma.setting.upsert({
      where: { key: 'payment_gateway_settings' },
      update: { value: next },
      create: { key: 'payment_gateway_settings', value: next },
    });

    return {
      key: saved.key,
      value: saved.value,
      providers: await this.getProvidersStatus(),
    };
  }

  async createPublicIntent(body: CreatePublicPaymentIntentDto) {
    const settlementPolicy = await this.getSettlementPolicy();
    const restaurant = await this.prisma.restaurant.findUnique({
      where: { slug: body.restaurantSlug },
      select: {
        id: true,
        name: true,
        city: true,
        neighborhood: true,
        acceptsCashOnDelivery: true,
      },
    });
    if (!restaurant) {
      throw new NotFoundException('Loja publica nao encontrada para gerar pagamento.');
    }
    if (body.paymentMethod === PaymentMethod.CASH && !restaurant.acceptsCashOnDelivery) {
      throw new BadRequestException('Esta loja nao aceita pagamento na entrega.');
    }

    const amount = Number(body.amount);
    const settings = await this.readGatewaySettings();
    const customer = {
      fullName: body.fullName,
      phone: body.phone,
      email: body.email ?? null,
    };

    if (body.paymentMethod === PaymentMethod.CASH) {
      return {
        provider: 'Pagamento na entrega',
        mode: 'direct',
        paymentMethod: body.paymentMethod,
        amount,
        currency: 'BRL',
        customer,
        restaurant,
        settlementPolicy,
        pix: null,
        card: null,
        wallet: null,
        cash: {
          changeSupported: true,
          settlement: 'na-entrega',
        },
      } satisfies PublicIntentPayload;
    }

    if (!body.orderId) {
      throw new BadRequestException('orderId obrigatorio para PIX e cartao.');
    }

    const payment = await this.prisma.payment.findFirst({
      where: {
        orderId: body.orderId,
        order: {
          restaurant: { slug: body.restaurantSlug },
        },
      },
      include: {
        order: {
          include: {
            restaurant: true,
            customer: { include: { user: true } },
          },
        },
        transactions: {
          orderBy: { createdAt: 'desc' },
          take: 10,
        },
      },
    });
    if (!payment) {
      throw new NotFoundException('Pagamento nao encontrado para este pedido.');
    }

    const storedIntent = this.readStoredIntent(payment.transactions, body.paymentMethod);
    if (storedIntent && payment.status !== PaymentStatus.REFUNDED) {
      return storedIntent;
    }

    if (
      body.paymentMethod === PaymentMethod.PIX ||
      body.paymentMethod === PaymentMethod.CREDIT_CARD
    ) {
      if (!settings.mercadoPagoEnabled || !settings.mercadoPagoAccessToken) {
        throw new BadRequestException('Mercado Pago nao configurado para cobranca real.');
      }
    }

    if (!payment.externalReference) {
      await this.prisma.payment.update({
        where: { id: payment.id },
        data: { externalReference: `CHEGO-${payment.orderId}` },
      });
      payment.externalReference = `CHEGO-${payment.orderId}`;
    }

    if (body.paymentMethod === PaymentMethod.PIX) {
      const pixIntent = await this.createMercadoPagoPixIntent(payment.id, {
        amount,
        restaurantName: restaurant.name,
        customer,
        externalReference: payment.externalReference,
        webhookBaseUrl: settings.webhookBaseUrl,
        accessToken: settings.mercadoPagoAccessToken,
      });

      const payload: PublicIntentPayload = {
        provider: 'Mercado Pago',
        mode: 'ready',
        paymentMethod: body.paymentMethod,
        amount,
        currency: 'BRL',
        customer,
        restaurant,
        settlementPolicy,
        pix: pixIntent,
        card: null,
        wallet: null,
        cash: null,
      };

      await this.persistIntentTransaction(payment.id, 'MERCADO_PAGO_PIX_INTENT', amount, {
        provider: 'mercado-pago',
        providerPaymentId: pixIntent.ticketUrl ? pixIntent.ticketUrl.split('/').pop() ?? null : null,
        externalReference: payment.externalReference,
        intent: payload,
      });

      return payload;
    }

    if (body.paymentMethod === PaymentMethod.CREDIT_CARD) {
      const cardIntent = await this.createMercadoPagoCheckoutIntent(payment.id, {
        amount,
        restaurantName: restaurant.name,
        customer,
        externalReference: payment.externalReference,
        webhookBaseUrl: settings.webhookBaseUrl,
        accessToken: settings.mercadoPagoAccessToken,
      });

      const payload: PublicIntentPayload = {
        provider: 'Mercado Pago',
        mode: 'ready',
        paymentMethod: body.paymentMethod,
        amount,
        currency: 'BRL',
        customer,
        restaurant,
        settlementPolicy,
        pix: null,
        card: cardIntent,
        wallet: null,
        cash: null,
      };

      await this.persistIntentTransaction(payment.id, 'MERCADO_PAGO_CARD_INTENT', amount, {
        provider: 'mercado-pago',
        preferenceId: cardIntent.preferenceId ?? null,
        externalReference: payment.externalReference,
        intent: payload,
      });

      return payload;
    }

    return {
      provider: 'Chego Wallet',
      mode: 'direct',
      paymentMethod: body.paymentMethod,
      amount,
      currency: 'BRL',
      customer,
      restaurant,
      settlementPolicy,
      pix: null,
      card: null,
      wallet: {
        available: true,
        settlement: 'instant',
      },
      cash: null,
    } satisfies PublicIntentPayload;
  }

  async getPublicPaymentStatus(orderId: string) {
    const payment = await this.prisma.payment.findUnique({
      where: { orderId },
      include: {
        order: true,
        transactions: {
          orderBy: { createdAt: 'desc' },
          take: 10,
        },
      },
    });
    if (!payment) {
      throw new NotFoundException('Pagamento nao encontrado para o pedido informado.');
    }

    const latestIntent = this.readStoredIntent(payment.transactions, payment.method);

    return {
      orderId: payment.orderId,
      orderStatus: payment.order.status,
      paymentStatus: payment.status,
      approved: payment.status === PaymentStatus.PAID,
      intent: latestIntent,
      amount: Number(payment.amount),
    };
  }

  async simulateWebhook(paymentId: string, status: PaymentStatus, actorUserId: string) {
    const payment = await this.prisma.payment.findUnique({
      where: { id: paymentId },
      include: { order: true },
    });
    if (!payment) {
      throw new NotFoundException('Pagamento nao encontrado.');
    }

    const updated = await this.prisma.$transaction(async (tx) => {
      const nextPayment = await tx.payment.update({
        where: { id: paymentId },
        data: {
          status,
          externalReference: payment.externalReference ?? `SIM-${Date.now()}`,
        },
        include: { order: true },
      });

      await tx.transaction.create({
        data: {
          paymentId,
          type: `WEBHOOK_${status}`,
          amount: nextPayment.amount,
          metadata: {
            source: 'admin-simulator',
            actorUserId,
            orderId: nextPayment.orderId,
            status,
          },
        },
      });

      return nextPayment;
    });

    return updated;
  }

  async handleMercadoPagoWebhook(
    body: Record<string, unknown>,
    signature?: string,
    requestId?: string,
  ) {
    const settings = await this.readGatewaySettings();
    if (!settings.mercadoPagoAccessToken) {
      throw new BadRequestException('Mercado Pago nao configurado.');
    }

    if (
      settings.mercadoPagoWebhookSecret &&
      !this.verifyMercadoPagoWebhookSignature(settings.mercadoPagoWebhookSecret, body, signature, requestId)
    ) {
      throw new UnauthorizedException('Assinatura do webhook do Mercado Pago invalida.');
    }

    const eventType =
      this.pickString(body, ['type']) ??
      this.pickString(body, ['topic']) ??
      this.pickString(body, ['action']) ??
      '';
    const dataId =
      this.pickString(body, ['data.id']) ??
      this.pickString(body, ['resource']) ??
      this.pickString(body, ['id']) ??
      '';

    let providerPayload: Record<string, unknown> | null = null;
    let externalReference: string | null = null;
    let paymentStatus: PaymentStatus = PaymentStatus.PENDING;
    let providerPaymentId: string | null = null;

    if (eventType.includes('merchant_order')) {
      providerPayload = await this.fetchMercadoPagoMerchantOrder(dataId, settings.mercadoPagoAccessToken);
      const payments = Array.isArray(providerPayload.payments)
        ? (providerPayload.payments as Array<Record<string, unknown>>)
        : [];
      const approvedPayment = payments.find((item) =>
        ['approved', 'accredited'].includes(String(item.status ?? '').toLowerCase()),
      );
      const latestPayment = approvedPayment ?? payments[0] ?? null;
      providerPaymentId = latestPayment ? String(latestPayment.id ?? '') || null : null;
      externalReference = this.pickString(providerPayload, ['external_reference']) ?? null;

      if (providerPaymentId) {
        providerPayload = await this.fetchMercadoPagoPayment(providerPaymentId, settings.mercadoPagoAccessToken);
        externalReference = this.pickString(providerPayload, ['external_reference']) ?? externalReference;
        paymentStatus = this.mapMercadoPagoStatusToPaymentStatus(
          this.pickString(providerPayload, ['status']) ?? '',
        );
      }
    } else {
      providerPayload = await this.fetchMercadoPagoPayment(dataId, settings.mercadoPagoAccessToken);
      providerPaymentId = this.pickString(providerPayload, ['id']) ?? dataId;
      externalReference = this.pickString(providerPayload, ['external_reference']) ?? null;
      paymentStatus = this.mapMercadoPagoStatusToPaymentStatus(
        this.pickString(providerPayload, ['status']) ?? '',
      );
    }

    const result = await this.reconcileWebhookPayment({
      provider: 'mercado-pago',
      externalReference,
      signature,
      rawPayload: body,
      statusHint: paymentStatus,
      providerPayload,
      providerPaymentId,
    });

    return {
      ok: true,
      provider: 'mercado-pago',
      matched: result.matched,
      paymentId: result.paymentId,
    };
  }

  async handleStripeWebhook(body: Record<string, unknown>, signature?: string) {
    const externalReference =
      this.pickString(body, ['data.object.metadata.externalReference']) ??
      this.pickString(body, ['data.object.payment_intent']) ??
      this.pickString(body, ['data.object.id']) ??
      null;

    const eventType = this.pickString(body, ['type']) ?? '';
    const statusHint = eventType.includes('refund')
      ? PaymentStatus.REFUNDED
      : eventType.includes('payment_intent.payment_failed')
        ? PaymentStatus.FAILED
        : PaymentStatus.PAID;

    const result = await this.reconcileWebhookPayment({
      provider: 'stripe',
      externalReference,
      signature,
      rawPayload: body,
      statusHint,
      providerPayload: body,
      providerPaymentId: this.pickString(body, ['data.object.id']) ?? null,
    });

    return {
      ok: true,
      provider: 'stripe',
      matched: result.matched,
      paymentId: result.paymentId,
    };
  }

  private async reconcileWebhookPayment(params: {
    provider: string;
    externalReference: string | null;
    signature?: string;
    rawPayload: Record<string, unknown>;
    statusHint: PaymentStatus;
    providerPayload?: Record<string, unknown> | null;
    providerPaymentId?: string | null;
  }) {
    if (!params.externalReference) {
      return { matched: false, paymentId: null };
    }

    const payment = await this.prisma.payment.findFirst({
      where: {
        OR: [{ externalReference: params.externalReference }, { id: params.externalReference }],
      },
      include: {
        order: {
          include: {
            customer: { include: { user: true } },
            restaurant: true,
          },
        },
      },
    });

    if (!payment) {
      return { matched: false, paymentId: null };
    }

    const updated = await this.prisma.$transaction(async (tx) => {
      const updatedPayment = await tx.payment.update({
        where: { id: payment.id },
        data: {
          status: params.statusHint,
          externalReference: payment.externalReference ?? params.externalReference ?? undefined,
        },
        include: {
          order: {
            include: {
              customer: { include: { user: true } },
              restaurant: true,
            },
          },
        },
      });

      await tx.transaction.create({
        data: {
          paymentId: payment.id,
          type: `WEBHOOK_${params.provider.toUpperCase().replace('-', '_')}`,
          amount: new Prisma.Decimal(payment.amount),
          metadata: {
            provider: params.provider,
            signaturePresent: Boolean(params.signature),
            externalReference: params.externalReference,
            providerPaymentId: params.providerPaymentId ?? null,
            status: params.statusHint,
            payload: JSON.parse(JSON.stringify(params.rawPayload)) as Prisma.InputJsonValue,
            providerPayload: JSON.parse(
              JSON.stringify(params.providerPayload ?? {}),
            ) as Prisma.InputJsonValue,
          },
        },
      });

      const persisted = await tx.setting.findUnique({ where: { key: 'payment_gateway_settings' } });
      const current = (persisted?.value as Record<string, unknown> | null) ?? {};
      const autoConfirmOnPaidWebhook = Boolean(current.autoConfirmOnPaidWebhook ?? true);
      const autoRefundOnRefundWebhook = Boolean(current.autoRefundOnRefundWebhook ?? true);

      if (params.statusHint === PaymentStatus.PAID && autoConfirmOnPaidWebhook) {
        const nextStatus = await this.resolvePaidOrderStatus(tx, updatedPayment.order.restaurantId);
        await tx.order.update({
          where: { id: updatedPayment.orderId },
          data: { status: nextStatus },
        });
      }

      if (params.statusHint === PaymentStatus.REFUNDED && autoRefundOnRefundWebhook) {
        await tx.order.update({
          where: { id: updatedPayment.orderId },
          data: { status: 'REFUNDED' },
        });
      }

      return updatedPayment;
    });

    if (params.statusHint === PaymentStatus.PAID) {
      await this.trySendPaymentApprovedMessage(updated);
    }

    return { matched: true, paymentId: payment.id };
  }

  private async trySendPaymentApprovedMessage(payment: {
    amount: Prisma.Decimal | number;
    order: {
      id: string;
      customer: { fullName: string; user: { phone: string | null } };
      restaurant: { name: string };
    };
  }) {
    const phone = payment.order.customer.user.phone;
    if (!phone) {
      return;
    }

    try {
      await this.messagingService.sendWascriptText(
        phone,
        `Pagamento aprovado no pedido ${payment.order.id} da loja ${payment.order.restaurant.name}. Total ${Number(payment.amount).toFixed(2)}.`,
      );
    } catch {
      // Falha de mensageria nao deve quebrar o fluxo financeiro.
    }
  }

  private async resolvePaidOrderStatus(
    tx: Prisma.TransactionClient,
    restaurantId: string,
  ): Promise<OrderStatus> {
    const setting = await tx.setting.findUnique({
      where: { key: `restaurant_order_flow:${restaurantId}` },
    });
    const flow = (setting?.value as Record<string, unknown> | null) ?? {};
    return flow.acceptMode === 'AUTOMATIC'
      ? OrderStatus.PREPARING
      : OrderStatus.SENT_TO_RESTAURANT;
  }

  private async createMercadoPagoPixIntent(
    paymentId: string,
    input: {
      amount: number;
      restaurantName: string;
      customer: { fullName: string; phone: string; email: string | null };
      externalReference: string;
      webhookBaseUrl: string;
      accessToken: string;
    },
  ) {
    const expiresAt = new Date(Date.now() + 30 * 60 * 1000).toISOString();
    const payer = this.buildMercadoPagoPayer(input.customer);
    const payload = await this.mercadoPagoRequest<Record<string, unknown>>(
      '/v1/payments',
      {
        transaction_amount: Number(input.amount.toFixed(2)),
        description: `Pedido ${input.restaurantName}`,
        payment_method_id: 'pix',
        external_reference: input.externalReference,
        notification_url: `${input.webhookBaseUrl.replace(/\/$/, '')}/mercado-pago`,
        date_of_expiration: expiresAt,
        payer,
      },
      input.accessToken,
      randomUUID(),
    );

    const qrCodeText =
      this.pickString(payload, ['point_of_interaction.transaction_data.qr_code']) ?? '';
    const qrCodeBase64 =
      this.pickString(payload, ['point_of_interaction.transaction_data.qr_code_base64']) ?? '';
    const qrCodeImageUrl = qrCodeBase64
      ? `data:image/png;base64,${qrCodeBase64}`
      : `https://api.qrserver.com/v1/create-qr-code/?size=280x280&data=${encodeURIComponent(qrCodeText)}`;

    return {
      qrCodeText,
      qrCodeImageUrl,
      expiresAt,
      ticketUrl:
        this.pickString(payload, ['point_of_interaction.transaction_data.ticket_url']) ?? null,
    };
  }

  private async createMercadoPagoCheckoutIntent(
    paymentId: string,
    input: {
      amount: number;
      restaurantName: string;
      customer: { fullName: string; phone: string; email: string | null };
      externalReference: string;
      webhookBaseUrl: string;
      accessToken: string;
    },
  ) {
    const appUrl = this.configService.get<string>('APP_URL') ?? 'http://localhost:3003';
    const payload = await this.mercadoPagoRequest<Record<string, unknown>>(
      '/checkout/preferences',
      {
        external_reference: input.externalReference,
        notification_url: `${input.webhookBaseUrl.replace(/\/$/, '')}/mercado-pago`,
        items: [
          {
            title: `Pedido ${input.restaurantName}`,
            quantity: 1,
            currency_id: 'BRL',
            unit_price: Number(input.amount.toFixed(2)),
          },
        ],
        payer: this.buildMercadoPagoPayer(input.customer),
        back_urls: {
          success: `${appUrl}/public-order/payment?payment=approved`,
          pending: `${appUrl}/public-order/payment?payment=pending`,
          failure: `${appUrl}/public-order/payment?payment=failure`,
        },
        auto_return: 'approved',
      },
      input.accessToken,
      randomUUID(),
    );

    return {
      checkoutMode: 'checkout-pro',
      provider: 'Mercado Pago',
      checkoutUrl: this.pickString(payload, ['init_point']) ?? null,
      sandboxCheckoutUrl: this.pickString(payload, ['sandbox_init_point']) ?? null,
      preferenceId: this.pickString(payload, ['id']) ?? null,
    };
  }

  private buildMercadoPagoPayer(customer: {
    fullName: string;
    phone: string;
    email: string | null;
  }) {
    const [firstName, ...restName] = customer.fullName.trim().split(/\s+/);
    return {
      email:
        customer.email?.trim().toLowerCase() ||
        `${customer.phone.replace(/\D+/g, '') || Date.now()}@chegoou.local`,
      first_name: firstName || 'Cliente',
      last_name: restName.join(' ') || 'Chegoou',
    };
  }

  private async mercadoPagoRequest<T>(
    endpoint: string,
    body: Record<string, unknown> | null,
    accessToken: string,
    idempotencyKey?: string,
  ) {
    const response = await fetch(`https://api.mercadopago.com${endpoint}`, {
      method: body ? 'POST' : 'GET',
      headers: {
        Authorization: `Bearer ${accessToken}`,
        'Content-Type': 'application/json',
        ...(idempotencyKey ? { 'X-Idempotency-Key': idempotencyKey } : {}),
      },
      body: body ? JSON.stringify(body) : undefined,
    });

    const payload = await response.json().catch(() => null);
    if (!response.ok) {
      throw new BadRequestException(
        payload && typeof payload === 'object'
          ? JSON.stringify(payload)
          : `Mercado Pago retornou HTTP ${response.status}.`,
      );
    }

    return (payload ?? {}) as T;
  }

  private async fetchMercadoPagoPayment(id: string, accessToken: string) {
    return this.mercadoPagoRequest<Record<string, unknown>>(
      `/v1/payments/${encodeURIComponent(id)}`,
      null,
      accessToken,
    );
  }

  private async fetchMercadoPagoMerchantOrder(id: string, accessToken: string) {
    return this.mercadoPagoRequest<Record<string, unknown>>(
      `/merchant_orders/${encodeURIComponent(id)}`,
      null,
      accessToken,
    );
  }

  private mapMercadoPagoStatusToPaymentStatus(status: string) {
    const normalized = status.trim().toLowerCase();
    if (['approved', 'accredited'].includes(normalized)) {
      return PaymentStatus.PAID;
    }
    if (['refunded', 'charged_back', 'cancelled'].includes(normalized)) {
      return PaymentStatus.REFUNDED;
    }
    if (['rejected', 'failed'].includes(normalized)) {
      return PaymentStatus.FAILED;
    }
    return PaymentStatus.PENDING;
  }

  private verifyMercadoPagoWebhookSignature(
    secret: string,
    body: Record<string, unknown>,
    signature?: string,
    requestId?: string,
  ) {
    if (!secret) {
      return true;
    }
    if (!signature) {
      return false;
    }

    const parts = Object.fromEntries(
      signature
        .split(',')
        .map((item) => item.trim())
        .filter(Boolean)
        .map((item) => {
          const [key, value] = item.split('=');
          return [key, value];
        }),
    );

    const ts = String(parts.ts ?? '');
    const v1 = String(parts.v1 ?? '').toLowerCase();
    const dataId = this.pickString(body, ['data.id']) ?? '';
    const manifest = `id:${dataId};request-id:${requestId ?? ''};ts:${ts};`;
    const expected = createHmac('sha256', secret).update(manifest).digest('hex').toLowerCase();

    return Boolean(v1) && expected === v1;
  }

  private readStoredIntent(
    transactions: Array<{ metadata: Prisma.JsonValue | null; createdAt: Date }>,
    paymentMethod: PaymentMethod,
  ) {
    for (const transaction of transactions) {
      if (!transaction.metadata || typeof transaction.metadata !== 'object') {
        continue;
      }
      const metadata = transaction.metadata as Record<string, unknown>;
      const intent = metadata.intent;
      if (
        intent &&
        typeof intent === 'object' &&
        (intent as Record<string, unknown>).paymentMethod === paymentMethod
      ) {
        return intent as PublicIntentPayload;
      }
    }

    return null;
  }

  private async persistIntentTransaction(
    paymentId: string,
    type: string,
    amount: number,
    metadata: Record<string, unknown>,
  ) {
    await this.prisma.transaction.create({
      data: {
        paymentId,
        type,
        amount: new Prisma.Decimal(amount),
        metadata: JSON.parse(JSON.stringify(metadata)) as Prisma.InputJsonValue,
      },
    });
  }

  private async readGatewaySettings(): Promise<GatewaySettingsState> {
    const persisted = await this.prisma.setting.findUnique({
      where: { key: 'payment_gateway_settings' },
    });
    const current = (persisted?.value as Record<string, unknown> | null) ?? {};

    return {
      ...defaultGatewaySettings,
      mercadoPagoEnabled: Boolean(current.mercadoPagoEnabled ?? defaultGatewaySettings.mercadoPagoEnabled),
      mercadoPagoEnvironment:
        current.mercadoPagoEnvironment === 'sandbox' ? 'sandbox' : defaultGatewaySettings.mercadoPagoEnvironment,
      mercadoPagoAccessToken:
        String(current.mercadoPagoAccessToken ?? this.configService.get('MERCADO_PAGO_ACCESS_TOKEN') ?? '').trim(),
      mercadoPagoPublicKey:
        String(current.mercadoPagoPublicKey ?? this.configService.get('MERCADO_PAGO_PUBLIC_KEY') ?? '').trim(),
      mercadoPagoWebhookSecret:
        String(
          current.mercadoPagoWebhookSecret ??
            this.configService.get('MERCADO_PAGO_WEBHOOK_SECRET') ??
            '',
        ).trim(),
      stripePublicKey:
        String(current.stripePublicKey ?? this.configService.get('STRIPE_PUBLIC_KEY') ?? '').trim(),
      pixProvider: String(current.pixProvider ?? defaultGatewaySettings.pixProvider).trim(),
      webhookBaseUrl:
        String(current.webhookBaseUrl ?? defaultGatewaySettings.webhookBaseUrl).trim(),
      firebaseProjectId:
        String(current.firebaseProjectId ?? this.configService.get('FIREBASE_PROJECT_ID') ?? '').trim(),
      settlementMode: String(current.settlementMode ?? defaultGatewaySettings.settlementMode).trim(),
      settlementFlow: String(current.settlementFlow ?? defaultGatewaySettings.settlementFlow).trim(),
      platformReceivesAllOnlinePayments:
        Boolean(
          current.platformReceivesAllOnlinePayments ??
            defaultGatewaySettings.platformReceivesAllOnlinePayments,
        ),
      restaurantPayoutDelayDays: Number(
        current.restaurantPayoutDelayDays ?? defaultGatewaySettings.restaurantPayoutDelayDays,
      ),
      defaultCashOnDeliveryEnabled:
        Boolean(
          current.defaultCashOnDeliveryEnabled ??
            defaultGatewaySettings.defaultCashOnDeliveryEnabled,
        ),
      autoConfirmOnPaidWebhook:
        Boolean(current.autoConfirmOnPaidWebhook ?? defaultGatewaySettings.autoConfirmOnPaidWebhook),
      autoRefundOnRefundWebhook:
        Boolean(current.autoRefundOnRefundWebhook ?? defaultGatewaySettings.autoRefundOnRefundWebhook),
      wascriptEnabled: Boolean(current.wascriptEnabled ?? defaultGatewaySettings.wascriptEnabled),
      wascriptBaseUrl:
        String(
          current.wascriptBaseUrl ??
            this.configService.get('WASCRIPT_BASE_URL') ??
            defaultGatewaySettings.wascriptBaseUrl,
        ).trim() ||
        defaultGatewaySettings.wascriptBaseUrl,
      wascriptInstanceToken:
        String(
          current.wascriptInstanceToken ??
            this.configService.get('WASCRIPT_INSTANCE_TOKEN') ??
            defaultGatewaySettings.wascriptInstanceToken,
        ).trim(),
      wascriptFallbackToWeb:
        Boolean(current.wascriptFallbackToWeb ?? defaultGatewaySettings.wascriptFallbackToWeb),
      wascriptWebhookEnabled:
        Boolean(current.wascriptWebhookEnabled ?? defaultGatewaySettings.wascriptWebhookEnabled),
      wascriptWebhookSecret:
        String(
          current.wascriptWebhookSecret ??
            this.configService.get('WASCRIPT_WEBHOOK_SECRET') ??
            defaultGatewaySettings.wascriptWebhookSecret,
        ).trim(),
      wascriptWebhookCallbackUrl:
        String(
          current.wascriptWebhookCallbackUrl ?? defaultGatewaySettings.wascriptWebhookCallbackUrl,
        ).trim(),
    };
  }

  private pickString(payload: Record<string, unknown>, paths: string[]) {
    for (const path of paths) {
      const value = path.split('.').reduce<unknown>((current, segment) => {
        if (!current || typeof current !== 'object') {
          return undefined;
        }
        return (current as Record<string, unknown>)[segment];
      }, payload);

      if (typeof value === 'string' && value.trim()) {
        return value;
      }

      if (typeof value === 'number' && Number.isFinite(value)) {
        return String(value);
      }
    }

    return undefined;
  }
}
