import {
  Body,
  Controller,
  Get,
  Param,
  Post,
  Query,
  UseGuards,
  UseInterceptors,
} from '@nestjs/common';
import { QueryBus } from '@nestjs/cqrs';
import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth, ApiBody, ApiParam } from '@nestjs/swagger';
import { JwtAuthGuard } from '@modules/auth';
import { EndpointRateLimit, EndpointRateLimitGuard } from '@modules/shared';
import { RequireQuota, QuotaGuard } from '@modules/subscriptions';
import { CacheMetaInterceptor } from '../interceptors/cache-meta.interceptor';
import { type BatchValuationDto as BatchValuationQueryDto } from '../../application/queries/batch-valuation/batch-valuation.handler';
import { BatchValuationQuery } from '../../application/queries/batch-valuation/batch-valuation.query';
import { type DistrictStatsDto } from '../../application/queries/get-district-stats/get-district-stats.handler';
import { GetDistrictStatsQuery } from '../../application/queries/get-district-stats/get-district-stats.query';
import { type HeatmapDto } from '../../application/queries/get-heatmap/get-heatmap.handler';
import { GetHeatmapQuery } from '../../application/queries/get-heatmap/get-heatmap.query';
import { type ListingVolumeWardDto } from '../../application/queries/get-listing-volume-ward/get-listing-volume-ward.handler';
import { GetListingVolumeWardQuery } from '../../application/queries/get-listing-volume-ward/get-listing-volume-ward.query';
import {
  type ListingAiAdviceResponse,
} from '../../application/queries/get-listing-ai-advice/get-listing-ai-advice.handler';
import { GetListingAiAdviceQuery } from '../../application/queries/get-listing-ai-advice/get-listing-ai-advice.query';
import {
  type ProjectAiAdviceResponse,
} from '../../application/queries/get-project-ai-advice/get-project-ai-advice.handler';
import { GetProjectAiAdviceQuery } from '../../application/queries/get-project-ai-advice/get-project-ai-advice.query';
import { type MarketReportDto } from '../../application/queries/get-market-report/get-market-report.handler';
import { GetMarketReportQuery } from '../../application/queries/get-market-report/get-market-report.query';
import { type MarketHistoryDto } from '../../application/queries/get-market-history/get-market-history.handler';
import { GetMarketHistoryQuery } from '../../application/queries/get-market-history/get-market-history.query';
import { type MarketSnapshotDto } from '../../application/queries/get-market-snapshot/get-market-snapshot.handler';
import { GetMarketSnapshotQuery } from '../../application/queries/get-market-snapshot/get-market-snapshot.query';
import { type PriceMoversDto } from '../../application/queries/get-price-movers/get-price-movers.handler';
import { GetPriceMoversQuery } from '../../application/queries/get-price-movers/get-price-movers.query';
import { type TrendingAreasDto } from '../../application/queries/get-trending-areas/get-trending-areas.handler';
import { GetTrendingAreasQuery } from '../../application/queries/get-trending-areas/get-trending-areas.query';
import { type NearbyPOIsResultDto } from '../../application/queries/get-nearby-pois/get-nearby-pois.handler';
import { GetNearbyPOIsQuery } from '../../application/queries/get-nearby-pois/get-nearby-pois.query';
import { GetNeighborhoodScoreQuery } from '../../application/queries/get-neighborhood-score/get-neighborhood-score.query';
import { type PriceTrendDto } from '../../application/queries/get-price-trend/get-price-trend.handler';
import { GetPriceTrendQuery } from '../../application/queries/get-price-trend/get-price-trend.query';
import { type ValuationDto } from '../../application/queries/get-valuation/get-valuation.handler';
import { GetValuationQuery } from '../../application/queries/get-valuation/get-valuation.query';
import { type PredictValuationDto } from '../../application/queries/predict-valuation/predict-valuation.handler';
import { PredictValuationQuery } from '../../application/queries/predict-valuation/predict-valuation.query';
import { type ValuationComparisonDto as ValuationComparisonResultDto } from '../../application/queries/valuation-comparison/valuation-comparison.handler';
import { ValuationComparisonQuery } from '../../application/queries/valuation-comparison/valuation-comparison.query';
import { type ValuationHistoryDto as ValuationHistoryResultDto } from '../../application/queries/valuation-history/valuation-history.handler';
import { ValuationHistoryQuery } from '../../application/queries/valuation-history/valuation-history.query';
import { type NeighborhoodScoreResult } from '../../domain/services/neighborhood-score.service';
import { BatchValuationDto } from '../dto/batch-valuation.dto';
import { GetDistrictStatsDto } from '../dto/get-district-stats.dto';
import { GetHeatmapDto } from '../dto/get-heatmap.dto';
import { GetListingVolumeWardDto } from '../dto/get-listing-volume-ward.dto';
import { GetMarketReportDto } from '../dto/get-market-report.dto';
import { GetMarketHistoryDto } from '../dto/get-market-history.dto';
import { GetMarketSnapshotDto } from '../dto/get-market-snapshot.dto';
import { GetPriceMoversDto } from '../dto/get-price-movers.dto';
import { GetTrendingAreasDto } from '../dto/get-trending-areas.dto';
import { GetNearbyPOIsDto } from '../dto/get-nearby-pois.dto';
import { GetPriceTrendDto } from '../dto/get-price-trend.dto';
import { GetValuationDto } from '../dto/get-valuation.dto';
import { PredictValuationDto as PredictValuationBodyDto } from '../dto/predict-valuation.dto';
import { ValuationComparisonDto } from '../dto/valuation-comparison.dto';
import { ValuationHistoryDto } from '../dto/valuation-history.dto';

@ApiTags('analytics')
@UseInterceptors(CacheMetaInterceptor)
@Controller('analytics')
export class AnalyticsController {
  constructor(
    private readonly queryBus: QueryBus,
  ) {}

  @ApiBearerAuth('JWT')
  @UseGuards(JwtAuthGuard, QuotaGuard)
  @RequireQuota('analytics_queries')
  @Get('market-report')
  @ApiOperation({ summary: 'Get market report for a city' })
  @ApiResponse({ status: 200, description: 'Market report retrieved' })
  @ApiResponse({ status: 403, description: 'Quota exceeded' })
  async getMarketReport(@Query() dto: GetMarketReportDto): Promise<MarketReportDto> {
    return this.queryBus.execute(
      new GetMarketReportQuery(dto.city, dto.period, dto.propertyType),
    );
  }

  @ApiBearerAuth('JWT')
  @UseGuards(JwtAuthGuard, QuotaGuard)
  @RequireQuota('analytics_queries')
  @Get('market-history')
  @ApiOperation({
    summary: 'Lịch sử thị trường BĐS theo chuỗi thời gian',
    description:
      'Trả về time-series dữ liệu thị trường (giá trung bình, giá trung vị, số tin đăng, thời gian rao) cho trang analytics. Cache 6 giờ.',
  })
  @ApiResponse({ status: 200, description: 'Market history time-series retrieved' })
  @ApiResponse({ status: 403, description: 'Quota exceeded' })
  async getMarketHistory(@Query() dto: GetMarketHistoryDto): Promise<MarketHistoryDto> {
    return this.queryBus.execute(
      new GetMarketHistoryQuery(dto.city, dto.period, dto.granularity, dto.propertyType),
    );
  }

  @ApiBearerAuth('JWT')
  @UseGuards(JwtAuthGuard, QuotaGuard)
  @RequireQuota('analytics_queries')
  @Get('market-snapshot')
  @ApiOperation({
    summary: 'Tổng quan thị trường cho dashboard tiles',
    description:
      'Trả về snapshot thị trường BĐS: số tin đang hoạt động, giá trung bình, giá trung vị, biến động giá 1d/7d/30d, giá/m², thời gian rao trung bình, tin mới 24h. Cache Redis 5 phút.',
  })
  @ApiResponse({ status: 200, description: 'Market snapshot retrieved' })
  @ApiResponse({ status: 403, description: 'Quota exceeded' })
  async getMarketSnapshot(@Query() dto: GetMarketSnapshotDto): Promise<MarketSnapshotDto> {
    return this.queryBus.execute(
      new GetMarketSnapshotQuery(dto.city, dto.propertyType),
    );
  }

  @ApiBearerAuth('JWT')
  @UseGuards(JwtAuthGuard, QuotaGuard)
  @RequireQuota('analytics_queries')
  @Get('price-movers')
  @ApiOperation({
    summary: 'Top tăng/giảm giá theo quận cho Home dashboard',
    description:
      'Trả về danh sách quận có biến động giá lớn nhất (tăng hoặc giảm) trong khoảng thời gian chỉ định. Chỉ hiển thị quận có ≥ 10 tin đăng. Cache Redis 30 phút.',
  })
  @ApiResponse({ status: 200, description: 'Price movers retrieved' })
  @ApiResponse({ status: 403, description: 'Quota exceeded' })
  async getPriceMovers(@Query() dto: GetPriceMoversDto): Promise<PriceMoversDto> {
    return this.queryBus.execute(
      new GetPriceMoversQuery(dto.direction, dto.period, dto.limit, dto.level),
    );
  }

  @ApiBearerAuth('JWT')
  @UseGuards(JwtAuthGuard, QuotaGuard)
  @RequireQuota('analytics_queries')
  @Get('price-trend')
  @ApiOperation({ summary: 'Get price trend for a district' })
  @ApiResponse({ status: 200, description: 'Price trend data retrieved' })
  @ApiResponse({ status: 403, description: 'Quota exceeded' })
  async getPriceTrend(@Query() dto: GetPriceTrendDto): Promise<PriceTrendDto> {
    return this.queryBus.execute(
      new GetPriceTrendQuery(dto.district, dto.city, dto.propertyType, dto.periods),
    );
  }

  @ApiBearerAuth('JWT')
  @UseGuards(JwtAuthGuard, QuotaGuard)
  @RequireQuota('analytics_queries')
  @Get('heatmap')
  @ApiOperation({
    summary: 'Get price heatmap for a city',
    description:
      'Trả về dữ liệu heatmap giá BĐS. `level=district` (mặc định) cho aggregation theo quận; `level=ward` drill-down xuống cấp phường. Cache 30 phút cho ward, 5 phút cho district.',
  })
  @ApiResponse({ status: 200, description: 'Heatmap data retrieved' })
  @ApiResponse({ status: 403, description: 'Quota exceeded' })
  async getHeatmap(@Query() dto: GetHeatmapDto): Promise<HeatmapDto> {
    return this.queryBus.execute(
      new GetHeatmapQuery(dto.city, dto.period, dto.level ?? 'district', dto.district),
    );
  }

  @ApiBearerAuth('JWT')
  @UseGuards(JwtAuthGuard, QuotaGuard)
  @RequireQuota('analytics_queries')
  @Get('listing-volume')
  @ApiOperation({
    summary: '[TEC-3055] Khối lượng tin đăng và giá trung bình/trung vị theo phường',
    description:
      'Drill-down volume tin đăng + giá avg/median cho một phường trong kỳ chỉ định. `wardId` là tên phường (khớp với `Property.ward`). `period` dạng "YYYY-QN" (quý) hoặc "YYYY-MM" (tháng). Cache 30 phút.',
  })
  @ApiResponse({ status: 200, description: 'Listing volume data retrieved' })
  @ApiResponse({ status: 404, description: 'Không có dữ liệu cho phường và kỳ này' })
  @ApiResponse({ status: 403, description: 'Quota exceeded' })
  async getListingVolumeByWard(@Query() dto: GetListingVolumeWardDto): Promise<ListingVolumeWardDto> {
    return this.queryBus.execute(
      new GetListingVolumeWardQuery(dto.wardId, dto.period),
    );
  }

  @ApiBearerAuth('JWT')
  @UseGuards(JwtAuthGuard, QuotaGuard)
  @RequireQuota('analytics_queries')
  @Get('district-stats')
  @ApiOperation({ summary: 'Get statistics by district' })
  @ApiResponse({ status: 200, description: 'District statistics retrieved' })
  @ApiResponse({ status: 403, description: 'Quota exceeded' })
  async getDistrictStats(@Query() dto: GetDistrictStatsDto): Promise<DistrictStatsDto> {
    return this.queryBus.execute(
      new GetDistrictStatsQuery(dto.city, dto.period),
    );
  }

  @ApiBearerAuth('JWT')
  @UseGuards(JwtAuthGuard, QuotaGuard)
  @RequireQuota('analytics_queries')
  @Get('valuation')
  @ApiOperation({ summary: 'Get automated property valuation (AVM)' })
  @ApiResponse({ status: 200, description: 'Valuation estimate retrieved' })
  @ApiResponse({ status: 400, description: 'Invalid parameters — provide propertyId or (lat, lng, areaM2)' })
  @ApiResponse({ status: 403, description: 'Quota exceeded' })
  async getValuation(@Query() dto: GetValuationDto): Promise<ValuationDto> {
    return this.queryBus.execute(
      new GetValuationQuery(dto.propertyId, dto.latitude, dto.longitude, dto.areaM2, dto.propertyType),
    );
  }

  @ApiBearerAuth('JWT')
  @EndpointRateLimit({ limit: 10, windowSeconds: 60, keyStrategy: 'user' })
  @UseGuards(EndpointRateLimitGuard, JwtAuthGuard, QuotaGuard)
  @RequireQuota('analytics_queries')
  @Post('valuation')
  @ApiOperation({
    summary: 'Định giá BĐS (AVM v1/v2) từ form nhập tay',
    description:
      'Nhận đặc trưng BĐS qua body và trả về ước tính giá. `useV2=true` dùng mô hình ensemble v2 với các trường khoảng cách hạ tầng + tiện nghi + nguy cơ ngập.',
  })
  @ApiResponse({ status: 200, description: 'Valuation estimate retrieved' })
  @ApiResponse({ status: 400, description: 'Invalid parameters' })
  @ApiResponse({ status: 401, description: 'Chưa xác thực' })
  @ApiResponse({ status: 403, description: 'Quota exceeded' })
  @ApiResponse({ status: 429, description: 'Rate limit exceeded' })
  @ApiResponse({ status: 503, description: 'Dịch vụ AI tạm thời không khả dụng' })
  async predictValuation(@Body() dto: PredictValuationBodyDto): Promise<PredictValuationDto> {
    return this.queryBus.execute(
      new PredictValuationQuery(
        null,
        dto.propertyType,
        dto.area,
        dto.district,
        dto.city,
        dto.bedrooms,
        dto.bathrooms,
        dto.floors,
        dto.frontage,
        dto.roadWidth,
        dto.yearBuilt,
        dto.hasLegalPaper,
        dto.latitude,
        dto.longitude,
        dto.projectId,
        dto.imageUrl,
        dto.description,
        dto.deepAnalysis,
        dto.useV2,
        dto.distanceToHospitalKm,
        dto.distanceToParkKm,
        dto.distanceToMallKm,
        dto.floodZoneRisk,
        dto.hasElevator,
        dto.hasParking,
        dto.hasPool,
      ),
    );
  }

  @ApiBearerAuth('JWT')
  @EndpointRateLimit({ limit: 10, windowSeconds: 60, keyStrategy: 'user' })
  @UseGuards(EndpointRateLimitGuard, JwtAuthGuard, QuotaGuard)
  @RequireQuota('analytics_queries')
  @Post('valuation/batch')
  @ApiOperation({ summary: 'Batch valuation for multiple properties (max 50)' })
  @ApiBody({
    schema: {
      type: 'object',
      properties: {
        propertyIds: {
          type: 'array',
          minItems: 1,
          maxItems: 50,
          items: { type: 'string' },
          example: ['prop-1', 'prop-2'],
          description: 'Array of property IDs to valuate (1-50)',
        },
      },
      required: ['propertyIds'],
    },
  })
  @ApiResponse({ status: 200, description: 'Batch valuation results retrieved' })
  @ApiResponse({ status: 400, description: 'Invalid parameters' })
  @ApiResponse({ status: 401, description: 'Chưa xác thực' })
  @ApiResponse({ status: 403, description: 'Quota exceeded' })
  @ApiResponse({ status: 429, description: 'Rate limit exceeded' })
  async batchValuation(@Body() dto: BatchValuationDto): Promise<BatchValuationQueryDto> {
    return this.queryBus.execute(
      new BatchValuationQuery(dto.propertyIds),
    );
  }

  @ApiBearerAuth('JWT')
  @UseGuards(JwtAuthGuard, QuotaGuard)
  @RequireQuota('analytics_queries')
  @Get('valuation/history/:propertyId')
  @ApiOperation({ summary: 'Get valuation history for a property (chart data)' })
  @ApiParam({ name: 'propertyId', description: 'Property ID' })
  @ApiResponse({ status: 200, description: 'Valuation history retrieved' })
  @ApiResponse({ status: 403, description: 'Quota exceeded' })
  async getValuationHistory(
    @Param('propertyId') propertyId: string,
    @Query() dto: ValuationHistoryDto,
  ): Promise<ValuationHistoryResultDto> {
    return this.queryBus.execute(
      new ValuationHistoryQuery(propertyId, dto.limit ?? 50),
    );
  }

  @ApiBearerAuth('JWT')
  @EndpointRateLimit({ limit: 10, windowSeconds: 60, keyStrategy: 'user' })
  @UseGuards(EndpointRateLimitGuard, JwtAuthGuard, QuotaGuard)
  @RequireQuota('analytics_queries')
  @Post('valuation/compare')
  @ApiOperation({ summary: 'Compare valuations for 2-5 properties side by side' })
  @ApiResponse({ status: 200, description: 'Valuation comparison retrieved' })
  @ApiResponse({ status: 400, description: 'Invalid parameters — provide 2-5 property IDs' })
  @ApiResponse({ status: 403, description: 'Quota exceeded' })
  @ApiResponse({ status: 429, description: 'Rate limit exceeded' })
  async compareValuations(@Body() dto: ValuationComparisonDto): Promise<ValuationComparisonResultDto> {
    return this.queryBus.execute(
      new ValuationComparisonQuery(dto.propertyIds),
    );
  }

  @ApiOperation({ summary: 'Get neighborhood score for a district' })
  @ApiParam({ name: 'district', description: 'District name', example: 'Quận 1' })
  @ApiResponse({ status: 200, description: 'Neighborhood score retrieved' })
  @Get('neighborhoods/:district/score')
  async getNeighborhoodScore(
    @Param('district') district: string,
    @Query('city') city: string = 'Hồ Chí Minh',
  ): Promise<NeighborhoodScoreResult> {
    return this.queryBus.execute(
      new GetNeighborhoodScoreQuery(district, city),
    );
  }

  @ApiOperation({ summary: 'Get nearby POIs around a coordinate (public)' })
  @ApiResponse({ status: 200, description: 'Nearby POIs retrieved' })
  @Get('pois/nearby')
  async getNearbyPOIs(@Query() dto: GetNearbyPOIsDto): Promise<NearbyPOIsResultDto> {
    return this.queryBus.execute(
      new GetNearbyPOIsQuery(
        dto.lat,
        dto.lng,
        dto.radius ?? 2000,
        dto.limit ?? 30,
      ),
    );
  }

  @ApiOperation({
    summary: 'Top khu vực đang trending (public)',
    description:
      'Trả về danh sách quận trending theo lượng tin đăng/inquiries/views trong khoảng nhìn lại. Public endpoint cho homepage. Cache.',
  })
  @ApiResponse({ status: 200, description: 'Trending areas retrieved' })
  @Get('trending-areas')
  async getTrendingAreas(@Query() dto: GetTrendingAreasDto): Promise<TrendingAreasDto> {
    return this.queryBus.execute(
      new GetTrendingAreasQuery(dto.period, dto.limit, dto.level),
    );
  }

  @ApiBearerAuth('JWT')
  @UseGuards(JwtAuthGuard)
  @Post('listings/:id/ai-advice')
  @ApiOperation({
    summary: 'AI nhận định + định giá cho một tin đăng (Claude)',
    description:
      'Gọi Claude API (Anthropic) để trả về JSON gồm ước tính giá và phân tích điểm mạnh/yếu. API key do admin cấu hình trong System Settings.',
  })
  @ApiParam({ name: 'id', description: 'Listing ID' })
  @ApiResponse({ status: 200, description: 'AI advice generated' })
  @ApiResponse({ status: 401, description: 'Chưa xác thực' })
  @ApiResponse({ status: 404, description: 'Listing not found' })
  @ApiResponse({ status: 502, description: 'AI provider error' })
  @ApiResponse({ status: 503, description: 'AI chưa được cấu hình' })
  async getListingAiAdvice(
    @Param('id') id: string,
  ): Promise<ListingAiAdviceResponse> {
    return this.queryBus.execute(new GetListingAiAdviceQuery(id));
  }

  @ApiBearerAuth('JWT')
  @UseGuards(JwtAuthGuard)
  @Post('projects/:id/ai-advice')
  @ApiOperation({
    summary: 'AI nhận định dự án BĐS (Claude)',
    description:
      'Gọi Claude API để trả về JSON gồm tóm tắt, điểm mạnh/yếu và nhóm khách phù hợp cho một dự án của chủ đầu tư. Không kèm ước tính giá (dự án là dải giá, không phải một căn cụ thể).',
  })
  @ApiParam({ name: 'id', description: 'Project ID' })
  @ApiResponse({ status: 200, description: 'AI advice generated' })
  @ApiResponse({ status: 401, description: 'Chưa xác thực' })
  @ApiResponse({ status: 404, description: 'Project not found' })
  @ApiResponse({ status: 502, description: 'AI provider error' })
  @ApiResponse({ status: 503, description: 'AI chưa được cấu hình' })
  async getProjectAiAdvice(
    @Param('id') id: string,
  ): Promise<ProjectAiAdviceResponse> {
    return this.queryBus.execute(new GetProjectAiAdviceQuery(id));
  }
}
