import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { createValuationServer } from '../valuation/valuation.server';

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

function getToolHandler(server: ReturnType<typeof createValuationServer>, 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 BASE_URL = 'http://localhost:8000';

const avmResponse = {
  estimated_price_vnd: 3_500_000_000,
  confidence: 0.85,
  price_per_m2: 50_000_000,
  price_range_low: 3_000_000_000,
  price_range_high: 4_000_000_000,
};

const featureResponse = {
  features: {
    area: 75,
    district: 'Quận 7',
    city: 'Hồ Chí Minh',
    property_type: 'apartment',
    bedrooms: 2,
    bathrooms: 2,
    floors: null,
    frontage: null,
    road_width: null,
    price_mentioned: 3_500_000_000,
    has_legal_paper: true,
  },
  tokens: ['căn', 'hộ', '2', 'phòng', 'ngủ'],
  entities: [{ text: 'Quận 7', label: 'LOCATION' }],
};

describe('ValuationServer', () => {
  let server: ReturnType<typeof createValuationServer>;
  let fetchMock: ReturnType<typeof vi.fn>;

  beforeEach(() => {
    fetchMock = vi.fn();
    vi.stubGlobal('fetch', fetchMock);
    server = createValuationServer({ aiServiceBaseUrl: BASE_URL });
  });

  afterEach(() => {
    vi.restoreAllMocks();
  });

  describe('estimate_property_value', () => {
    it('returns valuation estimate on success', async () => {
      fetchMock.mockResolvedValue({
        ok: true,
        json: async () => avmResponse,
      });

      const handler = getToolHandler(server, 'estimate_property_value');
      const result = await handler({
        area: 70,
        district: 'Quận 7',
        city: 'Hồ Chí Minh',
        propertyType: 'apartment',
        bedrooms: 2,
        bathrooms: 2,
        floors: 0,
        frontage: 0,
        roadWidth: 0,
        hasLegalPaper: true,
      });

      expect(fetchMock).toHaveBeenCalledWith(
        `${BASE_URL}/avm/predict`,
        expect.objectContaining({
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
        }),
      );

      const data = parseResult(result);
      expect(data.estimatedPriceVND).toBe(3_500_000_000);
      expect(data.confidence).toBe(0.85);
      expect(data.pricePerM2).toBe(50_000_000);
      expect(data.priceRangeLow).toBe(3_000_000_000);
      expect(data.priceRangeHigh).toBe(4_000_000_000);
    });

    it('sends correct body to AVM service', async () => {
      fetchMock.mockResolvedValue({ ok: true, json: async () => avmResponse });

      const handler = getToolHandler(server, 'estimate_property_value');
      await handler({
        area: 100,
        district: 'Quận 2',
        city: 'Hồ Chí Minh',
        propertyType: 'villa',
        bedrooms: 4,
        bathrooms: 3,
        floors: 2,
        frontage: 6,
        roadWidth: 12,
        yearBuilt: 2020,
        hasLegalPaper: true,
      });

      const body = JSON.parse(fetchMock.mock.calls[0][1].body) as Record<string, unknown>;
      expect(body.area).toBe(100);
      expect(body.district).toBe('Quận 2');
      expect(body.property_type).toBe('villa');
      expect(body.bedrooms).toBe(4);
      expect(body.year_built).toBe(2020);
      expect(body.has_legal_paper).toBe(true);
      expect(body.road_width).toBe(12);
    });

    it('returns error when AVM service fails', async () => {
      fetchMock.mockResolvedValue({
        ok: false,
        status: 500,
        text: async () => 'Internal Server Error',
      });

      const handler = getToolHandler(server, 'estimate_property_value');
      const result = await handler({
        area: 70,
        district: 'Quận 7',
        city: 'Hồ Chí Minh',
        propertyType: 'apartment',
        bedrooms: 0,
        bathrooms: 0,
        floors: 0,
        frontage: 0,
        roadWidth: 0,
        hasLegalPaper: true,
      });

      expect(result.isError).toBe(true);
      const data = parseResult(result);
      expect(data.error).toContain('AVM service error (500)');
    });
  });

  describe('extract_listing_features', () => {
    it('extracts features from Vietnamese text', async () => {
      fetchMock.mockResolvedValue({
        ok: true,
        json: async () => featureResponse,
      });

      const handler = getToolHandler(server, 'extract_listing_features');
      const result = await handler({
        text: 'Bán căn hộ 2 phòng ngủ 75m2 Quận 7, sổ hồng, giá 3.5 tỷ',
      });

      expect(fetchMock).toHaveBeenCalledWith(
        `${BASE_URL}/avm/extract-features`,
        expect.objectContaining({ method: 'POST' }),
      );

      const data = parseResult(result);
      const features = data.features as Record<string, unknown>;
      expect(features.area).toBe(75);
      expect(features.district).toBe('Quận 7');
      expect(features.bedrooms).toBe(2);
      expect(features.hasLegalPaper).toBe(true);
      expect((data.entities as unknown[]).length).toBeGreaterThan(0);
    });

    it('returns error when extraction fails', async () => {
      fetchMock.mockResolvedValue({
        ok: false,
        status: 422,
        text: async () => 'Unprocessable Entity',
      });

      const handler = getToolHandler(server, 'extract_listing_features');
      const result = await handler({ text: '' });

      expect(result.isError).toBe(true);
      const data = parseResult(result);
      expect(data.error).toContain('Feature extraction error (422)');
    });
  });

  describe('batch_valuation', () => {
    it('valuates multiple properties', async () => {
      fetchMock.mockResolvedValue({
        ok: true,
        json: async () => avmResponse,
      });

      const handler = getToolHandler(server, 'batch_valuation');
      const result = await handler({
        properties: [
          {
            area: 70,
            district: 'Quận 7',
            city: 'Hồ Chí Minh',
            propertyType: 'apartment',
            bedrooms: 2,
            bathrooms: 2,
            floors: 0,
            frontage: 0,
            roadWidth: 0,
            hasLegalPaper: true,
          },
          {
            area: 100,
            district: 'Quận 2',
            city: 'Hồ Chí Minh',
            propertyType: 'villa',
            bedrooms: 4,
            bathrooms: 3,
            floors: 2,
            frontage: 5,
            roadWidth: 10,
            hasLegalPaper: true,
          },
        ],
      });

      expect(fetchMock).toHaveBeenCalledTimes(2);

      const data = parseResult(result);
      const valuations = data.valuations as { index: number; valuation: Record<string, unknown> }[];
      expect(valuations).toHaveLength(2);
      expect(valuations[0].index).toBe(0);
      expect(valuations[0].valuation.estimatedPriceVND).toBe(3_500_000_000);
      expect(valuations[1].index).toBe(1);
    });

    it('handles partial failures gracefully', async () => {
      fetchMock
        .mockResolvedValueOnce({ ok: true, json: async () => avmResponse })
        .mockResolvedValueOnce({ ok: false, status: 500, text: async () => 'Error' });

      const handler = getToolHandler(server, 'batch_valuation');
      const result = await handler({
        properties: [
          { area: 70, district: 'Q7', city: 'HCM', propertyType: 'apartment', bedrooms: 0, bathrooms: 0, floors: 0, frontage: 0, roadWidth: 0, hasLegalPaper: true },
          { area: 100, district: 'Q2', city: 'HCM', propertyType: 'villa', bedrooms: 0, bathrooms: 0, floors: 0, frontage: 0, roadWidth: 0, hasLegalPaper: true },
        ],
      });

      const data = parseResult(result);
      const valuations = data.valuations as { index: number; valuation?: unknown; error?: string }[];
      expect(valuations).toHaveLength(2);
      expect(valuations[0].valuation).toBeDefined();
      expect(valuations[1].error).toContain('AVM service error');
    });
  });
});
