import {
  Controller,
  Get,
  Post,
  Patch,
  Body,
  Param,
  Query,
  Res,
  UseGuards,
  ParseUUIDPipe,
  NotFoundException,
} from '@nestjs/common';
import type { Response } from 'express';
import { JwtAuthGuard } from '../../auth/guards/jwt-auth.guard';
import { PermissionGuard } from '../../auth/guards/permission.guard';
import { Permissions } from '../../auth/decorators/permissions.decorator';
import { AccessLevel, ModuleName } from '../../entities/role-permission.entity';
import { paginatedResponse, successResponse } from '../../common/responses/api-response';
import { PaymentSettlementService } from './payment-settlement.service';
import {
  RecordPaymentDto,
  SettlementQueryDto,
  VoidPaymentDto,
  EditSettlementAmountDto,
  AppendNoteDto,
  MasterListQueryDto,
  PlaceHoldDto,
  SettlementAnalyticsQueryDto,
  SettlementByServiceQueryDto,
  CollectPaymentDto,
  CollectForMdDto,
  CustomerOutstandingQueryDto,
  CustomerBalanceQueryDto,
  SourceOutstandingQueryDto,
  SupplierOutstandingQueryDto,
  PaySupplierDto,
  CustomerSearchQueryDto,
  PaymentHistoryQueryDto,
  UpdatePaymentHistoryDto,
} from './dto';
import { BookingCashCollectionService } from '../service-payment-collection/booking-cash-collection.service';

/**
 * §12 Payment Settlement REST surface.
 *
 *   GET    /settlements                   — paginated list + filters
 *   GET    /settlements/stats             — pending / settled buckets per type
 *   GET    /settlements/:id               — detail (settlement + ledger)
 *   POST   /settlements/:id/payments      — Settle action (partial or full)
 *
 * Stats route is registered BEFORE the parametric `:id` so Nest matches
 * it first. Access keyed on the dedicated PAYMENT_SETTLEMENT module:
 *   VIEW = list / detail / stats
 *   EDIT = record a payment (Settle action)
 *
 * Settlement edit-amount + append-notes endpoints are deferred — the
 * detail page can be wired in Phase E and these admin actions added
 * once the visible flows are validated by ops.
 */
@Controller('settlements')
@UseGuards(JwtAuthGuard, PermissionGuard)
export class PaymentSettlementController {
  constructor(
    private readonly service: PaymentSettlementService,
    private readonly cashCollections: BookingCashCollectionService,
  ) {}

  @Get('stats')
  @Permissions({ module: ModuleName.PAYMENT_SETTLEMENT, accessLevel: AccessLevel.VIEW })
  async stats(
    @Query('from') from?: string,
    @Query('to') to?: string,
    @Query('destination_id', new ParseUUIDPipe({ optional: true })) destinationId?: string,
  ) {
    const data = await this.service.stats(from, to, destinationId);
    return successResponse(data, 'Settlement stats fetched successfully');
  }

  /**
   * Phase H — Excel export of the current filter set. Streams as a
   * .xlsx attachment. Same VIEW perm as the list — anyone who can see
   * a row can export it.
   */
  @Get('export')
  @Permissions({ module: ModuleName.PAYMENT_SETTLEMENT, accessLevel: AccessLevel.VIEW })
  async exportExcel(@Query() query: SettlementQueryDto, @Res() res: Response) {
    const buffer = await this.service.exportToExcel(query);
    res.setHeader(
      'Content-Type',
      'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
    );
    res.setHeader('Content-Disposition', 'attachment; filename="settlements.xlsx"');
    res.send(buffer);
  }

  // ── Analytical views ────────────────────────────────────────────────
  // Same fact table, three different GROUP BY lenses. All read-only.
  // Registered BEFORE the parametric `/masters/:id` and `/:id` routes
  // so Nest matches the literal `/by-...` prefix first. VIEW perm —
  // anyone who can see settlements can run the rollup.

  /**
   * By Supplier — accounts payable: one row per supplier with
   * total owed / paid / outstanding + oldest unsettled booking.
   * Sorted by outstanding DESC so the biggest payables float up.
   */
  @Get('by-supplier')
  @Permissions({ module: ModuleName.PAYMENT_SETTLEMENT, accessLevel: AccessLevel.VIEW })
  async aggregateBySupplier(@Query() query: SettlementAnalyticsQueryDto) {
    const { items, meta } = await this.service.aggregateBySupplier({
      search: query.search,
      date_from: query.date_from,
      date_to: query.date_to,
      page: query.page,
      limit: query.limit,
    });
    return paginatedResponse(items, meta, 'Settlements by supplier');
  }

  /**
   * By Sales Agent — commission base view: one row per agent with
   * bookings/billed/collected/realised-margin. Date-window filterable.
   */
  @Get('by-agent')
  @Permissions({ module: ModuleName.PAYMENT_SETTLEMENT, accessLevel: AccessLevel.VIEW })
  async aggregateByAgent(@Query() query: SettlementAnalyticsQueryDto) {
    const { items, meta } = await this.service.aggregateByAgent({
      search: query.search,
      date_from: query.date_from,
      date_to: query.date_to,
      page: query.page,
      limit: query.limit,
    });
    return paginatedResponse(items, meta, 'Settlements by sales agent');
  }

  /**
   * By Trip Source — one row per external "agent" the booking
   * originated from (TravClan, MakeMyTrip, walk-in office, …).
   * Powers the All Settlements "By Agent" tab per §12f.
   */
  @Get('by-source')
  @Permissions({ module: ModuleName.PAYMENT_SETTLEMENT, accessLevel: AccessLevel.VIEW })
  async aggregateBySource(@Query() query: SettlementAnalyticsQueryDto) {
    const { items, meta } = await this.service.aggregateBySource({
      search: query.search,
      date_from: query.date_from,
      date_to: query.date_to,
      page: query.page,
      limit: query.limit,
    });
    return paginatedResponse(items, meta, 'Settlements by trip source');
  }

  /**
   * By Service-line — product P&L: one row per transport/activity/hotel
   * master with bookings sold, selling, supplier cost, margin, margin %.
   * Sorted by total_selling DESC so revenue leaders float up.
   */
  @Get('by-service')
  @Permissions({ module: ModuleName.PAYMENT_SETTLEMENT, accessLevel: AccessLevel.VIEW })
  async aggregateByService(@Query() query: SettlementByServiceQueryDto) {
    const { items, meta } = await this.service.aggregateByService({
      destination_id: query.destination_id,
      date_from: query.date_from,
      date_to: query.date_to,
      page: query.page,
      limit: query.limit,
    });
    return paginatedResponse(items, meta, 'Settlements by service');
  }

  // ── Phase J — two-tier model: master endpoints ──────────────────────
  // Registered BEFORE the parametric `:id` route so Nest matches the
  // literal `/masters` path first. All masters endpoints are scoped to
  // the same PAYMENT_SETTLEMENT module: VIEW for read, DELETE
  // (Admin-only by seed) for hold / release.

  /**
   * Master list — grouped-by-booking Settlements view. Returns rolled-up
   * status + totals per (booking × party × supplier_id) tuple.
   */
  @Get('masters')
  @Permissions({ module: ModuleName.PAYMENT_SETTLEMENT, accessLevel: AccessLevel.VIEW })
  async findMasters(@Query() query: MasterListQueryDto) {
    const { items, meta } = await this.service.findMasterPaginated(query);
    return paginatedResponse(items, meta, 'Settlement masters fetched successfully');
  }

  /**
   * Master detail — master row + active children (timeline).
   */
  @Get('masters/:id')
  @Permissions({ module: ModuleName.PAYMENT_SETTLEMENT, accessLevel: AccessLevel.VIEW })
  async findMasterById(@Param('id', ParseUUIDPipe) id: string) {
    const data = await this.service.findMasterDetail(id);
    if (!data) throw new NotFoundException('Settlement master not found');
    return successResponse(data, 'Settlement master fetched successfully');
  }

  /**
   * Admin places a master on HOLD. Pauses auto-delta creation; requires
   * a mandatory reason (audit + DB CHECK constraint).
   */
  @Post('masters/:id/hold')
  @Permissions({ module: ModuleName.PAYMENT_SETTLEMENT, accessLevel: AccessLevel.DELETE })
  async placeOnHold(@Param('id', ParseUUIDPipe) id: string, @Body() dto: PlaceHoldDto) {
    const result = await this.service.placeOnHold(id, dto.reason);
    return successResponse(result, 'Settlement master placed on hold');
  }

  /**
   * Admin releases a HOLD master. Recomputes status from children.
   * Deltas that should have landed while held are NOT back-filled.
   */
  @Post('masters/:id/release')
  @Permissions({ module: ModuleName.PAYMENT_SETTLEMENT, accessLevel: AccessLevel.DELETE })
  async releaseHold(@Param('id', ParseUUIDPipe) id: string) {
    const result = await this.service.releaseHold(id);
    return successResponse(result, 'Settlement master hold released');
  }

  // ── Payment Collection (Phase A) ────────────────────────────────
  // Routes deliberately registered BEFORE the `:id` parametric so
  // Nest's matcher hits them first. `customer-outstanding` feeds the
  // grid on the Collection screen; `collect` is the screen's submit;
  // `collections/:id` returns the receipt detail for the print view.

  @Get('customer-outstanding')
  @Permissions({ module: ModuleName.PAYMENT_SETTLEMENT, accessLevel: AccessLevel.VIEW })
  async customerOutstanding(@Query() q: CustomerOutstandingQueryDto) {
    const data = await this.service.findCustomerOutstanding({
      mobile: q.mobile,
      currency_id: q.currency_id,
      exclude_collection_id: q.exclude_collection_id,
    });
    return successResponse(data, 'Customer outstanding fetched successfully');
  }

  @Get('customer-balance')
  @Permissions({ module: ModuleName.PAYMENT_SETTLEMENT, accessLevel: AccessLevel.VIEW })
  async customerBalance(@Query() q: CustomerBalanceQueryDto) {
    const data = await this.service.findCustomerBalance({
      mobile: q.mobile,
      currency_id: q.currency_id,
    });
    return successResponse(data, 'Customer balance fetched successfully');
  }

  // §12d — Typed-as-you-go autocomplete on the Customer field. Returns
  // distinct (name, mobile, email) triples so the FE can pre-fill
  // hidden mobile/email state on select and reuse the existing
  // mobile-keyed outstanding/balance endpoints below.
  @Get('customer-search')
  @Permissions({ module: ModuleName.PAYMENT_SETTLEMENT, accessLevel: AccessLevel.VIEW })
  async customerSearch(@Query() q: CustomerSearchQueryDto) {
    const data = await this.service.searchCustomersByName({ q: q.q });
    return successResponse(data, 'Customer search results');
  }

  // §12d Agent mode — same shape as customer-outstanding/balance, but
  // keyed on the trip-source id. Powers the Party=SOURCE branch of
  // the Payment Collection screen. Listed before `:id` so the matcher
  // doesn't capture "source-outstanding" as a settlement uuid.

  @Get('source-outstanding')
  @Permissions({ module: ModuleName.PAYMENT_SETTLEMENT, accessLevel: AccessLevel.VIEW })
  async sourceOutstanding(@Query() q: SourceOutstandingQueryDto) {
    const data = await this.service.findSourceOutstanding({
      source_id: q.source_id,
      currency_id: q.currency_id,
      exclude_collection_id: q.exclude_collection_id,
    });
    return successResponse(data, 'Source outstanding fetched successfully');
  }

  @Get('source-balance')
  @Permissions({ module: ModuleName.PAYMENT_SETTLEMENT, accessLevel: AccessLevel.VIEW })
  async sourceBalance(@Query() q: SourceOutstandingQueryDto) {
    const data = await this.service.findSourceBalance({
      source_id: q.source_id,
      currency_id: q.currency_id,
    });
    return successResponse(data, 'Source balance fetched successfully');
  }

  // §12e Supplier mode — feeds the upcoming Pay Supplier screen.
  // Same row shape as the customer/source endpoints so the FE can
  // share a rendering component.

  @Get('supplier-outstanding')
  @Permissions({ module: ModuleName.PAYMENT_SETTLEMENT, accessLevel: AccessLevel.VIEW })
  async supplierOutstanding(@Query() q: SupplierOutstandingQueryDto) {
    const data = await this.service.findSupplierOutstanding({
      supplier_id: q.supplier_id,
      currency_id: q.currency_id,
    });
    return successResponse(data, 'Supplier outstanding fetched successfully');
  }

  @Get('supplier-balance')
  @Permissions({ module: ModuleName.PAYMENT_SETTLEMENT, accessLevel: AccessLevel.VIEW })
  async supplierBalance(@Query() q: SupplierOutstandingQueryDto) {
    const data = await this.service.findSupplierBalance({
      supplier_id: q.supplier_id,
      currency_id: q.currency_id,
    });
    return successResponse(data, 'Supplier balance fetched successfully');
  }

  // §12e Pay Supplier — supplier-side mirror of POST /collect. EDIT
  // permission so Ops Manager can record payouts; voiding stays at
  // DELETE (handled via the existing per-payment void route).
  @Post('pay-supplier')
  @Permissions({ module: ModuleName.PAYMENT_SETTLEMENT, accessLevel: AccessLevel.EDIT })
  async paySupplier(@Body() dto: PaySupplierDto) {
    const result = await this.service.paySupplier({
      supplier_id: dto.supplier_id,
      supplier_name: dto.supplier_name ?? null,
      payment_date: dto.payment_date,
      payment_type: dto.payment_type ?? null,
      mode: dto.mode,
      ref_no: dto.ref_no ?? null,
      debit_bank_account_id: dto.debit_bank_account_id ?? null,
      credit_bank_account_id: dto.credit_bank_account_id ?? null,
      currency_id: dto.currency_id,
      paid_amount: dto.paid_amount,
      credit_applied: dto.credit_applied ?? null,
      allocations: dto.allocations,
      excess_treatment: dto.excess_treatment ?? null,
      notes: dto.notes ?? null,
    });
    return successResponse(result, 'Supplier payout recorded');
  }

  @Get('payouts/:id')
  @Permissions({ module: ModuleName.PAYMENT_SETTLEMENT, accessLevel: AccessLevel.VIEW })
  async findPayout(@Param('id', ParseUUIDPipe) id: string) {
    const data = await this.service.findPayoutDetail(id);
    if (!data) throw new NotFoundException('Payout not found');
    return successResponse(data, 'Payout fetched successfully');
  }

  @Post('collect')
  @Permissions({ module: ModuleName.PAYMENT_SETTLEMENT, accessLevel: AccessLevel.EDIT })
  async collect(@Body() dto: CollectPaymentDto) {
    const result = await this.service.collectPayment({
      party_type: dto.party_type,
      customer_mobile: dto.customer_mobile ?? null,
      source_id: dto.source_id ?? null,
      customer_name: dto.customer_name ?? null,
      payment_date: dto.payment_date,
      payment_type: dto.payment_type ?? null,
      mode: dto.mode,
      ref_no: dto.ref_no ?? null,
      debit_bank_account_id: dto.debit_bank_account_id ?? null,
      credit_bank_account_id: dto.credit_bank_account_id ?? null,
      currency_id: dto.currency_id,
      received_amount: dto.received_amount,
      receive_currency_id: dto.receive_currency_id ?? null,
      exchange_rate: dto.exchange_rate ?? null,
      exchange_rate_quote: dto.exchange_rate_quote ?? null,
      received_amount_receive_currency: dto.received_amount_receive_currency ?? null,
      roe: dto.roe ?? null,
      credit_applied: dto.credit_applied ?? null,
      allocations: dto.allocations,
      excess_treatment: dto.excess_treatment ?? null,
      notes: dto.notes ?? null,
      collected_by_type: dto.collected_by_type ?? null,
      collector_name: dto.collector_name ?? null,
      collector_supplier_id: dto.collector_supplier_id ?? null,
      collector_driver_id: dto.collector_driver_id ?? null,
    });
    return successResponse(result, 'Payment collected and allocated');
  }

  @Post('collect-for-md')
  @Permissions({ module: ModuleName.PAYMENT_SETTLEMENT, accessLevel: AccessLevel.EDIT })
  async collectForMd(@Body() dto: CollectForMdDto) {
    const data = await this.cashCollections.stampFromCollect(dto);
    return successResponse(data, 'Cash collection stamped on Movement Dashboard');
  }

  @Get('booking-transports/:bookingId')
  @Permissions({ module: ModuleName.PAYMENT_SETTLEMENT, accessLevel: AccessLevel.VIEW })
  async bookingTransports(@Param('bookingId', ParseUUIDPipe) bookingId: string) {
    const data = await this.cashCollections.listBookingTransports(bookingId);
    return successResponse(data, 'Booking transports fetched');
  }

  @Get('payment-history')
  @Permissions({ module: ModuleName.PAYMENT_SETTLEMENT, accessLevel: AccessLevel.VIEW })
  async paymentHistory(@Query() query: PaymentHistoryQueryDto) {
    const result =
      query.kind === 'MD'
        ? await this.cashCollections.listPaymentHistory(query)
        : await this.service.listOfficePaymentHistory(query);
    return paginatedResponse(result.items, result.meta, 'Payment history fetched successfully');
  }

  @Get('payment-history/:id')
  @Permissions({ module: ModuleName.PAYMENT_SETTLEMENT, accessLevel: AccessLevel.VIEW })
  async paymentHistoryDetail(
    @Param('id', ParseUUIDPipe) id: string,
    @Query('kind') kind?: string,
  ) {
    const data =
      kind === 'MD'
        ? await this.cashCollections.findPaymentHistoryDetail(id)
        : await this.service.findOfficeHistoryDetail(id);
    if (!data) throw new NotFoundException('Payment history not found');
    return successResponse(data, 'Payment history fetched successfully');
  }

  @Patch('payment-history/:id')
  @Permissions({ module: ModuleName.PAYMENT_SETTLEMENT, accessLevel: AccessLevel.EDIT })
  async updatePaymentHistory(
    @Param('id', ParseUUIDPipe) id: string,
    @Body() dto: UpdatePaymentHistoryDto,
  ) {
    const data =
      dto.kind === 'MD'
        ? await this.cashCollections.updateFromCollect(id, dto)
        : await this.service.updateCollection(id, {
            payment_date: dto.payment_date,
            mode: dto.mode,
            ref_no: dto.ref_no ?? null,
            debit_bank_account_id: dto.debit_bank_account_id ?? null,
            credit_bank_account_id: dto.credit_bank_account_id ?? null,
            currency_id: dto.currency_id,
            received_amount: dto.received_amount,
            receive_currency_id: dto.receive_currency_id ?? null,
            exchange_rate: dto.exchange_rate ?? null,
            exchange_rate_quote: dto.exchange_rate_quote ?? null,
            received_amount_receive_currency: dto.received_amount_receive_currency ?? null,
            roe: dto.roe ?? null,
            credit_applied: dto.credit_applied ?? null,
            allocations: dto.allocations,
            excess_treatment: dto.excess_treatment ?? null,
            notes: dto.notes ?? null,
            collected_by_type: dto.collected_by_type ?? null,
            collector_name: dto.collector_name ?? null,
            collector_supplier_id: dto.collector_supplier_id ?? null,
            collector_driver_id: dto.collector_driver_id ?? null,
          });
    return successResponse(data, 'Payment updated');
  }

  @Get('collections/:id')
  @Permissions({ module: ModuleName.PAYMENT_SETTLEMENT, accessLevel: AccessLevel.VIEW })
  async findCollection(@Param('id', ParseUUIDPipe) id: string) {
    const data = await this.service.findCollectionDetail(id);
    if (!data) throw new NotFoundException('Collection not found');
    return successResponse(data, 'Collection fetched successfully');
  }

  @Get()
  @Permissions({ module: ModuleName.PAYMENT_SETTLEMENT, accessLevel: AccessLevel.VIEW })
  async findAll(@Query() query: SettlementQueryDto) {
    const { items, meta } = await this.service.findPaginated(query);
    return paginatedResponse(items, meta, 'Settlements fetched successfully');
  }

  @Get(':id')
  @Permissions({ module: ModuleName.PAYMENT_SETTLEMENT, accessLevel: AccessLevel.VIEW })
  async findById(@Param('id', ParseUUIDPipe) id: string) {
    const data = await this.service.findDetail(id);
    if (!data) throw new NotFoundException('Settlement not found');
    return successResponse(data, 'Settlement fetched successfully');
  }

  /**
   * Phase F — Admin-only void on a payment. Settle (POST .../payments)
   * stays open to EDIT-level so Ops Manager can record receipts, but
   * reversing one of those receipts is a finance-grade action gated
   * to DELETE on the payment-settlement module — DELETE is the highest
   * RBAC tier and is seeded only on Admin by the Phase A migration.
   */
  @Post(':id/payments/:paymentId/void')
  @Permissions({ module: ModuleName.PAYMENT_SETTLEMENT, accessLevel: AccessLevel.DELETE })
  async voidPayment(
    @Param('id', ParseUUIDPipe) _id: string,
    @Param('paymentId', ParseUUIDPipe) paymentId: string,
    @Body() dto: VoidPaymentDto,
  ) {
    const result = await this.service.voidPayment(paymentId, dto.reason);
    return successResponse(result, 'Payment voided');
  }

  /**
   * Phase H — Admin edit of settlement.amount. Same DELETE-level gate
   * as the void endpoint (Admin only by RBAC seed). Service enforces
   * the lower-bound guard against amount_settled.
   */
  @Patch(':id/amount')
  @Permissions({ module: ModuleName.PAYMENT_SETTLEMENT, accessLevel: AccessLevel.DELETE })
  async editAmount(@Param('id', ParseUUIDPipe) id: string, @Body() dto: EditSettlementAmountDto) {
    const result = await this.service.editAmount(id, dto.amount, dto.reason);
    return successResponse(result, 'Settlement amount updated');
  }

  /**
   * Phase H — append a note to a settlement. EDIT-level gate so
   * Ops Manager + Admin can both annotate; Sales Head with VIEW
   * stays read-only.
   */
  @Post(':id/notes')
  @Permissions({ module: ModuleName.PAYMENT_SETTLEMENT, accessLevel: AccessLevel.EDIT })
  async appendNote(@Param('id', ParseUUIDPipe) id: string, @Body() dto: AppendNoteDto) {
    const result = await this.service.appendNote(id, dto.text);
    return successResponse(result, 'Note added');
  }

  @Post(':id/payments')
  @Permissions({ module: ModuleName.PAYMENT_SETTLEMENT, accessLevel: AccessLevel.EDIT })
  async recordPayment(@Param('id', ParseUUIDPipe) id: string, @Body() dto: RecordPaymentDto) {
    const result = await this.service.recordPayment(id, {
      paid_amount: dto.paid_amount,
      paid_date: dto.paid_date,
      mode: dto.mode,
      ref_no: dto.ref_no ?? null,
      notes: dto.notes ?? null,
    });
    const settled = result.settlement.status;
    return successResponse(
      result,
      settled === 'SETTLED' ? 'Settlement marked as settled' : 'Partial payment recorded',
    );
  }
}
