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

const DEFAULT_COLLECTION = 'listings';

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

const SearchPropertiesSchema = {
  query: z.string().describe('Natural language search query (e.g. "căn hộ 2 phòng ngủ Quận 7")'),
  propertyType: z.enum(PROPERTY_TYPES).optional().describe('Filter by property type'),
  transactionType: z.enum(['sale', 'rent']).optional().describe('Filter by transaction type'),
  minPrice: z.number().optional().describe('Minimum price in VND'),
  maxPrice: z.number().optional().describe('Maximum price in VND'),
  bedrooms: z.number().int().optional().describe('Exact number of bedrooms'),
  district: z.string().optional().describe('Filter by district name'),
  city: z.string().optional().describe('Filter by city name'),
  latitude: z.number().optional().describe('Center latitude for geo search'),
  longitude: z.number().optional().describe('Center longitude for geo search'),
  radiusKm: z.number().optional().describe('Radius in km for geo search'),
  sortBy: z.enum(['relevance', 'price_asc', 'price_desc', 'date_desc', 'distance']).optional(),
  page: z.number().int().min(1).default(1),
  perPage: z.number().int().min(1).max(50).default(20),
};

const ComparePropertiesSchema = {
  listingIds: z.array(z.string()).min(2).max(10).describe('Array of listing IDs to compare'),
};

const GetPropertyDetailsSchema = {
  listingId: z.string().describe('The listing ID to retrieve'),
};

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

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

  server.tool(
    'search_properties',
    'Search property listings using natural language queries with filters.',
    SearchPropertiesSchema,
    async (params: z.infer<z.ZodObject<typeof SearchPropertiesSchema>>) => {
      const filters: string[] = ['status:=ACTIVE'];

      if (params.propertyType) filters.push(`propertyType:=${params.propertyType}`);
      if (params.transactionType) filters.push(`transactionType:=${params.transactionType}`);
      if (params.minPrice != null) filters.push(`priceVND:>=${params.minPrice}`);
      if (params.maxPrice != null) filters.push(`priceVND:<=${params.maxPrice}`);
      if (params.bedrooms != null) filters.push(`bedrooms:=${params.bedrooms}`);
      if (params.district) filters.push(`district:=${params.district}`);
      if (params.city) filters.push(`city:=${params.city}`);

      let filterBy = filters.join(' && ');

      if (params.latitude != null && params.longitude != null && params.radiusKm) {
        const geoFilter = `location:(${params.latitude}, ${params.longitude}, ${params.radiusKm} km)`;
        filterBy = `${filterBy} && ${geoFilter}`;
      }

      let sortBy = 'publishedAt:desc';
      if (params.sortBy === 'price_asc') sortBy = 'priceVND:asc';
      else if (params.sortBy === 'price_desc') sortBy = 'priceVND:desc';
      else if (params.sortBy === 'date_desc') sortBy = 'publishedAt:desc';
      else if (params.sortBy === 'distance' && params.latitude != null && params.longitude != null) {
        sortBy = `location(${params.latitude}, ${params.longitude}):asc`;
      } else if (params.sortBy === 'relevance' && params.query) {
        sortBy = '_text_match:desc,publishedAt:desc';
      }

      const result = await client
        .collections(collectionName)
        .documents()
        .search({
          q: params.query || '*',
          query_by: 'title,description,address,district,city,projectName',
          query_by_weights: '5,3,2,2,1,2',
          filter_by: filterBy,
          sort_by: sortBy,
          page: params.page,
          per_page: params.perPage,
          highlight_full_fields: 'title,description',
        });

      const hits = (result.hits ?? []).map((hit) => {
        const doc = hit.document as Record<string, unknown>;
        return {
          listingId: doc['listingId'],
          title: doc['title'],
          propertyType: doc['propertyType'],
          transactionType: doc['transactionType'],
          priceVND: doc['priceVND'],
          pricePerM2: doc['pricePerM2'] ?? null,
          areaM2: doc['areaM2'],
          bedrooms: doc['bedrooms'] ?? null,
          bathrooms: doc['bathrooms'] ?? null,
          address: doc['address'],
          district: doc['district'],
          city: doc['city'],
        };
      });

      return {
        content: [{
          type: 'text' as const,
          text: JSON.stringify({
            totalFound: result.found ?? 0,
            page: params.page,
            perPage: params.perPage,
            searchTimeMs: result.search_time_ms ?? 0,
            results: hits,
          }, null, 2),
        }],
      };
    },
  );

  server.tool(
    'compare_properties',
    'Compare multiple property listings side by side with price and area analysis.',
    ComparePropertiesSchema,
    async (params: z.infer<z.ZodObject<typeof ComparePropertiesSchema>>) => {
      const filterBy = `listingId:[${params.listingIds.join(',')}]`;

      const result = await client
        .collections(collectionName)
        .documents()
        .search({
          q: '*',
          query_by: 'title',
          filter_by: filterBy,
          per_page: params.listingIds.length,
        });

      const properties: PropertySummary[] = (result.hits ?? []).map((hit) => {
        const doc = hit.document as Record<string, unknown>;
        return {
          listingId: doc['listingId'] as string,
          title: doc['title'] as string,
          propertyType: doc['propertyType'] as string,
          transactionType: doc['transactionType'] as string,
          priceVND: doc['priceVND'] as number,
          pricePerM2: (doc['pricePerM2'] as number) ?? null,
          areaM2: doc['areaM2'] as number,
          bedrooms: (doc['bedrooms'] as number) ?? null,
          bathrooms: (doc['bathrooms'] as number) ?? null,
          address: doc['address'] as string,
          district: doc['district'] as string,
          city: doc['city'] as string,
        };
      });

      if (properties.length < 2) {
        return {
          content: [{
            type: 'text' as const,
            text: JSON.stringify({ error: 'Need at least 2 valid listings to compare', found: properties.length }),
          }],
          isError: true,
        };
      }

      const prices = properties.map((p) => p.priceVND);
      const areas = properties.map((p) => p.areaM2);
      const pricesPerM2 = properties.map((p) => p.pricePerM2).filter((v): v is number => v != null);

      const comparison = {
        properties,
        comparison: {
          priceRange: {
            min: Math.min(...prices),
            max: Math.max(...prices),
            avg: Math.round(prices.reduce((a, b) => a + b, 0) / prices.length),
          },
          areaRange: {
            min: Math.min(...areas),
            max: Math.max(...areas),
            avg: Math.round((areas.reduce((a, b) => a + b, 0) / areas.length) * 100) / 100,
          },
          pricePerM2Range: pricesPerM2.length > 0 ? {
            min: Math.min(...pricesPerM2),
            max: Math.max(...pricesPerM2),
            avg: Math.round(pricesPerM2.reduce((a, b) => a + b, 0) / pricesPerM2.length),
          } : null,
        },
      };

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

  server.tool(
    'get_property_details',
    'Get detailed information about a specific property listing by its ID.',
    GetPropertyDetailsSchema,
    async (params: z.infer<z.ZodObject<typeof GetPropertyDetailsSchema>>) => {
      const result = await client
        .collections(collectionName)
        .documents()
        .search({
          q: '*',
          query_by: 'title',
          filter_by: `listingId:=${params.listingId}`,
          per_page: 1,
        });

      const hit = result.hits?.[0];
      if (!hit) {
        return {
          content: [{ type: 'text' as const, text: JSON.stringify({ error: 'Listing not found' }) }],
          isError: true,
        };
      }

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

  return server;
}
