import { HttpStatus, Inject } from '@nestjs/common';
import { QueryBus, QueryHandler, type IQueryHandler } from '@nestjs/cqrs';
import {
  AI_CONFIG_PROVIDER,
  DomainException,
  ErrorCode,
  type IAIConfigProvider,
  LoggerService,
} from '@modules/shared';
import {
  LISTING_REPOSITORY,
  type IListingRepository,
} from '@modules/listings/domain/repositories/listing.repository';
import { type ListingDetailData } from '../../../../listings/domain/repositories/listing-read.dto';
import {
  type NearbyPOIDto,
  type NearbyPOIsResultDto,
} from '../get-nearby-pois/get-nearby-pois.handler';
import { GetNearbyPOIsQuery } from '../get-nearby-pois/get-nearby-pois.query';
import { type NeighborhoodScoreResult } from '../../../domain/services/neighborhood-score.service';
import { GetNeighborhoodScoreQuery } from '../get-neighborhood-score/get-neighborhood-score.query';
import {
  asInt,
  asString,
  asStringArray,
  callAnthropicJson,
  isRecord,
  jsonShapeError,
  parseJsonObject,
} from '../_shared/ai-json-client';
import { GetListingAiAdviceQuery } from './get-listing-ai-advice.query';

/** Shape returned by Anthropic (parsed from first content block). */
export type AiConfidence = 'low' | 'medium' | 'high';

export interface ListingAiValuation {
  estimateVND: number;
  lowVND: number;
  highVND: number;
  confidence: AiConfidence;
  rationale: string;
}

export interface ListingAiAdvice {
  summary: string;
  pros: string[];
  cons: string[];
  suitableFor: string[];
}

export interface ListingAiAdviceResponse {
  valuation: ListingAiValuation;
  advice: ListingAiAdvice;
  model: string;
  cacheHit: boolean;
  cacheUsage?: {
    input: number;
    cacheCreation: number;
    cacheRead: number;
    output: number;
  };
}

// System prompt: Vietnamese real-estate persona. Short, stable, cacheable.
const SYSTEM_PROMPT = `Bạn là chuyên gia thẩm định bất động sản Việt Nam (HCM & Hà Nội). Dựa trên dữ liệu được cung cấp, hãy đưa ra (1) ước tính giá trị hợp lý bằng VND và (2) nhận định ngắn gọn về điểm mạnh/yếu của BĐS.

Bối cảnh thị trường:
- Đơn vị tiền tệ: VND (không dùng USD).
- Giá/m² tham khảo ở HCM: Quận 1/3 ~ 200–350 tr, Bình Thạnh/Phú Nhuận ~ 70–130 tr, Quận 7 ~ 60–100 tr, vùng ven ~ 30–55 tr. Hà Nội: Hoàn Kiếm/Ba Đình ~ 250–400 tr, Cầu Giấy/Thanh Xuân ~ 60–120 tr.
- Giá giao dịch có thể lệch ±15% so với giá niêm yết tuỳ pháp lý, nội thất, view, hướng.

Bắt buộc trả về **JSON thuần** đúng schema, KHÔNG bọc markdown, KHÔNG giải thích thêm:
{
  "valuation": {
    "estimateVND": <số nguyên VND>,
    "lowVND": <số nguyên VND>,
    "highVND": <số nguyên VND>,
    "confidence": "low" | "medium" | "high",
    "rationale": "<1-2 câu tiếng Việt>"
  },
  "advice": {
    "summary": "<≤ 2 câu tổng quan tiếng Việt>",
    "pros": ["<cụm ngắn>", ...],        // 3-5 mục
    "cons": ["<cụm ngắn>", ...],        // 2-4 mục
    "suitableFor": ["<nhãn persona>", ...] // 2-4 mục, tiếng Việt
  }
}

Đưa ra estimateVND nằm giữa lowVND và highVND. Dải low/high nên phản ánh độ bất định: confidence=high dải ~±8%, medium ~±15%, low ~±25%.`;

@QueryHandler(GetListingAiAdviceQuery)
export class GetListingAiAdviceHandler
  implements IQueryHandler<GetListingAiAdviceQuery, ListingAiAdviceResponse>
{
  constructor(
    @Inject(LISTING_REPOSITORY)
    private readonly listingRepo: IListingRepository,
    private readonly queryBus: QueryBus,
    @Inject(AI_CONFIG_PROVIDER)
    private readonly aiConfig: IAIConfigProvider,
    private readonly logger: LoggerService,
  ) {}

  async execute(
    query: GetListingAiAdviceQuery,
  ): Promise<ListingAiAdviceResponse> {
    const listing = await this.listingRepo.findByIdWithProperty(query.listingId);
    if (!listing) {
      throw new DomainException(
        ErrorCode.LISTING_NOT_FOUND,
        `Không tìm thấy tin đăng ${query.listingId}`,
        HttpStatus.NOT_FOUND,
      );
    }

    // Fetch enrichments in parallel (both tolerate missing data).
    const [poisResult, score] = await Promise.all([
      this.fetchPois(listing),
      this.fetchScore(listing),
    ]);

    const settings = await this.aiConfig.getAiConfig();
    if (!settings.apiKey) {
      throw new DomainException(
        ErrorCode.AI_NOT_CONFIGURED,
        'Quản trị viên chưa cấu hình Claude API. Vào /admin/settings/ai để thiết lập.',
        HttpStatus.SERVICE_UNAVAILABLE,
      );
    }

    const userPrompt = buildUserPrompt(listing, poisResult, score);

    const { text, usage } = await callAnthropicJson({
      apiUrl: settings.apiUrl,
      apiKey: settings.apiKey,
      model: settings.model,
      systemPrompt: SYSTEM_PROMPT,
      userPrompt,
      logger: this.logger,
      tag: 'ai-advice',
    });

    const obj = parseJsonObject(text);
    const valuation = parseValuation(obj['valuation']);
    const advice = parseAdvice(obj['advice']);

    return {
      valuation,
      advice,
      model: settings.model,
      cacheHit: usage.cacheRead > 0,
      cacheUsage: usage,
    };
  }

  private async fetchPois(
    listing: ListingDetailData,
  ): Promise<NearbyPOIsResultDto | null> {
    const { latitude, longitude } = listing.property;
    if (latitude == null || longitude == null) return null;
    try {
      return await this.queryBus.execute<
        GetNearbyPOIsQuery,
        NearbyPOIsResultDto
      >(new GetNearbyPOIsQuery(latitude, longitude, 2000, 30));
    } catch (err) {
      this.logger.warn(
        `[ai-advice] fetchPois failed: ${err instanceof Error ? err.message : err}`,
      );
      return null;
    }
  }

  private async fetchScore(
    listing: ListingDetailData,
  ): Promise<NeighborhoodScoreResult | null> {
    const { district, city } = listing.property;
    if (!district || !city) return null;
    try {
      return await this.queryBus.execute<
        GetNeighborhoodScoreQuery,
        NeighborhoodScoreResult | null
      >(new GetNeighborhoodScoreQuery(district, city));
    } catch (err) {
      this.logger.warn(
        `[ai-advice] fetchScore failed: ${err instanceof Error ? err.message : err}`,
      );
      return null;
    }
  }

}

// --------------------------------------------------------------------------
// Prompt construction
// --------------------------------------------------------------------------

function buildUserPrompt(
  listing: ListingDetailData,
  poisResult: NearbyPOIsResultDto | null,
  score: NeighborhoodScoreResult | null,
): string {
  const p = listing.property;
  const lines: string[] = [];

  lines.push('### THÔNG TIN TIN ĐĂNG');
  lines.push(`- Loại giao dịch: ${listing.transactionType}`);
  lines.push(`- Giá niêm yết: ${listing.priceVND} VND`);
  if (listing.pricePerM2 != null) {
    lines.push(`- Giá / m²: ${listing.pricePerM2.toLocaleString('vi-VN')} VND`);
  }
  lines.push(`- Diện tích: ${p.areaM2} m²`);
  if (p.bedrooms != null) lines.push(`- Phòng ngủ: ${p.bedrooms}`);
  if (p.bathrooms != null) lines.push(`- Phòng tắm: ${p.bathrooms}`);
  lines.push(`- Loại BĐS: ${p.propertyType}`);
  lines.push(`- Địa chỉ: ${p.address}, ${p.ward ?? ''}, ${p.district}, ${p.city}`);
  if (p.direction) lines.push(`- Hướng: ${p.direction}`);
  if (p.yearBuilt) lines.push(`- Năm xây: ${p.yearBuilt}`);
  if (p.legalStatus) lines.push(`- Pháp lý: ${p.legalStatus}`);
  if (p.furnishing) lines.push(`- Nội thất: ${p.furnishing}`);
  if (p.propertyCondition) lines.push(`- Tình trạng: ${p.propertyCondition}`);
  if (p.metroDistanceM != null) {
    lines.push(`- Cách metro: ${p.metroDistanceM} m`);
  }
  if (p.suitableFor && p.suitableFor.length > 0) {
    lines.push(`- Người đăng gợi ý phù hợp: ${p.suitableFor.join(', ')}`);
  }
  if (p.whyThisLocation) {
    lines.push(`- Ghi chú vị trí (người đăng): ${p.whyThisLocation}`);
  }

  if (poisResult && poisResult.pois.length > 0) {
    const counts = countByCategory(poisResult.pois);
    const closest = poisResult.pois.slice(0, 5);
    lines.push('');
    lines.push('### ĐIỂM QUAN TÂM LÂN CẬN (bán kính 2km)');
    const countLine = Object.entries(counts)
      .map(([cat, n]) => `${cat}: ${n}`)
      .join(', ');
    lines.push(`- Số lượng theo nhóm: ${countLine}`);
    lines.push('- 5 điểm gần nhất:');
    for (const poi of closest) {
      lines.push(
        `  • ${poi.name} (${poi.category}) — ${Math.round(poi.distance)} m`,
      );
    }
  }

  if (score) {
    lines.push('');
    lines.push('### ĐIỂM KHU VỰC (thang 0–10)');
    lines.push(`- Giáo dục: ${score.educationScore.toFixed(1)}`);
    lines.push(`- Y tế: ${score.healthcareScore.toFixed(1)}`);
    lines.push(`- Giao thông: ${score.transportScore.toFixed(1)}`);
    lines.push(`- Mua sắm: ${score.shoppingScore.toFixed(1)}`);
    lines.push(`- Môi trường: ${score.greeneryScore.toFixed(1)}`);
    lines.push(`- An ninh: ${score.safetyScore.toFixed(1)}`);
    lines.push(`- Tổng: ${score.totalScore.toFixed(1)}`);
  }

  lines.push('');
  lines.push('Hãy trả về JSON đúng schema đã quy định.');
  return lines.join('\n');
}

function countByCategory(pois: NearbyPOIDto[]): Record<string, number> {
  const out: Record<string, number> = {};
  for (const p of pois) {
    out[p.category] = (out[p.category] ?? 0) + 1;
  }
  return out;
}

// --------------------------------------------------------------------------
// Response validation (parsers specific to the listing response shape)
// --------------------------------------------------------------------------

function parseValuation(v: unknown): ListingAiValuation {
  if (!isRecord(v)) throw jsonShapeError('valuation');
  const estimateVND = asInt(v['estimateVND']);
  const lowVND = asInt(v['lowVND']);
  const highVND = asInt(v['highVND']);
  const rationale = asString(v['rationale']);
  const confidenceRaw = v['confidence'];
  const confidence: AiConfidence =
    confidenceRaw === 'low' || confidenceRaw === 'medium' || confidenceRaw === 'high'
      ? confidenceRaw
      : 'medium';
  if (
    estimateVND == null ||
    lowVND == null ||
    highVND == null ||
    rationale == null
  ) {
    throw jsonShapeError('valuation');
  }
  return { estimateVND, lowVND, highVND, confidence, rationale };
}

function parseAdvice(v: unknown): ListingAiAdvice {
  if (!isRecord(v)) throw jsonShapeError('advice');
  const summary = asString(v['summary']);
  const pros = asStringArray(v['pros']);
  const cons = asStringArray(v['cons']);
  const suitableFor = asStringArray(v['suitableFor']);
  if (summary == null || !pros || !cons || !suitableFor) {
    throw jsonShapeError('advice');
  }
  return { summary, pros, cons, suitableFor };
}
