import { describe, it, expect, vi, beforeEach } from 'vitest';
import { createPropertySearchServer } from '../property-search/property-search.server';
import type { PropertySearchDeps } from '../shared/types';

type ToolResult = { content: { type: string; text: string }[]; isError?: boolean };

function createMockClient(searchResult: unknown = { hits: [], found: 0 }) {
  const search = vi.fn().mockResolvedValue(searchResult);
  return {
    collections: vi.fn().mockReturnValue({
      documents: vi.fn().mockReturnValue({ search }),
    }),
    _search: search,
  };
}

function makeDeps(client: ReturnType<typeof createMockClient>): PropertySearchDeps {
  return {
    typesenseClient: client as unknown as PropertySearchDeps['typesenseClient'],
    collectionName: 'test-listings',
  };
}

function makeHits(docs: Record<string, unknown>[]) {
  return { hits: docs.map((d) => ({ document: d })), found: docs.length, search_time_ms: 5 };
}

function getToolHandler(server: ReturnType<typeof createPropertySearchServer>, name: string) {
  const tools = (server as unknown as { _registeredTools: Record<string, { handler: (p: unknown) => Promise<ToolResult> }> })._registeredTools;
  const entry = tools[name];
  if (!entry) throw new Error(`Tool "${name}" not found`);
  return entry.handler;
}

function parseResult(result: ToolResult) {
  return JSON.parse(result.content[0].text) as Record<string, unknown>;
}

const sampleDoc = {
  listingId: 'lst-001',
  title: 'Căn hộ 2PN Quận 7',
  propertyType: 'apartment',
  transactionType: 'sale',
  priceVND: 3_500_000_000,
  pricePerM2: 50_000_000,
  areaM2: 70,
  bedrooms: 2,
  bathrooms: 2,
  address: '123 Nguyễn Hữu Thọ',
  district: 'Quận 7',
  city: 'Hồ Chí Minh',
};

describe('PropertySearchServer', () => {
  let client: ReturnType<typeof createMockClient>;
  let server: ReturnType<typeof createPropertySearchServer>;

  beforeEach(() => {
    client = createMockClient(makeHits([sampleDoc]));
    server = createPropertySearchServer(makeDeps(client));
  });

  it('creates server with correct name', () => {
    expect(server).toBeDefined();
  });

  describe('search_properties', () => {
    it('always filters by uppercase ACTIVE status matching Prisma enum', async () => {
      client._search.mockResolvedValue(makeHits([]));
      const handler = getToolHandler(server, 'search_properties');

      await handler({ query: '*', page: 1, perPage: 20 });

      const filterBy = client._search.mock.calls[0][0].filter_by as string;
      expect(filterBy).toContain('status:=ACTIVE');
      expect(filterBy).not.toContain('status:=active');
    });

    it('searches with basic query', async () => {
      const handler = getToolHandler(server, 'search_properties');
      const result = await handler({ query: 'căn hộ Quận 7', page: 1, perPage: 20 });

      expect(client.collections).toHaveBeenCalledWith('test-listings');
      expect(client._search).toHaveBeenCalledWith(
        expect.objectContaining({
          q: 'căn hộ Quận 7',
          query_by: 'title,description,address,district,city,projectName',
        }),
      );

      const data = parseResult(result);
      expect(data.totalFound).toBe(1);
      expect(data.results).toHaveLength(1);
      expect((data.results as Record<string, unknown>[])[0].listingId).toBe('lst-001');
    });

    it('applies all filters correctly', async () => {
      client._search.mockResolvedValue(makeHits([]));
      const handler = getToolHandler(server, 'search_properties');

      await handler({
        query: '*',
        propertyType: 'apartment',
        transactionType: 'sale',
        minPrice: 1_000_000_000,
        maxPrice: 5_000_000_000,
        bedrooms: 2,
        district: 'Quận 7',
        city: 'Hồ Chí Minh',
        page: 1,
        perPage: 20,
      });

      const filterBy = client._search.mock.calls[0][0].filter_by as string;
      expect(filterBy).toContain('status:=ACTIVE');
      expect(filterBy).toContain('propertyType:=apartment');
      expect(filterBy).toContain('transactionType:=sale');
      expect(filterBy).toContain('priceVND:>=1000000000');
      expect(filterBy).toContain('priceVND:<=5000000000');
      expect(filterBy).toContain('bedrooms:=2');
      expect(filterBy).toContain('district:=Quận 7');
      expect(filterBy).toContain('city:=Hồ Chí Minh');
    });

    it('applies geo filter when coordinates provided', async () => {
      client._search.mockResolvedValue(makeHits([]));
      const handler = getToolHandler(server, 'search_properties');

      await handler({
        query: '*',
        latitude: 10.7297,
        longitude: 106.7253,
        radiusKm: 5,
        page: 1,
        perPage: 20,
      });

      const filterBy = client._search.mock.calls[0][0].filter_by as string;
      expect(filterBy).toContain('location:(10.7297, 106.7253, 5 km)');
    });

    it('sorts by price ascending', async () => {
      client._search.mockResolvedValue(makeHits([]));
      const handler = getToolHandler(server, 'search_properties');

      await handler({ query: '*', sortBy: 'price_asc', page: 1, perPage: 20 });
      expect(client._search.mock.calls[0][0].sort_by).toBe('priceVND:asc');
    });

    it('sorts by price descending', async () => {
      client._search.mockResolvedValue(makeHits([]));
      const handler = getToolHandler(server, 'search_properties');

      await handler({ query: '*', sortBy: 'price_desc', page: 1, perPage: 20 });
      expect(client._search.mock.calls[0][0].sort_by).toBe('priceVND:desc');
    });

    it('sorts by distance when coordinates present', async () => {
      client._search.mockResolvedValue(makeHits([]));
      const handler = getToolHandler(server, 'search_properties');

      await handler({
        query: '*',
        sortBy: 'distance',
        latitude: 10.73,
        longitude: 106.73,
        radiusKm: 3,
        page: 1,
        perPage: 20,
      });

      expect(client._search.mock.calls[0][0].sort_by).toBe('location(10.73, 106.73):asc');
    });

    it('sorts by relevance when query present', async () => {
      client._search.mockResolvedValue(makeHits([]));
      const handler = getToolHandler(server, 'search_properties');

      await handler({ query: 'villa', sortBy: 'relevance', page: 1, perPage: 20 });
      expect(client._search.mock.calls[0][0].sort_by).toBe('_text_match:desc,publishedAt:desc');
    });

    it('returns empty results gracefully', async () => {
      client._search.mockResolvedValue(makeHits([]));
      const handler = getToolHandler(server, 'search_properties');
      const result = await handler({ query: 'nonexistent', page: 1, perPage: 20 });
      const data = parseResult(result);

      expect(data.totalFound).toBe(0);
      expect(data.results).toHaveLength(0);
    });
  });

  describe('compare_properties', () => {
    it('compares multiple properties with stats', async () => {
      const docs = [
        { ...sampleDoc, listingId: 'lst-001', priceVND: 3_000_000_000, areaM2: 60, pricePerM2: 50_000_000 },
        { ...sampleDoc, listingId: 'lst-002', priceVND: 5_000_000_000, areaM2: 90, pricePerM2: 55_555_556 },
      ];
      client._search.mockResolvedValue(makeHits(docs));

      const handler = getToolHandler(server, 'compare_properties');
      const result = await handler({ listingIds: ['lst-001', 'lst-002'] });
      const data = parseResult(result) as Record<string, unknown>;

      expect(data.properties).toHaveLength(2);
      const comparison = data.comparison as Record<string, Record<string, number>>;
      expect(comparison.priceRange.min).toBe(3_000_000_000);
      expect(comparison.priceRange.max).toBe(5_000_000_000);
      expect(comparison.areaRange.min).toBe(60);
      expect(comparison.areaRange.max).toBe(90);
    });

    it('returns error when fewer than 2 listings found', async () => {
      client._search.mockResolvedValue(makeHits([sampleDoc]));
      const handler = getToolHandler(server, 'compare_properties');
      const result = await handler({ listingIds: ['lst-001', 'lst-999'] });

      expect(result.isError).toBe(true);
      const data = parseResult(result);
      expect(data.error).toContain('Need at least 2');
    });
  });

  describe('get_property_details', () => {
    it('returns full listing document', async () => {
      const handler = getToolHandler(server, 'get_property_details');
      const result = await handler({ listingId: 'lst-001' });
      const data = parseResult(result);

      expect(data.listingId).toBe('lst-001');
      expect(data.title).toBe('Căn hộ 2PN Quận 7');
    });

    it('returns error when listing not found', async () => {
      client._search.mockResolvedValue(makeHits([]));
      const handler = getToolHandler(server, 'get_property_details');
      const result = await handler({ listingId: 'nonexistent' });

      expect(result.isError).toBe(true);
      expect(parseResult(result).error).toBe('Listing not found');
    });
  });
});
