import {
  Body,
  Controller,
  Delete,
  Get,
  Headers,
  HttpStatus,
  Param,
  Patch,
  Post,
  Query,
  Res,
  SetMetadata,
  UseGuards,
} from "@nestjs/common";
import {
  ApiTags,
  ApiOperation,
  ApiBearerAuth,
  ApiQuery,
  ApiOkResponse,
  ApiProduces,
} from "@nestjs/swagger";

import { Response } from "express";
import { PaymentsService } from "./payments.service";
import {
  InitiatePaymentDto,
  RefundDto,
  AddPaymentMethodDto,
  PaymentRegionEnum,
  SupportedPaymentMethodDto,
  PaymentMethodTypeEnum,
  PaymentPlatformDto,
  BookingAmountResponseDto,
  ForceConfirmDto,
  ConfirmOtpDto,
} from "./payments.dto";
import { PassengerStaff } from "../../common/passenger-guards";
import { PASSENGER_PERMS } from "../../seed/passenger-permissions.registry";
import { resolveAllowedOrigin } from "../../common/utils/redirect-origin.util";
import { SupplementaryChargesService } from "./supplementary-charges.service";
import { IsString, IsInt, IsOptional, Min, IsEnum, IsIn } from "class-validator";
import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";

class CreateSupplementaryChargeDto {
  @ApiProperty({ example: 'EDR-20240001', description: 'Booking reference number' }) @IsString() bookingRef: string;
  @ApiProperty({ description: 'Amount owed in minor units (e.g. 5000 = 50 ETB)' }) @IsInt() @Min(1) amountMinor: number;
  @ApiProperty({ example: 'UNDERPAYMENT' }) @IsString() reason: string;
  @ApiPropertyOptional() @IsOptional() @IsString() notes?: string;
}

class WaiveSupplementaryChargeDto {
  @ApiPropertyOptional() @IsOptional() @IsString() notes?: string;
}

class PaySupplementaryChargeDto {
  @ApiProperty({ enum: PaymentMethodTypeEnum, example: 'TELEBIRR' }) @IsEnum(PaymentMethodTypeEnum) method: PaymentMethodTypeEnum;
  @ApiPropertyOptional({ enum: ['web', 'mobile'], default: 'web' }) @IsOptional() @IsIn(['web', 'mobile']) platform?: 'web' | 'mobile';
}

@ApiTags("Payment")
@Controller("payments")
// @Throttle({ strict: { limit: 20, ttl: 60_000 } })
export class PaymentsController {
  constructor(
    private service: PaymentsService,
    private supplementaryService: SupplementaryChargesService,
  ) {}

  @Delete(":id")
  @PassengerStaff([PASSENGER_PERMS.admin])
  @ApiBearerAuth("IAM-auth")
  @ApiOperation({ summary: "Delete a payment intent record (admin only)" })
  deletePayment(@Param("id") id: string) {
    return this.service.deletePayment(id);
  }

  @Get("all")
  @PassengerStaff([
    PASSENGER_PERMS.payments.view,
    PASSENGER_PERMS.payments.viewAll,
    PASSENGER_PERMS.admin,
  ])
  @ApiBearerAuth("IAM-auth")
  @ApiOperation({ summary: "Get all payments with filters (staff/admin only)" })
  @ApiQuery({ name: "search", required: false })
  @ApiQuery({ name: "status", required: false })
  @ApiQuery({ name: "method", required: false })
  @ApiQuery({ name: "page", required: false })
  @ApiQuery({ name: "pageSize", required: false })
  async getAll(
    @Query("search") search?: string,
    @Query("status") status?: string,
    @Query("method") method?: string,
    @Query("page") page?: string,
    @Query("pageSize") pageSize?: string,
  ) {
    return this.service.getAll({
      search,
      status,
      method,
      page: page ? parseInt(page) : 1,
      pageSize: pageSize ? parseInt(pageSize) : 10,
    });
  }

  @Post("initiate")
  @SetMetadata("isPublic", true)
  @ApiOperation({
    summary: "Initiate payment with nationality-based payment methods",
    description: `Initiates payment for a booking with support for multiple payment providers:\n\n**Ethiopian Payment Methods:**\n- TELEBIRR - Ethiopia's leading mobile money\n- CBE_BIRR - Commercial Bank of Ethiopia\n- EBIRR - Electronic payment gateway\n\n**Djiboutian Payment Methods:**\n- WAAFI - Djibouti's mobile money service\n\n**International Payment Methods:**\n- CARD - Visa, Mastercard\n- WALLET - Internal wallet balance\n\n**Multi-Currency:**\n- All transactions processed in ETB\n- Display amounts in ETB, DJF, or USD\n- Real-time exchange rate conversion`,
  })
  initiatePayment(
    @Body() dto: InitiatePaymentDto,
    @Headers("origin") origin?: string,
    @Headers("referer") referer?: string,
    @Headers("x-frontend-base-url") frontendBaseUrl?: string,
  ) {
    // Browser-facing return URLs (telebirr/waafi) follow the domain the user is
    // on — derived from Origin, then Referer, then the X-Frontend-Base-URL
    // header the portal sets, all validated against the allowlist.
    return this.service.initiatePayment(
      dto,
      resolveAllowedOrigin(origin, referer, frontendBaseUrl),
    );
  }

  @Get("intents/:bookingId")
  @SetMetadata("isPublic", true)
  @ApiOperation({ summary: "Get payment intent status for a booking" })
  getIntent(@Param("bookingId") bookingId: string) {
    return this.service.getIntentByBookingId(bookingId);
  }

  @Get("status/:bookingRefOrId")
  @SetMetadata("isPublic", true)
  @ApiOperation({
    summary: "Get payment status by booking id or booking reference (PNR)",
    description:
      "Accepts either a booking UUID or a booking reference / PNR (e.g. EDR-20240001), " +
      "resolves it to the booking, and returns the authoritative payment status pulled from " +
      "the payment microservice.",
  })
  getStatusByBookingRefOrId(@Param("bookingRefOrId") bookingRefOrId: string) {
    return this.service.getIntentByBookingRefOrId(bookingRefOrId);
  }

  @Get("diagnostic/:bookingRefOrId")
  @SetMetadata("isPublic", true)
  @ApiOperation({
    summary:
      "Get { db, provider } by booking id or booking reference (PNR) — diagnostic",
    description:
      "Accepts a booking UUID or a booking reference / PNR (e.g. EDR-20240001), resolves it to " +
      "the booking, and returns { db, provider }: the payment service's stored intent row and a " +
      "live provider status query, side by side. Pure read — does not reconcile the booking.",
  })
  getPaymentDiagnostic(@Param("bookingRefOrId") bookingRefOrId: string) {
    return this.service.getPaymentDiagnosticByBookingRefOrId(bookingRefOrId);
  }

  @Post(":bookingId/confirm")
  @SetMetadata("isPublic", true)
  @ApiOperation({
    summary: "Confirm an OTP-debit payment (CAC Bank)",
    description:
      "Submits the OTP the payer received by SMS. Returns the updated intent status. " +
      "A wrong or expired OTP returns 400 and the payment stays open for retry.",
  })
  confirmOtp(
    @Param("bookingId") bookingId: string,
    @Body() dto: ConfirmOtpDto,
  ) {
    return this.service.confirmOtpPayment(bookingId, dto.otp);
  }

  @Get("waafi/return")
  @SetMetadata("isPublic", true)
  @ApiOperation({
    summary:
      "DEMO ONLY — confirm a Waafi payment from the browser-return params and return JSON for the " +
      "UI to display. The frontend success page forwards the Waafi query params here. Gated by " +
      "WAAFI_DEMO_TRUST_RETURN (INSECURE; real confirmation is the webhook/HPP_GETTRANINFO).",
  })
  @ApiQuery({ name: "referenceId", required: true })
  @ApiQuery({ name: "state", required: true })
  @ApiQuery({ name: "transactionId", required: false })
  waafiReturn(
    @Query("referenceId") referenceId: string,
    @Query("state") state: string,
    @Query("transactionId") transactionId: string,
  ) {
    return this.service.confirmWaafiReturnDemo({
      referenceId,
      state,
      transactionId,
    });
  }

  @Post("refund")
  @PassengerStaff([
    PASSENGER_PERMS.payments.manage,
    PASSENGER_PERMS.payments.refund,
    PASSENGER_PERMS.admin,
  ])
  @ApiBearerAuth("IAM-auth")
  @ApiOperation({ summary: "Refund a confirmed booking (staff/agent only)" })
  refund(@Body() dto: RefundDto) {
    return this.service.refund(dto);
  }

  @Post(":bookingId/force-confirm")
  @PassengerStaff([
    PASSENGER_PERMS.payments.manage,
    PASSENGER_PERMS.payments.manageMethods,
    PASSENGER_PERMS.admin,
  ])
  @ApiBearerAuth("IAM-auth")
  @ApiOperation({
    summary: "Force-confirm payment & generate ticket (back-office only)",
    description:
      "Marks the payment as SUCCEEDED, confirms the booking, and generates the ticket. " +
      "Use when a vendor payment completed but the webhook was never delivered. Idempotent.",
  })
  forceConfirm(
    @Param("bookingId") bookingId: string,
    @Body() dto: ForceConfirmDto,
  ) {
    return this.service.forceConfirmPayment(bookingId, dto);
  }

  @Post("methods")
  @PassengerStaff([
    PASSENGER_PERMS.paymentMethods.manage,
    PASSENGER_PERMS.payments.manageMethods,
    PASSENGER_PERMS.admin,
  ])
  @ApiBearerAuth("IAM-auth")
  @ApiOperation({
    summary: "Add a payment system to the platform catalog (admin only)",
  })
  addMethod(@Body() dto: AddPaymentMethodDto) {
    return this.service.addPaymentMethod(dto);
  }

  @Patch("methods/:id")
  @PassengerStaff([
    PASSENGER_PERMS.paymentMethods.manage,
    PASSENGER_PERMS.payments.manageMethods,
    PASSENGER_PERMS.admin,
  ])
  @ApiBearerAuth("IAM-auth")
  @ApiOperation({
    summary: "Update a payment method configuration (admin only)",
  })
  updateMethod(
    @Param("id") id: string,
    @Body() dto: Partial<AddPaymentMethodDto>,
  ) {
    return this.service.updatePaymentMethod(id, dto);
  }

  @Get("methods")
  @SetMetadata("isPublic", true)
  @ApiOperation({
    summary: "List payment systems supported by the platform",
    description:
      "Returns all enabled payment methods. Optionally filter by `region` to narrow to methods available for a passenger's nationality.",
  })
  @ApiQuery({ name: "region", enum: PaymentRegionEnum, required: false })
  @ApiOkResponse({ type: [SupportedPaymentMethodDto] })
  getMethods(@Query("region") region?: PaymentRegionEnum) {
    return this.service.getSupportedPaymentMethods(region);
  }

  @Get("booking-amount")
  @SetMetadata("isPublic", true)
  @ApiOperation({
    summary: "Get booking amount in a specific currency",
    description:
      "Returns the booking total converted from the booking's stored currency to the requested currency using the latest exchange rate. " +
      "If currency is ETB the stored amount is returned as-is (no conversion). " +
      "Amounts are returned in major currency units (e.g. 162.50 DJF, not centimes).",
  })
  @ApiQuery({ name: "bookingId", required: true, description: "Booking UUID" })
  @ApiQuery({
    name: "currency",
    required: true,
    example: "DJF",
    description: "Target currency: ETB, DJF, or USD",
  })
  @ApiOkResponse({ type: BookingAmountResponseDto })
  getBookingAmount(
    @Query("bookingId") bookingId: string,
    @Query("currency") currency: string,
  ) {
    return this.service.getBookingAmountByCurrency(bookingId, currency);
  }

  @Get("checkout")
  @SetMetadata("isPublic", true)
  @ApiOperation({
    summary: "Browser checkout redirect",
    description:
      "Initiates payment and returns an HTML page that auto-redirects the browser to the provider checkout URL. Designed to be opened directly in a browser tab.",
  })
  @ApiQuery({ name: "bookingId", required: true })
  @ApiQuery({ name: "method", enum: PaymentMethodTypeEnum, required: true })
  @ApiQuery({ name: "platform", enum: ["web", "mobile"], required: false })
  @ApiProduces("text/html")
  async checkout(
    @Query("bookingId") bookingId: string,
    @Query("method") method: PaymentMethodTypeEnum,
    @Query("platform") platform: PaymentPlatformDto = "web",
    @Res() res: Response,
  ) {
    if (!bookingId) {
      return res
        .status(HttpStatus.BAD_REQUEST)
        .type("html")
        .send(
          this.buildErrorHtml("Missing required query parameter: bookingId"),
        );
    }
    if (!method || !Object.values(PaymentMethodTypeEnum).includes(method)) {
      return res
        .status(HttpStatus.BAD_REQUEST)
        .type("html")
        .send(
          this.buildErrorHtml("Missing or invalid query parameter: method"),
        );
    }

    try {
      const result = await this.service.initiatePayment({
        bookingId,
        method,
        platform,
      });
      const url =
        result.clientAction?.type === "REDIRECT"
          ? result.clientAction.url
          : undefined;

      if (url) {
        return res
          .status(HttpStatus.OK)
          .type("html")
          .send(this.buildRedirectHtml(url));
      }

      return res
        .status(HttpStatus.OK)
        .type("html")
        .send(this.buildStatusHtml(result.status, result.intentId));
    } catch (err: unknown) {
      const message =
        err instanceof Error ? err.message : "An unexpected error occurred";
      return res
        .status(HttpStatus.OK)
        .type("html")
        .send(this.buildErrorHtml(message));
    }
  }

  // ── Supplementary Charges ──────────────────────────────────────────────────

  @Post('supplementary')
  @PassengerStaff([PASSENGER_PERMS.payments.manage, PASSENGER_PERMS.admin])
  @ApiBearerAuth('IAM-auth')
  @ApiOperation({ summary: 'Raise a supplementary charge for an underpayment (staff only)' })
  createSupplementaryCharge(
    @Body() dto: CreateSupplementaryChargeDto,
    @Headers('x-iam-user-id') iamUserId?: string,
  ) {
    return this.supplementaryService.create({
      ...dto,
      createdBy: iamUserId ?? 'staff',
    });
  }

  @Get('supplementary')
  @PassengerStaff([PASSENGER_PERMS.payments.view, PASSENGER_PERMS.payments.viewAll, PASSENGER_PERMS.admin])
  @ApiBearerAuth('IAM-auth')
  @ApiOperation({ summary: 'List supplementary charges (staff only)' })
  @ApiQuery({ name: 'bookingRef', required: false })
  @ApiQuery({ name: 'status', required: false })
  @ApiQuery({ name: 'page', required: false })
  @ApiQuery({ name: 'pageSize', required: false })
  listSupplementaryCharges(
    @Query('bookingRef') bookingRef?: string,
    @Query('status') status?: string,
    @Query('page') page?: string,
    @Query('pageSize') pageSize?: string,
  ) {
    return this.supplementaryService.getAll({
      bookingRef,
      status,
      page: page ? parseInt(page) : 1,
      pageSize: pageSize ? parseInt(pageSize) : 20,
    });
  }

  @Get('supplementary/by-token/:token')
  @SetMetadata('isPublic', true)
  @ApiOperation({ summary: 'Get supplementary charge by payment token (public — for self-pay page)' })
  getSupplementaryByToken(@Param('token') token: string) {
    return this.supplementaryService.getByToken(token);
  }

  @Post('supplementary/by-token/:token/pay')
  @SetMetadata('isPublic', true)
  @ApiOperation({ summary: 'Initiate payment for a supplementary charge (public — self-pay)' })
  paySupplementaryCharge(
    @Param('token') token: string,
    @Body() dto: PaySupplementaryChargeDto,
  ) {
    return this.supplementaryService.pay(token, dto.method, dto.platform);
  }

  @Post('supplementary/:id/mark-paid')
  @PassengerStaff([PASSENGER_PERMS.payments.manage, PASSENGER_PERMS.admin])
  @ApiBearerAuth('IAM-auth')
  @ApiOperation({ summary: 'Manually mark a supplementary charge as paid (staff only)' })
  markSupplementaryPaid(
    @Param('id') id: string,
    @Body() body: { providerTxnId?: string },
  ) {
    return this.supplementaryService.markPaid(id, body.providerTxnId);
  }

  @Post('supplementary/:id/waive')
  @PassengerStaff([PASSENGER_PERMS.payments.manage, PASSENGER_PERMS.admin])
  @ApiBearerAuth('IAM-auth')
  @ApiOperation({ summary: 'Waive a supplementary charge (staff only)' })
  waiveSupplementaryCharge(
    @Param('id') id: string,
    @Body() dto: WaiveSupplementaryChargeDto,
    @Headers('x-iam-user-id') iamUserId?: string,
  ) {
    return this.supplementaryService.waive(id, dto.notes ?? '', iamUserId ?? 'staff');
  }

  @Post('supplementary/:id/resend')
  @PassengerStaff([PASSENGER_PERMS.payments.manage, PASSENGER_PERMS.admin])
  @ApiBearerAuth('IAM-auth')
  @ApiOperation({ summary: 'Resend payment link for a supplementary charge (staff only)' })
  resendSupplementaryLink(@Param('id') id: string) {
    return this.supplementaryService.resendLink(id);
  }

  private buildRedirectHtml(url: string): string {
    const escaped = url.replace(/\"/g, "&quot;");
    return `<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="refresh" content="0;url=${escaped}">
  <title>Redirecting to payment…</title>
  <style>
    body { font-family: sans-serif; display: flex; align-items: center; justify-content: center; min-height: 100vh; margin: 0; background: #f5f5f5; }
    .card { background: #fff; border-radius: 8px; padding: 40px; text-align: center; box-shadow: 0 2px 8px rgba(0,0,0,.1); max-width: 400px; }
    .spinner { width: 40px; height: 40px; border: 4px solid #e0e0e0; border-top-color: #1a73e8; border-radius: 50%; animation: spin .8s linear infinite; margin: 0 auto 20px; }
    @keyframes spin { to { transform: rotate(360deg); } }
    p { color: #555; margin: 0 0 16px; }
    a { color: #1a73e8; }
  </style>
</head>
<body>
  <div class="card">
    <div class="spinner"></div>
    <p>Redirecting to payment provider…</p>
    <p><a href="${escaped}">Click here if you are not redirected</a></p>
  </div>
  <script>window.location.href = "${escaped}";</script>
</body>
</html>`;
  }

  private buildStatusHtml(status: string, intentId: string): string {
    return `<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Payment status</title>
  <style>
    body { font-family: sans-serif; display: flex; align-items: center; justify-content: center; min-height: 100vh; margin: 0; background: #f5f5f5; }
    .card { background: #fff; border-radius: 8px; padding: 40px; text-align: center; box-shadow: 0 2px 8px rgba(0,0,0,.1); max-width: 400px; }
    .status { font-size: 1.1rem; font-weight: 600; color: #333; margin-bottom: 8px; }
    small { color: #888; }
  </style>
</head>
<body>
  <div class="card">
    <div class="status">${status}</div>
    <small>Intent: ${intentId}</small>
  </div>
</body>
</html>`;
  }

  private buildErrorHtml(message: string): string {
    return `<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Payment error</title>
  <style>
    body { font-family: sans-serif; display: flex; align-items: center; justify-content: center; min-height: 100vh; margin: 0; background: #f5f5f5; }
    .card { background: #fff; border-radius: 8px; padding: 40px; text-align: center; box-shadow: 0 2px 8px rgba(0,0,0,.1); max-width: 400px; }
    .error { color: #d32f2f; font-weight: 600; margin-bottom: 8px; }
    p { color: #555; }
  </style>
</head>
<body>
  <div class="card">
    <div class="error">Payment could not be initiated</div>
    <p>${message}</p>
  </div>
</body>
</html>`;
  }
}
