import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { z } from 'zod/v3';
import type { MarketAnalyticsDeps } from '../shared/types';

const DEFAULT_COLLECTION = 'listings';
const PROPERTY_TYPES = ['apartment', 'house', 'townhouse', 'villa', 'land', 'shophouse'] as const;

const MarketReportSchema = {
  district: z.string().describe('District name (e.g. "Quận 7")'),
  city: z.string().describe('City name (e.g. "Hồ Chí Minh")'),
  propertyType: z.enum(PROPERTY_TYPES).optional().describe('Filter by property type'),
  transactionType: z.enum(['sale', 'rent']).optional().describe('Filter by transaction type'),
};

const PriceTrendsSchema = {
  district: z.string().optional().describe('District name (omit for city-wide)'),
  city: z.string().describe('City name'),
  propertyType: z.enum(PROPERTY_TYPES).optional(),
  transactionType: z.enum(['sale', 'rent']).optional(),
  months: z.number().int().min(1).max(24).default(6).describe('Months to look back'),
};

const DistrictComparisonSchema = {
  city: z.string().describe('City name'),
  districts: z.array(z.string()).min(2).max(10).describe('District names to compare'),
  propertyType: z.enum(PROPERTY_TYPES).optional(),
  transactionType: z.enum(['sale', 'rent']).optional(),
};

export function createMarketAnalyticsServer(deps: MarketAnalyticsDeps): McpServer {
  const collectionName = deps.collectionName ?? DEFAULT_COLLECTION;
  const client = deps.typesenseClient;

  const server = new McpServer({
    name: 'goodgo-market-analytics',
    version: '0.1.0',
  });

  server.tool(
    'market_report',
    'Generate a market report for a district/city with prices, listing counts, and distribution.',
    MarketReportSchema,
    async (params: z.infer<z.ZodObject<typeof MarketReportSchema>>) => {
      const filters: string[] = [
        'status:=active',
        `district:=${params.district}`,
        `city:=${params.city}`,
      ];
      if (params.propertyType) filters.push(`propertyType:=${params.propertyType}`);
      if (params.transactionType) filters.push(`transactionType:=${params.transactionType}`);

      const result = await client
        .collections(collectionName)
        .documents()
        .search({
          q: '*',
          query_by: 'title',
          filter_by: filters.join(' && '),
          sort_by: 'priceVND:asc',
          per_page: 250,
          page: 1,
        });

      const docs = (result.hits ?? []).map((h) => h.document as Record<string, unknown>);
      const totalListings = (result.found as number) ?? 0;

      if (docs.length === 0) {
        return {
          content: [{
            type: 'text' as const,
            text: JSON.stringify({
              district: params.district,
              city: params.city,
              totalListings: 0,
              message: 'No listings found for the specified criteria.',
            }, null, 2),
          }],
        };
      }

      const prices = docs.map((d) => d['priceVND'] as number);
      const areas = docs.map((d) => d['areaM2'] as number);
      const pricesPerM2 = docs
        .map((d) => d['pricePerM2'] as number | undefined)
        .filter((v): v is number => v != null && v > 0);

      const sorted = [...prices].sort((a, b) => a - b);
      const mid = Math.floor(sorted.length / 2);
      const medianPrice = sorted.length % 2 === 0
        ? (sorted[mid - 1]! + sorted[mid]!) / 2
        : sorted[mid]!;

      const maxPrice = sorted[sorted.length - 1]!;
      const bucketSize = maxPrice > 10_000_000_000 ? 2_000_000_000 : maxPrice > 1_000_000_000 ? 500_000_000 : 100_000_000;
      const buckets = new Map<string, number>();
      for (const price of prices) {
        const bucketStart = Math.floor(price / bucketSize) * bucketSize;
        const label = `${formatVND(bucketStart)}-${formatVND(bucketStart + bucketSize)}`;
        buckets.set(label, (buckets.get(label) ?? 0) + 1);
      }

      const report = {
        district: params.district,
        city: params.city,
        propertyType: params.propertyType ?? 'all',
        transactionType: params.transactionType ?? 'all',
        totalListings,
        sampleSize: docs.length,
        averagePriceVND: Math.round(prices.reduce((a, b) => a + b, 0) / prices.length),
        medianPriceVND: Math.round(medianPrice),
        averagePricePerM2: pricesPerM2.length > 0
          ? Math.round(pricesPerM2.reduce((a, b) => a + b, 0) / pricesPerM2.length)
          : null,
        averageAreaM2: Math.round((areas.reduce((a, b) => a + b, 0) / areas.length) * 100) / 100,
        priceRange: { min: sorted[0], max: sorted[sorted.length - 1] },
        priceDistribution: Array.from(buckets.entries()).map(([range, count]) => ({ range, count })),
      };

      return {
        content: [{ type: 'text' as const, text: JSON.stringify(report, null, 2) }],
      };
    },
  );

  server.tool(
    'price_trends',
    'Analyze price trends by grouping listings by published date for a given area.',
    PriceTrendsSchema,
    async (params: z.infer<z.ZodObject<typeof PriceTrendsSchema>>) => {
      const filters: string[] = ['status:=active'];
      if (params.district) filters.push(`district:=${params.district}`);
      filters.push(`city:=${params.city}`);
      if (params.propertyType) filters.push(`propertyType:=${params.propertyType}`);
      if (params.transactionType) filters.push(`transactionType:=${params.transactionType}`);

      const cutoffTs = Math.floor(Date.now() / 1000) - params.months * 30 * 24 * 3600;
      filters.push(`publishedAt:>=${cutoffTs}`);

      const result = await client
        .collections(collectionName)
        .documents()
        .search({
          q: '*',
          query_by: 'title',
          filter_by: filters.join(' && '),
          sort_by: 'publishedAt:asc',
          per_page: 250,
          page: 1,
        });

      const docs = (result.hits ?? []).map((h) => h.document as Record<string, unknown>);

      const monthlyData = new Map<string, { total: number; count: number }>();
      for (const doc of docs) {
        const ts = doc['publishedAt'] as number;
        const date = new Date(ts * 1000);
        const label = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`;
        const existing = monthlyData.get(label) ?? { total: 0, count: 0 };
        existing.total += doc['priceVND'] as number;
        existing.count += 1;
        monthlyData.set(label, existing);
      }

      const dataPoints = Array.from(monthlyData.entries())
        .sort(([a], [b]) => a.localeCompare(b))
        .map(([label, data]) => ({
          month: label,
          avgPrice: Math.round(data.total / data.count),
          listingCount: data.count,
        }));

      return {
        content: [{
          type: 'text' as const,
          text: JSON.stringify({
            district: params.district ?? 'all',
            city: params.city,
            propertyType: params.propertyType ?? 'all',
            periodMonths: params.months,
            totalListings: (result.found as number) ?? 0,
            dataPoints,
          }, null, 2),
        }],
      };
    },
  );

  server.tool(
    'district_comparison',
    'Compare real estate metrics across multiple districts in a city.',
    DistrictComparisonSchema,
    async (params: z.infer<z.ZodObject<typeof DistrictComparisonSchema>>) => {
      const results = await Promise.all(
        params.districts.map(async (district) => {
          const filters: string[] = [
            'status:=active',
            `district:=${district}`,
            `city:=${params.city}`,
          ];
          if (params.propertyType) filters.push(`propertyType:=${params.propertyType}`);
          if (params.transactionType) filters.push(`transactionType:=${params.transactionType}`);

          const res = await client
            .collections(collectionName)
            .documents()
            .search({
              q: '*',
              query_by: 'title',
              filter_by: filters.join(' && '),
              per_page: 250,
              page: 1,
            });

          const docs = (res.hits ?? []).map((h) => h.document as Record<string, unknown>);
          const prices = docs.map((d) => d['priceVND'] as number);
          const areas = docs.map((d) => d['areaM2'] as number);

          return {
            district,
            totalListings: (res.found as number) ?? 0,
            avgPriceVND: prices.length > 0
              ? Math.round(prices.reduce((a, b) => a + b, 0) / prices.length)
              : null,
            avgAreaM2: areas.length > 0
              ? Math.round((areas.reduce((a, b) => a + b, 0) / areas.length) * 100) / 100
              : null,
            avgPricePerM2: prices.length > 0 && areas.length > 0
              ? Math.round(prices.reduce((a, b) => a + b, 0) / areas.reduce((a, b) => a + b, 0))
              : null,
          };
        }),
      );

      return {
        content: [{
          type: 'text' as const,
          text: JSON.stringify({
            city: params.city,
            propertyType: params.propertyType ?? 'all',
            districts: results,
          }, null, 2),
        }],
      };
    },
  );

  return server;
}

function formatVND(amount: number): string {
  if (amount >= 1_000_000_000) return `${(amount / 1_000_000_000).toFixed(1)} tỷ`;
  if (amount >= 1_000_000) return `${(amount / 1_000_000).toFixed(0)} triệu`;
  return `${amount.toLocaleString()} VND`;
}
