import {
  Body,
  Controller,
  Delete,
  Get,
  Ip,
  Param,
  ParseIntPipe,
  DefaultValuePipe,
  Patch,
  Post,
  Query,
  Res,
  UploadedFile,
  UseGuards,
  UseInterceptors,
} from '@nestjs/common';
import { CommandBus, QueryBus } from '@nestjs/cqrs';
import { FileInterceptor } from '@nestjs/platform-express';
import {
  ApiTags,
  ApiOperation,
  ApiResponse,
  ApiBearerAuth,
  ApiBody,
  ApiConsumes,
  ApiQuery,
  ApiParam,
} from '@nestjs/swagger';
import { Throttle } from '@nestjs/throttler';
import type { Response } from 'express';
import * as QRCode from 'qrcode';
import { type JwtPayload, CurrentUser, Roles, JwtAuthGuard, RolesGuard, OptionalJwtAuthGuard } from '@modules/auth';
import { NotFoundException, ValidationException, EndpointRateLimit, EndpointRateLimitGuard, FeatureListingThrottlerGuard, FileValidationPipe, type UploadedFile as ValidatedFile } from '@modules/shared';
import { RequireQuota, QuotaGuard } from '@modules/subscriptions';
import { BulkUpdateListingsCommand } from '../../application/commands/bulk-update-listings/bulk-update-listings.command';
import type { BulkUpdateListingsResult } from '../../application/commands/bulk-update-listings/bulk-update-listings.handler';
import { CreateListingCommand } from '../../application/commands/create-listing/create-listing.command';
import type { CreateListingResult } from '../../application/commands/create-listing/create-listing.handler';
import { DeleteListingCommand } from '../../application/commands/delete-listing/delete-listing.command';
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 { PromoteFeaturedListingCommand } from '../../application/commands/promote-featured-listing/promote-featured-listing.command';
import type { PromoteFeaturedListingResult } from '../../application/commands/promote-featured-listing/promote-featured-listing.handler';
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 type { GetPropertyDuplicatesResult } from '../../application/queries/get-property-duplicates/get-property-duplicates.handler';
import { GetPropertyDuplicatesQuery } from '../../application/queries/get-property-duplicates/get-property-duplicates.query';
import { GetSimilarListingsQuery } from '../../application/queries/get-similar-listings/get-similar-listings.query';
import { SearchListingsQuery } from '../../application/queries/search-listings/search-listings.query';
import type { ListingDetailData, ListingSearchItem, ListingSimilarItem } from '../../domain/repositories/listing-read.dto';
import type { PaginatedResult } from '../../domain/repositories/listing.repository';
import { BulkUpdateListingsDto } from '../dto/bulk-update-listings.dto';
import { CreateListingDto } from '../dto/create-listing.dto';
import { FeatureListingDto } from '../dto/feature-listing.dto';
import { ModerateListingDto } from '../dto/moderate-listing.dto';
import { PromoteFeaturedListingDto } from '../dto/promote-featured-listing.dto';
import { SearchListingsDto } from '../dto/search-listings.dto';
import { UpdateListingStatusDto } from '../dto/update-listing-status.dto';
import { UpdateListingDto } from '../dto/update-listing.dto';
import { ReportListingDto } from '../dto/report-listing.dto';
import { ReportListingCommand } from '../../application/commands/report-listing/report-listing.command';
import type { ReportListingResult } from '../../application/commands/report-listing/report-listing.handler';

@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,
        {
          furnishing: dto.furnishing,
          propertyCondition: dto.propertyCondition,
          balconyDirection: dto.balconyDirection,
          maintenanceFeeVND: dto.maintenanceFeeVND,
          parkingSlots: dto.parkingSlots,
          viewType: dto.viewType,
          petFriendly: dto.petFriendly,
          suitableFor: dto.suitableFor,
          whyThisLocation: dto.whyThisLocation,
          certificateVerified: dto.certificateVerified,
        },
      ),
    );
  }

  @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),
    );
  }

  @ApiBearerAuth('JWT')
  @ApiOperation({ summary: 'List potential duplicate listings for a property (admin only)' })
  @ApiQuery({ name: 'propertyId', required: true, type: String, description: 'Property UUID to scan for duplicates' })
  @ApiQuery({ name: 'radiusMeters', required: false, type: Number, example: 50, description: 'Override the default 50m search radius' })
  @ApiResponse({ status: 200, description: 'List of duplicate candidates' })
  @ApiResponse({ status: 400, description: 'Missing propertyId' })
  @ApiResponse({ status: 401, description: 'Unauthorized' })
  @ApiResponse({ status: 403, description: 'Forbidden — admin role required' })
  @ApiResponse({ status: 404, description: 'Property not found' })
  @UseGuards(JwtAuthGuard, RolesGuard)
  @Roles('ADMIN')
  @Get('duplicates')
  async getDuplicates(
    @Query('propertyId') propertyId: string,
    @Query('radiusMeters') radiusMeters?: number,
  ): Promise<GetPropertyDuplicatesResult> {
    if (!propertyId || typeof propertyId !== 'string' || propertyId.trim() === '') {
      throw new ValidationException('propertyId là bắt buộc');
    }
    return this.queryBus.execute(
      new GetPropertyDuplicatesQuery(propertyId, radiusMeters ? Number(radiusMeters) : undefined),
    );
  }

  @ApiOperation({ summary: 'Generate QR code image linking to a listing' })
  @ApiParam({ name: 'id', description: 'Listing UUID', example: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890' })
  @ApiQuery({ name: 'size', required: false, type: Number, example: 300, description: 'QR image size in pixels (PNG only, 50–1000, default 300)' })
  @ApiQuery({ name: 'format', required: false, enum: ['png', 'svg'], example: 'png', description: 'Output format: png (default) or svg' })
  @ApiResponse({ status: 200, description: 'QR code PNG image', content: { 'image/png': {}, 'image/svg+xml': {} } })
  @ApiResponse({ status: 404, description: 'Listing not found' })
  @Get(':id/qr')
  async getQrCode(
    @Param('id') id: string,
    @Res() res: Response,
    @Query('size', new DefaultValuePipe(300), ParseIntPipe) size: number,
    @Query('format') format?: string,
  ): 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 safeSize = Math.min(Math.max(size, 50), 1000);
    const useSvg = format === 'svg';

    if (useSvg) {
      const svgString = await QRCode.toString(listingUrl, {
        type: 'svg',
        margin: 2,
        errorCorrectionLevel: 'M',
      });
      res.set({
        'Content-Type': 'image/svg+xml',
        'Content-Length': Buffer.byteLength(svgString).toString(),
        'Cache-Control': 'public, max-age=86400',
      });
      res.send(svgString);
    } else {
      const qrBuffer = await QRCode.toBuffer(listingUrl, {
        type: 'png',
        width: safeSize,
        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 similar listings (comparables) for a listing' })
  @ApiParam({ name: 'id', description: 'Listing UUID', example: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890' })
  @ApiQuery({ name: 'limit', required: false, type: Number, example: 5, description: 'Max comparables to return (1–10, default 5)' })
  @ApiResponse({ status: 200, description: 'Array of similar listings' })
  @Get(':id/similar')
  async getSimilarListings(
    @Param('id') id: string,
    @Query('limit') limit?: number,
  ): Promise<ListingSimilarItem[]> {
    const safeLimit = Math.min(Math.max(Number(limit) || 5, 1), 10);
    return this.queryBus.execute(new GetSimilarListingsQuery(id, safeLimit));
  }

  @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' })
  @UseGuards(OptionalJwtAuthGuard)
  @Get(':id')
  async getListing(
    @Param('id') id: string,
    @CurrentUser() user?: JwtPayload,
  ): Promise<ListingDetailData> {
    const caller = user ? { userId: user.sub, role: user.role } : undefined;
    const result = await this.queryBus.execute(new GetListingQuery(id, caller));
    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,
        dto.sortBy,
        dto.newSince != null ? new Date(dto.newSince) : undefined,
        dto.order,
      ),
    );
  }

  @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,
        user.role,
        {
          furnishing: dto.furnishing,
          propertyCondition: dto.propertyCondition,
          balconyDirection: dto.balconyDirection,
          maintenanceFeeVND: dto.maintenanceFeeVND,
          parkingSlots: dto.parkingSlots,
          viewType: dto.viewType,
          petFriendly: dto.petFriendly,
          suitableFor: dto.suitableFor,
          whyThisLocation: dto.whyThisLocation,
          certificateVerified: dto.certificateVerified,
        },
        dto.agentId,
      ),
    );
  }

  @ApiBearerAuth('JWT')
  @ApiOperation({
    summary: 'Bulk-update listings (price/status/featured) for an agent',
    description:
      'Cập nhật nhiều listing cùng lúc. Authorization theo từng listing (chỉ owner/agent/admin). Toàn bộ thay đổi chạy trong một transaction; nếu có bất kỳ item nào lỗi sẽ rollback và trả về lý do từng item.',
  })
  @ApiResponse({ status: 200, description: 'Bulk-update result with per-item status' })
  @ApiResponse({ status: 400, description: 'Validation error (empty patch, > 100 items, duplicates, invalid featuredDays)' })
  @ApiResponse({ status: 401, description: 'Unauthorized' })
  @UseGuards(JwtAuthGuard)
  @Post('bulk-update')
  async bulkUpdateListings(
    @Body() dto: BulkUpdateListingsDto,
    @CurrentUser() user: JwtPayload,
  ): Promise<BulkUpdateListingsResult> {
    return this.commandBus.execute(
      new BulkUpdateListingsCommand(
        dto.ids,
        {
          priceVND: dto.patch.priceVND,
          status: dto.patch.status,
          featured: dto.patch.featured,
          featuredDays: dto.patch.featuredDays,
        },
        user.sub,
        user.role ?? 'USER',
      ),
    );
  }

  @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')
  @ApiBody({
    schema: {
      type: 'object',
      properties: {
        file: {
          type: 'string',
          format: 'binary',
          description: 'Image (jpeg/png/webp, max 10MB) or video (mp4, max 100MB)',
        },
        caption: {
          type: 'string',
          description: 'Optional caption for the media',
        },
      },
      required: ['file'],
    },
  })
  @ApiParam({ name: 'id', description: 'Listing UUID' })
  @ApiResponse({
    status: 201,
    description: 'Media uploaded successfully',
    schema: {
      example: {
        mediaId: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
        url: 'https://cdn.goodgo.vn/listings/abc/def.jpg',
      },
    },
  })
  @ApiResponse({ status: 401, description: 'Unauthorized' })
  @ApiResponse({ status: 404, description: 'Listing not found' })
  @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' })
  @ApiResponse({ status: 429, description: 'Too many requests — feature-listing rate limit exceeded' })
  @UseGuards(JwtAuthGuard, FeatureListingThrottlerGuard)
  @Throttle({ default: { limit: 10, ttl: 60_000 } })
  @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),
    );
  }

  @ApiBearerAuth('JWT')
  @ApiOperation({ summary: 'Delete a listing (owner or admin)' })
  @ApiParam({ name: 'id', description: 'Listing UUID' })
  @ApiResponse({ status: 200, description: 'Listing deleted successfully' })
  @ApiResponse({ status: 401, description: 'Unauthorized' })
  @ApiResponse({ status: 403, description: 'Forbidden — not the seller or admin' })
  @ApiResponse({ status: 404, description: 'Listing not found' })
  @UseGuards(JwtAuthGuard)
  @Delete(':id')
  async deleteListing(
    @Param('id') id: string,
    @CurrentUser() user: JwtPayload,
  ): Promise<{ success: true }> {
    await this.commandBus.execute(new DeleteListingCommand(id, user.sub, user.role));
    return { success: true };
  }

  @ApiBearerAuth('JWT')
  @ApiOperation({
    summary: 'Promote a listing via subscription entitlement (no payment)',
    description:
      'Sử dụng quota `featured_listings_promoted` của subscription để bật featured không qua thanh toán.',
  })
  @ApiParam({ name: 'id', description: 'Listing UUID' })
  @ApiResponse({ status: 201, description: 'Listing promoted successfully' })
  @ApiResponse({ status: 400, description: 'Invalid duration or listing not ACTIVE' })
  @ApiResponse({ status: 401, description: 'Unauthorized' })
  @ApiResponse({ status: 403, description: 'Not owner/agent or quota exhausted' })
  @UseGuards(JwtAuthGuard, QuotaGuard)
  @RequireQuota('featured_listings_promoted')
  @Post(':id/promote')
  async promoteListing(
    @Param('id') id: string,
    @Body() dto: PromoteFeaturedListingDto,
    @CurrentUser() user: JwtPayload,
  ): Promise<PromoteFeaturedListingResult> {
    return this.commandBus.execute(
      new PromoteFeaturedListingCommand(id, user.sub, dto.durationDays),
    );
  }

  // ── Report / Flag ──

  @ApiBearerAuth('JWT')
  @ApiOperation({ summary: 'Report a listing (Báo cáo tin đăng)' })
  @ApiParam({ name: 'id', description: 'Listing ID' })
  @ApiResponse({ status: 201, description: 'Báo cáo thành công' })
  @ApiResponse({ status: 400, description: 'Validation error' })
  @ApiResponse({ status: 401, description: 'Unauthorized' })
  @ApiResponse({ status: 404, description: 'Listing not found' })
  @ApiResponse({ status: 409, description: 'Đã báo cáo tin đăng này' })
  @UseGuards(JwtAuthGuard)
  @Throttle({ default: { limit: 5, ttl: 60000 } })
  @Post(':id/report')
  async reportListing(
    @Param('id') id: string,
    @Body() dto: ReportListingDto,
    @CurrentUser() user: JwtPayload,
  ): Promise<ReportListingResult> {
    return this.commandBus.execute(
      new ReportListingCommand(id, user.sub, dto.reason as any, dto.description),
    );
  }
}
