import {
  Controller,
  Post,
  Get,
  Patch,
  Delete,
  Body,
  Param,
  Query,
  UseGuards,
  UseInterceptors,
  UploadedFiles,
  Request,
  BadRequestException,
  ForbiddenException,
  ParseIntPipe,
  DefaultValuePipe,
  Inject,
} from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { FilesInterceptor } from '@nestjs/platform-express';
import {
  ApiTags,
  ApiOperation,
  ApiResponse,
  ApiConsumes,
  ApiBody,
  ApiBearerAuth,
  ApiQuery,
  ApiParam,
} from '@nestjs/swagger';
import { JwtAuthGuard } from '../guards/jwt-auth.guard';
import { Public } from '../decorators/public.decorator';
import { CsvBookingService } from '../services/csv-booking.service';
import { SubscriptionService } from '../services/subscription.service';
import {
  ShipmentCounterPort,
  SHIPMENT_COUNTER_PORT,
} from '@domain/ports/out/shipment-counter.port';
import {
  OrganizationRepository,
  ORGANIZATION_REPOSITORY,
} from '@domain/ports/out/organization.repository';
import { ShipmentLimitExceededException } from '@domain/exceptions/shipment-limit-exceeded.exception';
import {
  CreateCsvBookingDto,
  CsvBookingResponseDto,
  CsvBookingListResponseDto,
  CsvBookingStatsDto,
} from '../dto/csv-booking.dto';

/**
 * CSV Bookings Controller
 *
 * Handles HTTP requests for CSV-based booking requests
 *
 * IMPORTANT: Route order matters in NestJS!
 * Static routes MUST come BEFORE parameterized routes.
 * Otherwise, `:id` will capture "stats", "organization", etc.
 */
@ApiTags('CSV Bookings')
@Controller('csv-bookings')
export class CsvBookingsController {
  constructor(
    private readonly csvBookingService: CsvBookingService,
    private readonly subscriptionService: SubscriptionService,
    private readonly configService: ConfigService,
    @Inject(SHIPMENT_COUNTER_PORT)
    private readonly shipmentCounter: ShipmentCounterPort,
    @Inject(ORGANIZATION_REPOSITORY)
    private readonly organizationRepository: OrganizationRepository
  ) {}

  // ============================================================================
  // STATIC ROUTES (must come FIRST)
  // ============================================================================

  /**
   * Create a new CSV booking request
   *
   * POST /api/v1/csv-bookings
   */
  @Post()
  @ApiBearerAuth()
  @UseInterceptors(FilesInterceptor('documents', 10))
  @ApiConsumes('multipart/form-data')
  @ApiOperation({
    summary: 'Create a new CSV booking request',
    description:
      'Creates a new booking request from CSV rate selection. Uploads documents, sends email to carrier, and creates a notification for the user.',
  })
  @ApiBody({
    schema: {
      type: 'object',
      required: [
        'carrierName',
        'carrierEmail',
        'origin',
        'destination',
        'volumeCBM',
        'weightKG',
        'palletCount',
        'priceUSD',
        'priceEUR',
        'primaryCurrency',
        'transitDays',
        'containerType',
      ],
      properties: {
        carrierName: { type: 'string', example: 'SSC Consolidation' },
        carrierEmail: { type: 'string', format: 'email', example: 'bookings@sscconsolidation.com' },
        origin: { type: 'string', example: 'NLRTM' },
        destination: { type: 'string', example: 'USNYC' },
        volumeCBM: { type: 'number', example: 25.5 },
        weightKG: { type: 'number', example: 3500 },
        palletCount: { type: 'number', example: 10 },
        priceUSD: { type: 'number', example: 1850.5 },
        priceEUR: { type: 'number', example: 1665.45 },
        primaryCurrency: { type: 'string', enum: ['USD', 'EUR'], example: 'USD' },
        transitDays: { type: 'number', example: 28 },
        containerType: { type: 'string', example: 'LCL' },
        notes: { type: 'string', example: 'Handle with care' },
        documents: {
          type: 'array',
          items: { type: 'string', format: 'binary' },
          description: 'Shipping documents (Bill of Lading, Packing List, Invoice, etc.)',
        },
      },
    },
  })
  @ApiResponse({
    status: 201,
    description: 'Booking created successfully',
    type: CsvBookingResponseDto,
  })
  @ApiResponse({ status: 400, description: 'Invalid request data or missing documents' })
  @ApiResponse({ status: 401, description: 'Unauthorized' })
  async createBooking(
    @Body() dto: CreateCsvBookingDto,
    @UploadedFiles() files: Express.Multer.File[],
    @Request() req: any
  ): Promise<CsvBookingResponseDto> {
    // Debug: Log request details
    console.log('=== CSV Booking Request Debug ===');
    console.log('req.user:', req.user);
    console.log('req.body:', req.body);
    console.log('dto:', dto);
    console.log('files:', files?.length);
    console.log('================================');

    if (!files || files.length === 0) {
      throw new BadRequestException('At least one document is required');
    }

    // Validate user authentication
    if (!req.user || !req.user.id) {
      throw new BadRequestException('User authentication failed - no user info in request');
    }

    if (!req.user.organizationId) {
      throw new BadRequestException('Organization ID is required');
    }

    const userId = req.user.id;
    const organizationId = req.user.organizationId;

    // ADMIN users bypass shipment limits
    if (req.user.role !== 'ADMIN') {
      // Check shipment limit (Bronze plan = 12/year)
      const subscription = await this.subscriptionService.getOrCreateSubscription(organizationId);
      const maxShipments = subscription.plan.maxShipmentsPerYear;
      if (maxShipments !== -1) {
        const currentYear = new Date().getFullYear();
        const count = await this.shipmentCounter.countShipmentsForOrganizationInYear(
          organizationId,
          currentYear
        );
        if (count >= maxShipments) {
          throw new ShipmentLimitExceededException(organizationId, count, maxShipments);
        }
      }
    }

    // Convert string values to numbers (multipart/form-data sends everything as strings)
    const sanitizedDto: CreateCsvBookingDto = {
      ...dto,
      volumeCBM: typeof dto.volumeCBM === 'string' ? parseFloat(dto.volumeCBM) : dto.volumeCBM,
      weightKG: typeof dto.weightKG === 'string' ? parseFloat(dto.weightKG) : dto.weightKG,
      palletCount:
        typeof dto.palletCount === 'string' ? parseInt(dto.palletCount, 10) : dto.palletCount,
      priceUSD: typeof dto.priceUSD === 'string' ? parseFloat(dto.priceUSD) : dto.priceUSD,
      priceEUR: typeof dto.priceEUR === 'string' ? parseFloat(dto.priceEUR) : dto.priceEUR,
      transitDays:
        typeof dto.transitDays === 'string' ? parseInt(dto.transitDays, 10) : dto.transitDays,
    };

    return await this.csvBookingService.createBooking(sanitizedDto, files, userId, organizationId);
  }

  /**
   * Get current user's bookings (paginated)
   *
   * GET /api/v1/csv-bookings
   */
  @Get()
  @UseGuards(JwtAuthGuard)
  @ApiBearerAuth()
  @ApiOperation({
    summary: 'Get user bookings',
    description: 'Retrieve all bookings for the authenticated user with pagination.',
  })
  @ApiQuery({ name: 'page', required: false, type: Number, example: 1 })
  @ApiQuery({ name: 'limit', required: false, type: Number, example: 10 })
  @ApiResponse({
    status: 200,
    description: 'Bookings retrieved successfully',
    type: CsvBookingListResponseDto,
  })
  @ApiResponse({ status: 401, description: 'Unauthorized' })
  async getUserBookings(
    @Request() req: any,
    @Query('page', new DefaultValuePipe(1), ParseIntPipe) page: number,
    @Query('limit', new DefaultValuePipe(10), ParseIntPipe) limit: number
  ): Promise<CsvBookingListResponseDto> {
    const userId = req.user.id;
    return await this.csvBookingService.getUserBookings(userId, page, limit);
  }

  /**
   * Get booking statistics for user
   *
   * GET /api/v1/csv-bookings/stats/me
   */
  @Get('stats/me')
  @UseGuards(JwtAuthGuard)
  @ApiBearerAuth()
  @ApiOperation({
    summary: 'Get user booking statistics',
    description:
      'Get aggregated statistics for the authenticated user (pending, accepted, rejected, cancelled).',
  })
  @ApiResponse({
    status: 200,
    description: 'Statistics retrieved successfully',
    type: CsvBookingStatsDto,
  })
  @ApiResponse({ status: 401, description: 'Unauthorized' })
  async getUserStats(@Request() req: any): Promise<CsvBookingStatsDto> {
    const userId = req.user.id;
    return await this.csvBookingService.getUserStats(userId);
  }

  /**
   * Get organization booking statistics
   *
   * GET /api/v1/csv-bookings/stats/organization
   */
  @Get('stats/organization')
  @UseGuards(JwtAuthGuard)
  @ApiBearerAuth()
  @ApiOperation({
    summary: 'Get organization booking statistics',
    description: "Get aggregated statistics for the user's organization. For managers/admins.",
  })
  @ApiResponse({
    status: 200,
    description: 'Statistics retrieved successfully',
    type: CsvBookingStatsDto,
  })
  @ApiResponse({ status: 401, description: 'Unauthorized' })
  async getOrganizationStats(@Request() req: any): Promise<CsvBookingStatsDto> {
    const organizationId = req.user.organizationId;
    return await this.csvBookingService.getOrganizationStats(organizationId);
  }

  /**
   * Get organization bookings (for managers/admins)
   *
   * GET /api/v1/csv-bookings/organization/all
   */
  @Get('organization/all')
  @UseGuards(JwtAuthGuard)
  @ApiBearerAuth()
  @ApiOperation({
    summary: 'Get organization bookings',
    description:
      "Retrieve all bookings for the user's organization with pagination. For managers/admins.",
  })
  @ApiQuery({ name: 'page', required: false, type: Number, example: 1 })
  @ApiQuery({ name: 'limit', required: false, type: Number, example: 10 })
  @ApiResponse({
    status: 200,
    description: 'Organization bookings retrieved successfully',
    type: CsvBookingListResponseDto,
  })
  @ApiResponse({ status: 401, description: 'Unauthorized' })
  async getOrganizationBookings(
    @Request() req: any,
    @Query('page', new DefaultValuePipe(1), ParseIntPipe) page: number,
    @Query('limit', new DefaultValuePipe(10), ParseIntPipe) limit: number
  ): Promise<CsvBookingListResponseDto> {
    const organizationId = req.user.organizationId;
    return await this.csvBookingService.getOrganizationBookings(organizationId, page, limit);
  }

  /**
   * Accept a booking request (PUBLIC - token-based)
   *
   * GET /api/v1/csv-bookings/accept/:token
   */
  @Public()
  @Get('accept/:token')
  @ApiOperation({
    summary: 'Accept booking request (public)',
    description:
      'Public endpoint for carriers to accept a booking via email link. Updates booking status and notifies the user.',
  })
  @ApiParam({ name: 'token', description: 'Booking confirmation token (UUID)' })
  @ApiResponse({
    status: 200,
    description: 'Booking accepted successfully.',
  })
  @ApiResponse({ status: 404, description: 'Booking not found or invalid token' })
  @ApiResponse({
    status: 400,
    description: 'Booking cannot be accepted (invalid status or expired)',
  })
  async acceptBooking(@Param('token') token: string) {
    // Accept the booking
    const booking = await this.csvBookingService.acceptBooking(token);

    // Return simple success response
    return {
      success: true,
      bookingId: booking.id,
      action: 'accepted',
    };
  }

  /**
   * Reject a booking request (PUBLIC - token-based)
   *
   * GET /api/v1/csv-bookings/reject/:token
   */
  @Public()
  @Get('reject/:token')
  @ApiOperation({
    summary: 'Reject booking request (public)',
    description:
      'Public endpoint for carriers to reject a booking via email link. Updates booking status and notifies the user.',
  })
  @ApiParam({ name: 'token', description: 'Booking confirmation token (UUID)' })
  @ApiQuery({
    name: 'reason',
    required: false,
    description: 'Rejection reason',
    example: 'No capacity available',
  })
  @ApiResponse({
    status: 200,
    description: 'Booking rejected successfully.',
  })
  @ApiResponse({ status: 404, description: 'Booking not found or invalid token' })
  @ApiResponse({
    status: 400,
    description: 'Booking cannot be rejected (invalid status or expired)',
  })
  async rejectBooking(@Param('token') token: string, @Query('reason') reason: string) {
    // Reject the booking
    const booking = await this.csvBookingService.rejectBooking(token, reason);

    // Return simple success response
    return {
      success: true,
      bookingId: booking.id,
      action: 'rejected',
      reason: reason || null,
    };
  }

  /**
   * Create Stripe Checkout session for commission payment
   *
   * POST /api/v1/csv-bookings/:id/pay
   */
  @Post(':id/pay')
  @UseGuards(JwtAuthGuard)
  @ApiBearerAuth()
  @ApiOperation({
    summary: 'Pay commission for a booking',
    description:
      'Creates a Stripe Checkout session for the commission payment. Returns the Stripe session URL to redirect the user to.',
  })
  @ApiParam({ name: 'id', description: 'Booking ID (UUID)' })
  @ApiResponse({
    status: 200,
    description: 'Stripe checkout session created',
    schema: {
      type: 'object',
      properties: {
        sessionUrl: { type: 'string' },
        sessionId: { type: 'string' },
        commissionAmountEur: { type: 'number' },
      },
    },
  })
  @ApiResponse({ status: 400, description: 'Booking not in PENDING_PAYMENT status' })
  @ApiResponse({ status: 404, description: 'Booking not found' })
  async payCommission(@Param('id') id: string, @Request() req: any) {
    const userId = req.user.id;
    const userEmail = req.user.email;
    const organizationId = req.user.organizationId;
    const frontendUrl = this.configService.get<string>('FRONTEND_URL') || 'http://localhost:3000';

    // ADMIN users bypass SIRET verification
    if (req.user.role !== 'ADMIN') {
      // SIRET verification gate: organization must have a verified SIRET before paying
      const organization = await this.organizationRepository.findById(organizationId);
      if (!organization || !organization.siretVerified) {
        throw new ForbiddenException(
          'Le numero SIRET de votre organisation doit etre verifie par un administrateur avant de pouvoir effectuer un paiement. Contactez votre administrateur.'
        );
      }
    }

    return await this.csvBookingService.createCommissionPayment(id, userId, userEmail, frontendUrl);
  }

  /**
   * Confirm commission payment after Stripe redirect
   *
   * POST /api/v1/csv-bookings/:id/confirm-payment
   */
  @Post(':id/confirm-payment')
  @UseGuards(JwtAuthGuard)
  @ApiBearerAuth()
  @ApiOperation({
    summary: 'Confirm commission payment',
    description:
      'Called after Stripe payment success. Verifies the payment, updates booking to PENDING, sends email to carrier.',
  })
  @ApiParam({ name: 'id', description: 'Booking ID (UUID)' })
  @ApiBody({
    schema: {
      type: 'object',
      required: ['sessionId'],
      properties: {
        sessionId: { type: 'string', description: 'Stripe Checkout session ID' },
      },
    },
  })
  @ApiResponse({
    status: 200,
    description: 'Payment confirmed, booking activated',
    type: CsvBookingResponseDto,
  })
  @ApiResponse({ status: 400, description: 'Payment not completed or session mismatch' })
  @ApiResponse({ status: 404, description: 'Booking not found' })
  async confirmPayment(
    @Param('id') id: string,
    @Body('sessionId') sessionId: string,
    @Request() req: any
  ): Promise<CsvBookingResponseDto> {
    if (!sessionId) {
      throw new BadRequestException('sessionId is required');
    }

    const userId = req.user.id;
    return await this.csvBookingService.confirmCommissionPayment(id, sessionId, userId);
  }

  /**
   * Declare bank transfer — user confirms they have sent the wire transfer
   *
   * POST /api/v1/csv-bookings/:id/declare-transfer
   */
  @Post(':id/declare-transfer')
  @UseGuards(JwtAuthGuard)
  @ApiBearerAuth()
  @ApiOperation({
    summary: 'Declare bank transfer',
    description:
      'User confirms they have sent the bank wire transfer. Transitions booking to PENDING_BANK_TRANSFER awaiting admin validation.',
  })
  @ApiParam({ name: 'id', description: 'Booking ID (UUID)' })
  @ApiResponse({
    status: 200,
    description: 'Bank transfer declared, booking awaiting admin validation',
    type: CsvBookingResponseDto,
  })
  @ApiResponse({ status: 400, description: 'Booking not in PENDING_PAYMENT status' })
  @ApiResponse({ status: 404, description: 'Booking not found' })
  async declareTransfer(
    @Param('id') id: string,
    @Request() req: any
  ): Promise<CsvBookingResponseDto> {
    const userId = req.user.id;
    return await this.csvBookingService.declareBankTransfer(id, userId);
  }

  // ============================================================================
  // PARAMETERIZED ROUTES (must come LAST)
  // ============================================================================

  /**
   * Get a booking by ID
   *
   * GET /api/v1/csv-bookings/:id
   *
   * IMPORTANT: This route MUST be after all static GET routes
   * Otherwise it will capture "stats", "organization", etc.
   */
  @Get(':id')
  @UseGuards(JwtAuthGuard)
  @ApiBearerAuth()
  @ApiOperation({
    summary: 'Get booking by ID',
    description: 'Retrieve a specific CSV booking by its ID. Only accessible by the booking owner.',
  })
  @ApiParam({ name: 'id', description: 'Booking ID (UUID)' })
  @ApiResponse({
    status: 200,
    description: 'Booking retrieved successfully',
    type: CsvBookingResponseDto,
  })
  @ApiResponse({ status: 404, description: 'Booking not found' })
  @ApiResponse({ status: 401, description: 'Unauthorized' })
  async getBooking(@Param('id') id: string, @Request() req: any): Promise<CsvBookingResponseDto> {
    const userId = req.user.id;
    const carrierId = req.user.carrierId; // May be undefined if not a carrier
    return await this.csvBookingService.getBookingById(id, userId, carrierId);
  }

  /**
   * Cancel a booking (user action)
   *
   * PATCH /api/v1/csv-bookings/:id/cancel
   */
  @Patch(':id/cancel')
  @UseGuards(JwtAuthGuard)
  @ApiBearerAuth()
  @ApiOperation({
    summary: 'Cancel booking',
    description: 'Cancel a pending booking. Only accessible by the booking owner.',
  })
  @ApiParam({ name: 'id', description: 'Booking ID (UUID)' })
  @ApiResponse({
    status: 200,
    description: 'Booking cancelled successfully',
    type: CsvBookingResponseDto,
  })
  @ApiResponse({ status: 404, description: 'Booking not found' })
  @ApiResponse({ status: 400, description: 'Booking cannot be cancelled (already accepted)' })
  @ApiResponse({ status: 401, description: 'Unauthorized' })
  async cancelBooking(
    @Param('id') id: string,
    @Request() req: any
  ): Promise<CsvBookingResponseDto> {
    const userId = req.user.id;
    return await this.csvBookingService.cancelBooking(id, userId);
  }

  /**
   * Add documents to an existing booking
   *
   * POST /api/v1/csv-bookings/:id/documents
   */
  @Post(':id/documents')
  @UseGuards(JwtAuthGuard)
  @ApiBearerAuth()
  @UseInterceptors(FilesInterceptor('documents', 10))
  @ApiConsumes('multipart/form-data')
  @ApiOperation({
    summary: 'Add documents to an existing booking',
    description:
      'Upload additional documents to a pending booking. Only the booking owner can add documents.',
  })
  @ApiParam({ name: 'id', description: 'Booking ID (UUID)' })
  @ApiBody({
    schema: {
      type: 'object',
      properties: {
        documents: {
          type: 'array',
          items: { type: 'string', format: 'binary' },
          description: 'Documents to add (max 10 files)',
        },
      },
    },
  })
  @ApiResponse({
    status: 200,
    description: 'Documents added successfully',
    schema: {
      type: 'object',
      properties: {
        success: { type: 'boolean', example: true },
        message: { type: 'string', example: 'Documents added successfully' },
        documentsAdded: { type: 'number', example: 2 },
      },
    },
  })
  @ApiResponse({ status: 400, description: 'Invalid request or booking cannot be modified' })
  @ApiResponse({ status: 401, description: 'Unauthorized' })
  @ApiResponse({ status: 404, description: 'Booking not found' })
  async addDocuments(
    @Param('id') id: string,
    @UploadedFiles() files: Express.Multer.File[],
    @Request() req: any
  ) {
    if (!files || files.length === 0) {
      throw new BadRequestException('At least one document is required');
    }

    const userId = req.user.id;
    return await this.csvBookingService.addDocuments(id, files, userId);
  }

  /**
   * Replace a document in a booking
   *
   * PUT /api/v1/csv-bookings/:bookingId/documents/:documentId
   */
  @Patch(':bookingId/documents/:documentId')
  @UseGuards(JwtAuthGuard)
  @ApiBearerAuth()
  @UseInterceptors(FilesInterceptor('document', 1))
  @ApiConsumes('multipart/form-data')
  @ApiOperation({
    summary: 'Replace a document in a booking',
    description:
      'Replace an existing document with a new one. Only the booking owner can replace documents.',
  })
  @ApiParam({ name: 'bookingId', description: 'Booking ID (UUID)' })
  @ApiParam({ name: 'documentId', description: 'Document ID (UUID) to replace' })
  @ApiBody({
    schema: {
      type: 'object',
      properties: {
        document: {
          type: 'string',
          format: 'binary',
          description: 'New document file to replace the existing one',
        },
      },
    },
  })
  @ApiResponse({
    status: 200,
    description: 'Document replaced successfully',
    schema: {
      type: 'object',
      properties: {
        success: { type: 'boolean', example: true },
        message: { type: 'string', example: 'Document replaced successfully' },
        newDocument: {
          type: 'object',
          properties: {
            id: { type: 'string' },
            type: { type: 'string' },
            fileName: { type: 'string' },
            filePath: { type: 'string' },
            mimeType: { type: 'string' },
            size: { type: 'number' },
            uploadedAt: { type: 'string', format: 'date-time' },
          },
        },
      },
    },
  })
  @ApiResponse({ status: 400, description: 'Invalid request - missing file' })
  @ApiResponse({ status: 401, description: 'Unauthorized' })
  @ApiResponse({ status: 404, description: 'Booking or document not found' })
  async replaceDocument(
    @Param('bookingId') bookingId: string,
    @Param('documentId') documentId: string,
    @UploadedFiles() files: Express.Multer.File[],
    @Request() req: any
  ) {
    if (!files || files.length === 0) {
      throw new BadRequestException('A document file is required');
    }

    const userId = req.user.id;
    return await this.csvBookingService.replaceDocument(bookingId, documentId, files[0], userId);
  }

  /**
   * Delete a document from a booking
   *
   * DELETE /api/v1/csv-bookings/:bookingId/documents/:documentId
   */
  @Delete(':bookingId/documents/:documentId')
  @UseGuards(JwtAuthGuard)
  @ApiBearerAuth()
  @ApiOperation({
    summary: 'Delete a document from a booking',
    description:
      'Remove a document from a pending booking. Only the booking owner can delete documents.',
  })
  @ApiParam({ name: 'bookingId', description: 'Booking ID (UUID)' })
  @ApiParam({ name: 'documentId', description: 'Document ID (UUID)' })
  @ApiResponse({
    status: 200,
    description: 'Document deleted successfully',
    schema: {
      type: 'object',
      properties: {
        success: { type: 'boolean', example: true },
        message: { type: 'string', example: 'Document deleted successfully' },
      },
    },
  })
  @ApiResponse({ status: 400, description: 'Booking cannot be modified (not pending)' })
  @ApiResponse({ status: 401, description: 'Unauthorized' })
  @ApiResponse({ status: 404, description: 'Booking or document not found' })
  async deleteDocument(
    @Param('bookingId') bookingId: string,
    @Param('documentId') documentId: string,
    @Request() req: any
  ) {
    const userId = req.user.id;
    return await this.csvBookingService.deleteDocument(bookingId, documentId, userId);
  }
}
