import { describe, it, expect, vi, beforeEach } from 'vitest';
import { createMarketAnalyticsServer } from '../market-analytics/market-analytics.server';
import type { MarketAnalyticsDeps } 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>): MarketAnalyticsDeps {
  return {
    typesenseClient: client as unknown as MarketAnalyticsDeps['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 createMarketAnalyticsServer>, 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 now = Math.floor(Date.now() / 1000);
const oneMonthAgo = now - 30 * 24 * 3600;
const twoMonthsAgo = now - 60 * 24 * 3600;

const makeListingDoc = (overrides: Record<string, unknown> = {}) => ({
  listingId: 'lst-001',
  title: 'Căn hộ',
  propertyType: 'apartment',
  transactionType: 'sale',
  priceVND: 3_000_000_000,
  pricePerM2: 42_857_143,
  areaM2: 70,
  district: 'Quận 7',
  city: 'Hồ Chí Minh',
  publishedAt: now,
  status: 'active',
  ...overrides,
});

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

  beforeEach(() => {
    client = createMockClient();
    server = createMarketAnalyticsServer(makeDeps(client));
  });

  describe('market_report', () => {
    it('generates report with price stats and distribution', async () => {
      const docs = [
        makeListingDoc({ priceVND: 2_000_000_000, areaM2: 50, pricePerM2: 40_000_000 }),
        makeListingDoc({ priceVND: 3_000_000_000, areaM2: 70, pricePerM2: 42_857_143 }),
        makeListingDoc({ priceVND: 5_000_000_000, areaM2: 100, pricePerM2: 50_000_000 }),
      ];
      client._search.mockResolvedValue(makeHits(docs));

      const handler = getToolHandler(server, 'market_report');
      const result = await handler({
        district: 'Quận 7',
        city: 'Hồ Chí Minh',
      });
      const data = parseResult(result);

      expect(data.district).toBe('Quận 7');
      expect(data.city).toBe('Hồ Chí Minh');
      expect(data.totalListings).toBe(3);
      expect(data.medianPriceVND).toBe(3_000_000_000);
      expect(data.priceDistribution).toBeDefined();
      expect(Array.isArray(data.priceDistribution)).toBe(true);
    });

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

      await handler({
        district: 'Quận 2',
        city: 'Hồ Chí Minh',
        propertyType: 'villa',
        transactionType: 'sale',
      });

      const filterBy = client._search.mock.calls[0][0].filter_by as string;
      expect(filterBy).toContain('district:=Quận 2');
      expect(filterBy).toContain('propertyType:=villa');
      expect(filterBy).toContain('transactionType:=sale');
    });

    it('returns message when no listings found', async () => {
      client._search.mockResolvedValue(makeHits([]));
      const handler = getToolHandler(server, 'market_report');
      const result = await handler({ district: 'Quận 99', city: 'Hồ Chí Minh' });
      const data = parseResult(result);

      expect(data.totalListings).toBe(0);
      expect(data.message).toContain('No listings found');
    });

    it('calculates median correctly for even count', async () => {
      const docs = [
        makeListingDoc({ priceVND: 2_000_000_000 }),
        makeListingDoc({ priceVND: 4_000_000_000 }),
      ];
      client._search.mockResolvedValue(makeHits(docs));

      const handler = getToolHandler(server, 'market_report');
      const result = await handler({ district: 'Quận 7', city: 'Hồ Chí Minh' });
      const data = parseResult(result);

      expect(data.medianPriceVND).toBe(3_000_000_000);
    });
  });

  describe('price_trends', () => {
    it('groups listings by month', async () => {
      const docs = [
        makeListingDoc({ priceVND: 3_000_000_000, publishedAt: oneMonthAgo }),
        makeListingDoc({ priceVND: 3_200_000_000, publishedAt: oneMonthAgo + 86400 }),
        makeListingDoc({ priceVND: 3_500_000_000, publishedAt: twoMonthsAgo }),
      ];
      client._search.mockResolvedValue(makeHits(docs));

      const handler = getToolHandler(server, 'price_trends');
      const result = await handler({ city: 'Hồ Chí Minh', months: 6 });
      const data = parseResult(result);

      expect(data.city).toBe('Hồ Chí Minh');
      expect(data.periodMonths).toBe(6);
      expect(Array.isArray(data.dataPoints)).toBe(true);
      expect((data.dataPoints as unknown[]).length).toBeGreaterThan(0);
    });

    it('applies time cutoff filter', async () => {
      client._search.mockResolvedValue(makeHits([]));
      const handler = getToolHandler(server, 'price_trends');

      await handler({ city: 'Hồ Chí Minh', months: 3 });

      const filterBy = client._search.mock.calls[0][0].filter_by as string;
      expect(filterBy).toMatch(/publishedAt:>=\d+/);
    });

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

      await handler({ district: 'Quận 7', city: 'Hồ Chí Minh', months: 6 });

      const filterBy = client._search.mock.calls[0][0].filter_by as string;
      expect(filterBy).toContain('district:=Quận 7');
    });
  });

  describe('district_comparison', () => {
    it('compares multiple districts', async () => {
      const q7Docs = [
        makeListingDoc({ district: 'Quận 7', priceVND: 3_000_000_000, areaM2: 70 }),
        makeListingDoc({ district: 'Quận 7', priceVND: 4_000_000_000, areaM2: 80 }),
      ];
      const q2Docs = [
        makeListingDoc({ district: 'Quận 2', priceVND: 5_000_000_000, areaM2: 100 }),
      ];

      client._search
        .mockResolvedValueOnce(makeHits(q7Docs))
        .mockResolvedValueOnce(makeHits(q2Docs));

      const handler = getToolHandler(server, 'district_comparison');
      const result = await handler({
        city: 'Hồ Chí Minh',
        districts: ['Quận 7', 'Quận 2'],
      });
      const data = parseResult(result);

      expect(data.city).toBe('Hồ Chí Minh');
      const districts = data.districts as { district: string; totalListings: number; avgPriceVND: number }[];
      expect(districts).toHaveLength(2);
      expect(districts[0].district).toBe('Quận 7');
      expect(districts[0].totalListings).toBe(2);
      expect(districts[0].avgPriceVND).toBe(3_500_000_000);
      expect(districts[1].district).toBe('Quận 2');
      expect(districts[1].totalListings).toBe(1);
    });

    it('handles districts with no listings', async () => {
      client._search
        .mockResolvedValueOnce(makeHits([]))
        .mockResolvedValueOnce(makeHits([]));

      const handler = getToolHandler(server, 'district_comparison');
      const result = await handler({
        city: 'Hồ Chí Minh',
        districts: ['Empty1', 'Empty2'],
      });
      const data = parseResult(result);
      const districts = data.districts as { avgPriceVND: number | null }[];

      expect(districts[0].avgPriceVND).toBeNull();
      expect(districts[1].avgPriceVND).toBeNull();
    });
  });
});
