import {
  Body,
  Controller,
  Get,
  Headers,
  Param,
  Post,
  Query,
  UseGuards,
} from "@nestjs/common";
import {
  ApiBearerAuth,
  ApiExtraModels,
  ApiHeader,
  ApiOperation,
  ApiQuery,
  ApiResponse,
  ApiTags,
  getSchemaPath,
} from "@nestjs/swagger";
import { PaymentIntentStatus } from "@prisma/client";
import { ApiKeyGuard } from "../common/auth/api-key.guard";
import { MerchantAuth } from "../common/auth/auth-context.decorator";
import type { ApiKeyAuthContext } from "../common/auth/auth.types";
import { ApiErrorResponse } from "../common/swagger/api-error.schema";
import { ConfirmPaymentIntentDto } from "./dto/confirm-payment-intent.dto";
import { CreatePaymentIntentDto } from "./dto/create-payment-intent.dto";
import { PaymentAttemptDto } from "./dto/payment-attempt.dto";
import { PaymentIntentDto, PaymentIntentListDto } from "./dto/payment-intent.dto";
import { PaymentIntentService } from "./payment-intent.service";

@ApiTags("Payment Intents")
@ApiExtraModels(PaymentAttemptDto, PaymentIntentDto)
@ApiBearerAuth("bearerAuth")
@ApiResponse({ status: 401, description: "API key faltante, inválida o revocada.", type: ApiErrorResponse })
@ApiResponse({ status: 403, description: "Merchant inactivo.", type: ApiErrorResponse })
@ApiResponse({ status: 429, description: "Rate limit excedido.", type: ApiErrorResponse })
@Controller("payment-intents")
@UseGuards(ApiKeyGuard)
export class PaymentIntentController {
  constructor(private readonly service: PaymentIntentService) {}

  @Post()
  @ApiOperation({
    operationId: "paymentIntentsCreate",
    summary: "Crear payment intent",
    description:
      "Crea un intent de pago en estado `created`. El intent es la **fuente de verdad** del lifecycle (intent → attempt → refund). " +
      "En esta fase no se invoca al provider aún (se hace en endpoints separados de captura/checkout).",
  })
  @ApiHeader({
    name: "Idempotency-Key",
    required: false,
    description:
      "Token único del cliente para deduplicar requests dentro de 24h. Si se reenvía el mismo body+key, devuelve el mismo intent.",
    example: "8c4ee2e6-1c4e-4f3b-9c7d-1234567890ab",
  })
  @ApiResponse({ status: 201, type: PaymentIntentDto })
  @ApiResponse({ status: 400, description: "Validación falló o `customerId` no pertenece al merchant.", type: ApiErrorResponse })
  @ApiResponse({ status: 409, description: "Idempotency-Key reusado con body distinto.", type: ApiErrorResponse })
  create(
    @MerchantAuth() auth: ApiKeyAuthContext,
    @Body() dto: CreatePaymentIntentDto,
    @Headers("idempotency-key") idempotencyKey?: string,
  ) {
    return this.service.create(auth.merchantId, dto, idempotencyKey?.trim() || undefined);
  }

  @Get()
  @ApiOperation({
    operationId: "paymentIntentsList",
    summary: "Listar payment intents",
    description: "Paginación por cursor. Filtro opcional por `status`.",
  })
  @ApiQuery({ name: "limit", required: false, type: Number, example: 20 })
  @ApiQuery({ name: "cursor", required: false, type: String })
  @ApiQuery({ name: "status", required: false, enum: PaymentIntentStatus })
  @ApiResponse({ status: 200, type: PaymentIntentListDto })
  list(
    @MerchantAuth() auth: ApiKeyAuthContext,
    @Query("limit") limit?: string,
    @Query("cursor") cursor?: string,
    @Query("status") status?: PaymentIntentStatus,
  ) {
    return this.service.list(auth.merchantId, {
      limit: limit ? Number(limit) : undefined,
      cursor,
      status,
    });
  }

  @Post(":id/confirm")
  @ApiOperation({
    operationId: "paymentIntentsConfirm",
    summary: "Confirmar payment intent (cobrar vía provider)",
    description:
      "Invoca al provider ruteado (Dpago, dLocal, …) y crea un `PaymentAttempt`, actualiza el status del intent " +
      "y emite el webhook correspondiente. Tres modalidades según el body:\n" +
      "- `paymentLink: true` → link de pago nativo del provider (ej. Dpago `/links`); no requiere `platformId`. " +
      "Devuelve `attempt.redirectUrl` (URL a compartir).\n" +
      "- `platformId: \"18\"` → cobro directo con ese medio; devuelve `attempt.qr` (EMV para dibujar) y/o `attempt.redirectUrl`.\n" +
      "- sin ninguno → el provider decide (hosted checkout); devuelve `attempt.redirectUrl` si aplica.",
  })
  @ApiResponse({
    status: 200,
    description: "Intent confirmado (estado actual + attempt creado).",
    schema: {
      type: "object",
      properties: {
        intent: { $ref: getSchemaPath(PaymentIntentDto) },
        attempt: { $ref: getSchemaPath(PaymentAttemptDto) },
      },
      required: ["intent", "attempt"],
    },
  })
  @ApiResponse({ status: 400, description: "Intent en status no confirmable, o falta provider config apto.", type: ApiErrorResponse })
  @ApiResponse({ status: 404, description: "Intent no encontrado.", type: ApiErrorResponse })
  confirm(
    @MerchantAuth() auth: ApiKeyAuthContext,
    @Param("id") id: string,
    @Body() dto: ConfirmPaymentIntentDto,
  ) {
    return this.service.confirm(auth.merchantId, id, dto);
  }

  @Get(":id")
  @ApiOperation({ operationId: "paymentIntentsRetrieve", summary: "Obtener payment intent por ID" })
  @ApiResponse({ status: 200, type: PaymentIntentDto })
  @ApiResponse({ status: 404, description: "No encontrado.", type: ApiErrorResponse })
  findOne(@MerchantAuth() auth: ApiKeyAuthContext, @Param("id") id: string) {
    return this.service.findOne(auth.merchantId, id);
  }

  @Post(":id/sync")
  @ApiOperation({
    operationId: "paymentIntentsSync",
    summary: "Resincronizar intent contra el provider",
    description:
      "Pull manual del estado al provider (útil cuando el webhook del provider no llegó). " +
      "Actualiza attempt + intent y emite el webhook outbound correspondiente si hay cambio.",
  })
  @ApiResponse({ status: 200, type: PaymentIntentDto })
  @ApiResponse({ status: 404, description: "Intent no encontrado.", type: ApiErrorResponse })
  sync(@MerchantAuth() auth: ApiKeyAuthContext, @Param("id") id: string) {
    return this.service.syncFromProvider(auth.merchantId, id);
  }
}
