import {
  Body,
  Controller,
  Get,
  Ip,
  Param,
  Patch,
  Post,
  Query,
  Res,
  UploadedFile,
  UseGuards,
  UseInterceptors,
} from '@nestjs/common';
import { type CommandBus, type QueryBus } from '@nestjs/cqrs';
import { FileInterceptor } from '@nestjs/platform-express';
import {
  ApiTags,
  ApiOperation,
  ApiResponse,
  ApiBearerAuth,
  ApiConsumes,
  ApiQuery,
  ApiParam,
} from '@nestjs/swagger';
import type { Response } from 'express';
import * as QRCode from 'qrcode';
import { type JwtPayload, CurrentUser, Roles, JwtAuthGuard, RolesGuard } from '@modules/auth';
import { NotFoundException, EndpointRateLimit, EndpointRateLimitGuard, FileValidationPipe, type UploadedFile as ValidatedFile } from '@modules/shared';
import { RequireQuota, QuotaGuard } from '@modules/subscriptions';
import { CreateListingCommand } from '../../application/commands/create-listing/create-listing.command';
import type { CreateListingResult } from '../../application/commands/create-listing/create-listing.handler';
import { FeatureListingCommand } from '../../application/commands/feature-listing/feature-listing.command';
import type { FeatureListingResult } from '../../application/commands/feature-listing/feature-listing.handler';
import { ModerateListingCommand } from '../../application/commands/moderate-listing/moderate-listing.command';
import { UpdateListingCommand } from '../../application/commands/update-listing/update-listing.command';
import type { UpdateListingResult } from '../../application/commands/update-listing/update-listing.handler';
import { UpdateListingStatusCommand } from '../../application/commands/update-listing-status/update-listing-status.command';
import { UploadMediaCommand } from '../../application/commands/upload-media/upload-media.command';
import { GetListingQuery } from '../../application/queries/get-listing/get-listing.query';
import { GetPendingModerationQuery } from '../../application/queries/get-pending-moderation/get-pending-moderation.query';
import type { PriceHistoryItem } from '../../application/queries/get-price-history/get-price-history.handler';
import { GetPriceHistoryQuery } from '../../application/queries/get-price-history/get-price-history.query';
import { SearchListingsQuery } from '../../application/queries/search-listings/search-listings.query';
import type { ListingDetailData, ListingSearchItem } from '../../domain/repositories/listing-read.dto';
import type { PaginatedResult } from '../../domain/repositories/listing.repository';
import type { CreateListingDto } from '../dto/create-listing.dto';
import type { FeatureListingDto } from '../dto/feature-listing.dto';
import type { ModerateListingDto } from '../dto/moderate-listing.dto';
import { type SearchListingsDto } from '../dto/search-listings.dto';
import type { UpdateListingStatusDto } from '../dto/update-listing-status.dto';
import type { UpdateListingDto } from '../dto/update-listing.dto';

@ApiTags('listings')
@Controller('listings')
export class ListingsController {
  constructor(
    private readonly commandBus: CommandBus,
    private readonly queryBus: QueryBus,
  ) {}

  @ApiBearerAuth('JWT')
  @ApiOperation({ summary: 'Create a new property listing' })
  @ApiResponse({ status: 201, description: 'Listing created successfully' })
  @ApiResponse({ status: 400, description: 'Validation error' })
  @ApiResponse({ status: 401, description: 'Unauthorized' })
  @ApiResponse({ status: 403, description: 'Quota exceeded' })
  @UseGuards(EndpointRateLimitGuard, JwtAuthGuard, QuotaGuard)
  @EndpointRateLimit({ limit: 10, windowSeconds: 60, keyStrategy: 'user' })
  @RequireQuota('listings_created')
  @Post()
  async createListing(
    @Body() dto: CreateListingDto,
    @CurrentUser() user: JwtPayload,
  ): Promise<CreateListingResult> {
    return this.commandBus.execute(
      new CreateListingCommand(
        user.sub,
        dto.transactionType,
        dto.priceVND,
        dto.propertyType,
        dto.title,
        dto.description,
        dto.address,
        dto.ward,
        dto.district,
        dto.city,
        dto.latitude,
        dto.longitude,
        dto.areaM2,
        dto.usableAreaM2,
        dto.bedrooms,
        dto.bathrooms,
        dto.floors,
        dto.floor,
        dto.totalFloors,
        dto.direction,
        dto.yearBuilt,
        dto.legalStatus,
        dto.amenities,
        dto.nearbyPOIs,
        dto.metroDistanceM,
        dto.projectName,
        dto.agentId,
        dto.rentPriceMonthly,
        dto.commissionPct,
      ),
    );
  }

  @ApiBearerAuth('JWT')
  @ApiOperation({ summary: 'Get listings pending moderation (admin only)' })
  @ApiResponse({ status: 200, description: 'Paginated list of pending listings' })
  @ApiResponse({ status: 401, description: 'Unauthorized' })
  @ApiResponse({ status: 403, description: 'Forbidden — admin role required' })
  @ApiQuery({ name: 'page', required: false, type: Number, example: 1, description: 'Page number' })
  @ApiQuery({ name: 'limit', required: false, type: Number, example: 20, description: 'Items per page' })
  @UseGuards(JwtAuthGuard, RolesGuard)
  @Roles('ADMIN')
  @Get('pending')
  async getPendingModeration(
    @Query('page') page?: number,
    @Query('limit') limit?: number,
  ): Promise<PaginatedResult<ListingSearchItem>> {
    return this.queryBus.execute(
      new GetPendingModerationQuery(page ?? 1, limit ?? 20),
    );
  }

  @ApiOperation({ summary: 'Generate QR code image linking to a listing' })
  @ApiParam({ name: 'id', description: 'Listing UUID', example: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890' })
  @ApiResponse({ status: 200, description: 'QR code PNG image', content: { 'image/png': {} } })
  @ApiResponse({ status: 404, description: 'Listing not found' })
  @Get(':id/qr-code')
  async getQrCode(
    @Param('id') id: string,
    @Res() res: Response,
  ): Promise<void> {
    const listing = await this.queryBus.execute(new GetListingQuery(id));
    if (!listing) {
      throw new NotFoundException('Listing', id);
    }

    const siteUrl = process.env['SITE_URL'] || 'https://goodgo.vn';
    const listingUrl = `${siteUrl}/vi/listings/${id}`;

    const qrBuffer = await QRCode.toBuffer(listingUrl, {
      type: 'png',
      width: 300,
      margin: 2,
      color: {
        dark: '#000000',
        light: '#FFFFFF',
      },
      errorCorrectionLevel: 'M',
    });

    res.set({
      'Content-Type': 'image/png',
      'Content-Length': qrBuffer.length.toString(),
      'Cache-Control': 'public, max-age=86400',
    });
    res.send(qrBuffer);
  }

  @ApiOperation({ summary: 'Get price change history for a listing' })
  @ApiParam({ name: 'id', description: 'Listing UUID', example: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890' })
  @ApiResponse({ status: 200, description: 'Price history returned' })
  @Get(':id/price-history')
  async getPriceHistory(@Param('id') id: string): Promise<PriceHistoryItem[]> {
    return this.queryBus.execute(new GetPriceHistoryQuery(id));
  }

  @ApiOperation({ summary: 'Get listing details by ID' })
  @ApiParam({ name: 'id', description: 'Listing UUID', example: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890' })
  @ApiResponse({ status: 200, description: 'Listing details returned' })
  @ApiResponse({ status: 404, description: 'Listing not found' })
  @Get(':id')
  async getListing(@Param('id') id: string): Promise<ListingDetailData> {
    const result = await this.queryBus.execute(new GetListingQuery(id));
    if (!result) {
      throw new NotFoundException('Listing', id);
    }
    return result;
  }

  @ApiOperation({ summary: 'Search and filter property listings' })
  @ApiResponse({ status: 200, description: 'Paginated search results' })
  @Get()
  async searchListings(@Query() dto: SearchListingsDto): Promise<PaginatedResult<ListingSearchItem>> {
    return this.queryBus.execute(
      new SearchListingsQuery(
        dto.status,
        dto.transactionType,
        dto.propertyType,
        dto.city,
        dto.district,
        dto.minPrice,
        dto.maxPrice,
        dto.minArea,
        dto.maxArea,
        dto.bedrooms,
        dto.page,
        dto.limit,
      ),
    );
  }

  @ApiBearerAuth('JWT')
  @ApiOperation({ summary: 'Update listing content (title, description, price, amenities)' })
  @ApiParam({ name: 'id', description: 'Listing UUID' })
  @ApiResponse({ status: 200, description: 'Listing updated successfully' })
  @ApiResponse({ status: 400, description: 'Validation error or no fields provided' })
  @ApiResponse({ status: 401, description: 'Unauthorized' })
  @ApiResponse({ status: 403, description: 'Forbidden — not the seller or assigned agent' })
  @ApiResponse({ status: 404, description: 'Listing not found' })
  @UseGuards(JwtAuthGuard)
  @Patch(':id')
  async updateListing(
    @Param('id') id: string,
    @Body() dto: UpdateListingDto,
    @CurrentUser() user: JwtPayload,
  ): Promise<UpdateListingResult> {
    return this.commandBus.execute(
      new UpdateListingCommand(
        id,
        user.sub,
        dto.title,
        dto.description,
        dto.priceVND,
        dto.rentPriceMonthly,
        dto.amenities,
        dto.mediaOrder,
      ),
    );
  }

  @ApiBearerAuth('JWT')
  @ApiOperation({ summary: 'Update listing status' })
  @ApiParam({ name: 'id', description: 'Listing UUID' })
  @ApiResponse({ status: 200, description: 'Status updated successfully' })
  @ApiResponse({ status: 401, description: 'Unauthorized' })
  @ApiResponse({ status: 404, description: 'Listing not found' })
  @UseGuards(JwtAuthGuard)
  @Patch(':id/status')
  async updateStatus(
    @Param('id') id: string,
    @Body() dto: UpdateListingStatusDto,
    @CurrentUser() user: JwtPayload,
  ): Promise<{ status: string }> {
    return this.commandBus.execute(
      new UpdateListingStatusCommand(id, dto.status, user.sub, dto.moderationNotes),
    );
  }

  @ApiBearerAuth('JWT')
  @ApiOperation({ summary: 'Upload media (photo/video) for a listing' })
  @ApiConsumes('multipart/form-data')
  @ApiParam({ name: 'id', description: 'Listing UUID' })
  @ApiResponse({ status: 201, description: 'Media uploaded successfully' })
  @ApiResponse({ status: 401, description: 'Unauthorized' })
  @ApiResponse({ status: 413, description: 'File too large (images: max 10MB, video: max 100MB)' })
  @UseGuards(JwtAuthGuard)
  @UseInterceptors(FileInterceptor('file'))
  @Post(':id/media')
  async uploadMedia(
    @Param('id') id: string,
    @UploadedFile(new FileValidationPipe({
      maxSizeBytes: 10 * 1024 * 1024, // 10 MB default for images
      maxSizeByMimeType: {
        'video/mp4': 100 * 1024 * 1024, // 100 MB for video
      },
      allowedMimeTypes: ['image/jpeg', 'image/png', 'image/webp', 'video/mp4'],
    }))
    file: ValidatedFile,
    @CurrentUser() user: JwtPayload,
    @Body('caption') caption?: string,
  ): Promise<{ mediaId: string; url: string }> {
    return this.commandBus.execute(
      new UploadMediaCommand(id, user.sub, file, caption),
    );
  }

  @ApiBearerAuth('JWT')
  @ApiOperation({ summary: 'Moderate a listing (admin only)' })
  @ApiParam({ name: 'id', description: 'Listing UUID' })
  @ApiResponse({ status: 200, description: 'Listing moderated successfully' })
  @ApiResponse({ status: 401, description: 'Unauthorized' })
  @ApiResponse({ status: 403, description: 'Forbidden — admin role required' })
  @UseGuards(JwtAuthGuard, RolesGuard)
  @Roles('ADMIN')
  @Patch(':id/moderate')
  async moderateListing(
    @Param('id') id: string,
    @Body() dto: ModerateListingDto,
    @CurrentUser() user: JwtPayload,
  ): Promise<{ status: string }> {
    return this.commandBus.execute(
      new ModerateListingCommand(id, user.sub, dto.action, dto.moderationScore, dto.notes),
    );
  }

  @ApiBearerAuth('JWT')
  @ApiOperation({ summary: 'Feature a listing (creates payment for featured boost)' })
  @ApiParam({ name: 'id', description: 'Listing UUID' })
  @ApiResponse({ status: 201, description: 'Payment created for featured listing' })
  @ApiResponse({ status: 400, description: 'Invalid package or listing not ACTIVE' })
  @ApiResponse({ status: 401, description: 'Unauthorized' })
  @ApiResponse({ status: 403, description: 'Not the seller or assigned agent' })
  @UseGuards(JwtAuthGuard)
  @Post(':id/feature')
  async featureListing(
    @Param('id') id: string,
    @Body() dto: FeatureListingDto,
    @CurrentUser() user: JwtPayload,
    @Ip() ip: string,
  ): Promise<FeatureListingResult> {
    return this.commandBus.execute(
      new FeatureListingCommand(id, user.sub, dto.package, dto.provider, dto.returnUrl, ip),
    );
  }
}
