import {
  Body,
  Controller,
  Get,
  Headers,
  Param,
  Patch,
  Post,
  RawBodyRequest,
  Req,
  Res,
} from '@nestjs/common';
import { Request, Response } from 'express';
import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';
import { IsNumber, IsOptional, IsString, IsUrl } from 'class-validator';
import { PaymentsService } from '../services/payments.service';
import { RecordPaymentDto } from '../dto/record-payment.dto';
import { ConfirmPaymentDto } from '../dto/confirm-payment.dto';
import { CurrentUser, AuthenticatedUser } from '../../../common/decorators/current-user.decorator';
import { Roles } from '../../../common/decorators/roles.decorator';
import { Public } from '../../../common/decorators/public.decorator';

class InitiateOnlinePaymentDto {
  @IsNumber() amount!: number;
  @IsOptional() @IsString() currency?: string;
  @IsOptional() @IsString() providerCode?: string;
  @IsOptional() @IsUrl({ require_tld: false }) successUrl?: string;
  @IsOptional() @IsUrl({ require_tld: false }) failureUrl?: string;
}

@ApiTags('Finance — Payments')
@ApiBearerAuth()
@Controller()
export class PaymentsController {
  constructor(private readonly payments: PaymentsService) {}

  @Post('fund-call-items/:id/payments')
  @ApiOperation({
    summary:
      'Record offline payment. Launch configuration only accepts BANK_TRANSFER (virement bancaire).',
  })
  record(
    @Param('id') itemId: string,
    @Body() dto: RecordPaymentDto,
    @CurrentUser() user: AuthenticatedUser,
  ) {
    return this.payments.record(itemId, dto, user);
  }

  @Post('fund-call-items/:id/bank-transfers')
  @ApiOperation({
    summary:
      'Record manual bank transfer payment. Production launch endpoint for BANK_TRANSFER only.',
  })
  recordBankTransfer(
    @Param('id') itemId: string,
    @Body() dto: RecordPaymentDto,
    @CurrentUser() user: AuthenticatedUser,
  ) {
    return this.payments.record(itemId, { ...dto, method: 'BANK_TRANSFER' }, user);
  }

  @Get('fund-call-items/:id/payments')
  list(@Param('id') itemId: string, @CurrentUser() user: AuthenticatedUser) {
    return this.payments.listForItem(itemId, user);
  }

  @Get('payments/:id/receipt.pdf')
  async receipt(
    @Param('id') id: string,
    @CurrentUser() user: AuthenticatedUser,
    @Res() res: Response,
  ) {
    const pdf = await this.payments.generateReceiptPdf(id, user);
    if (!pdf) {
      res.status(404).send('Payment not found or not yet paid');
      return;
    }
    res
      .status(200)
      .type('application/pdf')
      .set('Content-Disposition', `inline; filename=receipt-${id}.pdf`)
      .send(pdf);
  }

  @Post('fund-call-items/:id/online-payments')
  @ApiOperation({ summary: 'Start online (CARD) payment — returns checkoutUrl for redirect' })
  initiateOnline(
    @Param('id') itemId: string,
    @Body() dto: InitiateOnlinePaymentDto,
    @CurrentUser() user: AuthenticatedUser,
  ) {
    return this.payments.initiateOnlinePayment(itemId, dto, user);
  }

  @Roles('SYNDIC', 'SUPERADMIN')
  @Patch('payments/:id/confirm')
  @ApiOperation({ summary: 'Manual confirm (testing / back-office fallback)' })
  confirm(@Param('id') id: string, @Body() dto: ConfirmPaymentDto, @CurrentUser() user: AuthenticatedUser) {
    return this.payments.confirmCardPayment(id, dto.reference, user);
  }

  /**
   * Webhook endpoints — called server-to-server by payment provider.
   * Public (no JWT) since caller is the provider, not the user.
   * Real implementation must verify HMAC signature inside the provider.
   */
  @Public()
  @Post('payments/webhooks/:provider')
  @ApiOperation({ summary: 'Provider webhook (YouCan Pay / Stub)' })
  async webhook(
    @Param('provider') provider: string,
    @Req() req: RawBodyRequest<Request>,
    @Headers() headers: Record<string, string>,
  ) {
    const raw = req.rawBody?.toString('utf8') ?? JSON.stringify(req.body);
    return this.payments.handleWebhook(provider.toUpperCase(), raw, headers);
  }

  /**
   * Stub-provider hosted checkout page — for local dev only.
   * Auto-confirms the payment by posting to the webhook.
   */
  @Public()
  @Get('payments/stub-checkout/:token')
  @ApiOperation({ summary: '[DEV] Stub provider hosted checkout — auto-confirms' })
  async stubCheckout(@Param('token') token: string, @Res() res: Response) {
    const result = await this.payments.confirmStubCheckout(token);
    if (!result) {
      res.status(404).send('Unknown stub checkout token');
      return;
    }
    res
      .status(200)
      .type('html')
      .send(
        `<html><body style="font-family:sans-serif"><h2>✅ Paiement confirmé (stub)</h2><p>Token: ${token}</p></body></html>`,
      );
  }
}
