/**
 * Integration test: verifies all MCP servers register correctly in McpRegistryService
 * and each tool is callable with valid response schemas.
 *
 * External HTTP calls (AI service, NestJS API) are mocked via globalThis.fetch.
 * Typesense is mocked at the client level.
 */
import type { Client as TypesenseClient } from 'typesense';
import { describe, it, expect, vi, beforeAll, afterAll } from 'vitest';
import { createIndustrialParksServer } from '../industrial-parks/industrial-parks.server';
import { createMarketAnalyticsServer } from '../market-analytics/market-analytics.server';
import { createPropertySearchServer } from '../property-search/property-search.server';
import { createReportsServer } from '../reports/reports.server';
import { createValuationServer } from '../valuation/valuation.server';

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

// ---------------------------------------------------------------------------
// Mocks — Typesense client
// ---------------------------------------------------------------------------
function createMockTypesenseClient(defaultHits: unknown[] = []) {
  const search = vi.fn().mockResolvedValue({
    hits: defaultHits.map((d) => ({ document: d })),
    found: defaultHits.length,
    search_time_ms: 2,
  });
  return {
    collections: vi.fn().mockReturnValue({
      documents: vi.fn().mockReturnValue({ search }),
    }),
    _search: search,
  };
}

// ---------------------------------------------------------------------------
// Mocks — fetch responses for each backend
// ---------------------------------------------------------------------------
const MOCK_RESPONSES: Record<string, unknown> = {
  '/industrial/analyze-location': {
    overall_score: 8.2,
    connectivity: {
      nearest_port: { name: 'Cảng Cát Lái', distanceKm: 22 },
      nearest_airport: { name: 'Tân Sơn Nhất', distanceKm: 28 },
      nearest_highway: { name: 'QL1A', distanceKm: 1.5 },
    },
    infrastructure: {
      power_availability: '110kV on-site',
      water_supply: 'Municipal',
      wastewater_treatment: 'Central WWTP',
      telecom: 'Fiber optic',
    },
    labor_market: {
      worker_pool_radius_30km: 450000,
      average_wage_usd: 290,
      nearby_universities: ['ĐH Bình Dương'],
    },
    incentives: ['CIT exemption 4 years'],
    risks: ['Flooding risk'],
  },
  '/industrial/estimate-rent': {
    estimated_rent_usd_m2: 4.5,
    pricing_unit: 'USD/m²/month',
    total_monthly_usd: 45000,
    total_lease_usd: 5400000,
    management_fee_usd_m2: 0.6,
    deposit_months: 3,
    market_comparison: {
      province_low: 3.0,
      province_high: 7.0,
      province_avg: 4.8,
    },
    breakdown: [
      { item: 'Base rent', amount: 38000 },
      { item: 'Management fee', amount: 6000 },
    ],
  },
  '/reports/generate': {
    report_id: 'rpt-int-001',
    report_type: 'market_overview',
    title: 'Báo cáo thị trường Q7',
    location: 'Quận 7, Hồ Chí Minh',
    generated_at: '2026-04-16T10:00:00Z',
    summary: 'Thị trường ổn định',
    sections: [{ title: 'Tổng quan', content: '...', charts: [] }],
    key_metrics: { avgPriceVND: 4_500_000_000 },
  },
  '/reports/macro-data': {
    province: 'Bình Dương',
    data: {
      gdp: [{ year: 2024, value: 20.1, unit: 'billion USD', yoy_change: 8.6 }],
    },
    highlights: ['GDP above national average'],
  },
};

function mockFetchForUrl(url: string): Response {
  for (const [path, body] of Object.entries(MOCK_RESPONSES)) {
    if (url.includes(path)) {
      return {
        ok: true,
        status: 200,
        json: async () => body,
        text: async () => JSON.stringify(body),
      } as unknown as Response;
    }
  }
  return {
    ok: false,
    status: 404,
    text: async () => 'Not found',
  } as unknown as Response;
}

// ---------------------------------------------------------------------------
// Industrial park sample document (for Typesense search results)
// ---------------------------------------------------------------------------
const SAMPLE_PARK = {
  parkId: 'park-int-001',
  name: 'KCN VSIP II-A',
  nameEn: 'VSIP II-A Industrial Park',
  developer: 'VSIP Group',
  province: 'Bình Dương',
  region: 'south',
  status: 'operational',
  totalAreaHa: 345,
  remainingAreaHa: 62,
  occupancyRate: 82,
  landRentUsdM2Year: 90,
  rbfRentUsdM2Month: 4.8,
  rbwRentUsdM2Month: 3.5,
  targetIndustries: ['electronics', 'automotive'],
  tenantCount: 85,
};

// ---------------------------------------------------------------------------
// Helper: extract tool handler from McpServer internal state
// ---------------------------------------------------------------------------
function getToolHandler(
  server: unknown,
  name: string,
): (params: unknown) => Promise<ToolResult> {
  const tools = (
    server as { _registeredTools: Record<string, { handler: (p: unknown) => Promise<ToolResult> }> }
  )._registeredTools;
  const entry = tools[name];
  if (!entry) {
    throw new Error(`Tool "${name}" not registered. Available: ${Object.keys(tools).join(', ')}`);
  }
  return entry.handler;
}

function parseToolResult(result: ToolResult): Record<string, unknown> {
  expect(result.content).toHaveLength(1);
  expect(result.content[0].type).toBe('text');
  return JSON.parse(result.content[0].text) as Record<string, unknown>;
}

// ---------------------------------------------------------------------------
// Integration tests
// ---------------------------------------------------------------------------
describe('MCP Integration: all servers and tools end-to-end', () => {
  const typesenseClient = createMockTypesenseClient([SAMPLE_PARK]);

  let industrialServer: ReturnType<typeof createIndustrialParksServer>;
  let reportsServer: ReturnType<typeof createReportsServer>;

  const fetchSpy = vi.spyOn(globalThis, 'fetch');

  beforeAll(() => {
    fetchSpy.mockImplementation(async (input: string | URL | Request) => {
      const url = typeof input === 'string' ? input : input instanceof URL ? input.toString() : input.url;
      return mockFetchForUrl(url);
    });

    industrialServer = createIndustrialParksServer({
      typesenseClient: typesenseClient as unknown as TypesenseClient,
      collectionName: 'industrial_parks',
      aiServiceBaseUrl: 'http://ai-service:8000',
    });

    reportsServer = createReportsServer({
      apiBaseUrl: 'http://api:3001/api/v1',
    });
  });

  afterAll(() => {
    fetchSpy.mockRestore();
  });

  // -----------------------------------------------------------------------
  // 1. Server factory tests — all 5 factories produce valid McpServer instances
  // -----------------------------------------------------------------------
  describe('server factories', () => {
    it('creates all 5 server instances without errors', () => {
      expect(industrialServer).toBeDefined();
      expect(reportsServer).toBeDefined();

      const propertySearch = createPropertySearchServer({
        typesenseClient: typesenseClient as unknown as TypesenseClient,
        collectionName: 'listings',
      });
      expect(propertySearch).toBeDefined();

      const marketAnalytics = createMarketAnalyticsServer({
        typesenseClient: typesenseClient as unknown as TypesenseClient,
        collectionName: 'listings',
      });
      expect(marketAnalytics).toBeDefined();

      const valuation = createValuationServer({
        aiServiceBaseUrl: 'http://ai-service:8000',
      });
      expect(valuation).toBeDefined();
    });
  });

  // -----------------------------------------------------------------------
  // 2. Industrial parks server — 3 tools
  // -----------------------------------------------------------------------
  describe('industrial-parks server', () => {
    it('search_industrial_parks: returns structured results from Typesense', async () => {
      const handler = getToolHandler(industrialServer, 'search_industrial_parks');
      const result = await handler({
        query: 'VSIP Bình Dương',
        page: 1,
        perPage: 20,
      });

      expect(result.isError).toBeFalsy();
      const data = parseToolResult(result);

      // Schema validation
      expect(data).toHaveProperty('totalFound');
      expect(data).toHaveProperty('page');
      expect(data).toHaveProperty('perPage');
      expect(data).toHaveProperty('searchTimeMs');
      expect(data).toHaveProperty('results');
      expect(typeof data.totalFound).toBe('number');

      const results = data.results as Record<string, unknown>[];
      expect(results.length).toBeGreaterThan(0);

      // Validate result item schema
      const item = results[0];
      expect(item).toHaveProperty('parkId');
      expect(item).toHaveProperty('name');
      expect(item).toHaveProperty('developer');
      expect(item).toHaveProperty('province');
      expect(item).toHaveProperty('region');
      expect(item).toHaveProperty('status');
      expect(item).toHaveProperty('totalAreaHa');
      expect(item).toHaveProperty('remainingAreaHa');
      expect(item).toHaveProperty('occupancyRate');
      expect(item).toHaveProperty('landRentUsdM2Year');
      expect(item).toHaveProperty('targetIndustries');
      expect(item).toHaveProperty('tenantCount');
    });

    it('analyze_industrial_location: calls AI service and returns analysis schema', async () => {
      const handler = getToolHandler(industrialServer, 'analyze_industrial_location');
      const result = await handler({
        latitude: 11.05,
        longitude: 106.65,
        targetIndustry: 'electronics',
      });

      expect(result.isError).toBeFalsy();
      const data = parseToolResult(result);

      // Schema validation
      expect(data).toHaveProperty('overallScore');
      expect(data).toHaveProperty('connectivity');
      expect(data).toHaveProperty('infrastructure');
      expect(data).toHaveProperty('laborMarket');
      expect(data).toHaveProperty('incentives');
      expect(data).toHaveProperty('risks');
      expect(typeof data.overallScore).toBe('number');

      const connectivity = data.connectivity as Record<string, unknown>;
      expect(connectivity).toHaveProperty('nearestPort');
      expect(connectivity).toHaveProperty('nearestAirport');

      // Verify correct URL was called
      expect(fetchSpy).toHaveBeenCalledWith(
        'http://ai-service:8000/industrial/analyze-location',
        expect.objectContaining({ method: 'POST' }),
      );
    });

    it('estimate_industrial_rent: calls AI service and returns rent estimate schema', async () => {
      const handler = getToolHandler(industrialServer, 'estimate_industrial_rent');
      const result = await handler({
        province: 'Bình Dương',
        propertyType: 'ready_built_factory',
        areaM2: 10000,
        leaseDurationYears: 10,
      });

      expect(result.isError).toBeFalsy();
      const data = parseToolResult(result);

      // Schema validation
      expect(data).toHaveProperty('estimatedRentUsdM2');
      expect(data).toHaveProperty('pricingUnit');
      expect(data).toHaveProperty('totalMonthlyUsd');
      expect(data).toHaveProperty('totalLeaseUsd');
      expect(data).toHaveProperty('managementFeeUsdM2');
      expect(data).toHaveProperty('depositMonths');
      expect(data).toHaveProperty('marketComparison');
      expect(data).toHaveProperty('breakdown');
      expect(data).toHaveProperty('input');
      expect(typeof data.estimatedRentUsdM2).toBe('number');

      const mc = data.marketComparison as Record<string, unknown>;
      expect(mc).toHaveProperty('provinceLow');
      expect(mc).toHaveProperty('provinceHigh');
      expect(mc).toHaveProperty('provinceAvg');

      // Verify correct URL was called
      expect(fetchSpy).toHaveBeenCalledWith(
        'http://ai-service:8000/industrial/estimate-rent',
        expect.objectContaining({ method: 'POST' }),
      );
    });
  });

  // -----------------------------------------------------------------------
  // 3. Reports server — 2 tools
  // -----------------------------------------------------------------------
  describe('reports server', () => {
    it('generate_report: calls NestJS API and returns report schema', async () => {
      const handler = getToolHandler(reportsServer, 'generate_report');
      const result = await handler({
        reportType: 'market_overview',
        location: 'Quận 7, Hồ Chí Minh',
        period: '1y',
        includeForecasts: false,
        includeMacro: false,
        language: 'vi',
      });

      expect(result.isError).toBeFalsy();
      const data = parseToolResult(result);

      // Schema validation
      expect(data).toHaveProperty('reportId');
      expect(data).toHaveProperty('reportType');
      expect(data).toHaveProperty('title');
      expect(data).toHaveProperty('location');
      expect(data).toHaveProperty('generatedAt');
      expect(data).toHaveProperty('summary');
      expect(data).toHaveProperty('sections');
      expect(data).toHaveProperty('keyMetrics');
      expect(typeof data.reportId).toBe('string');
      expect(Array.isArray(data.sections)).toBe(true);

      // Verify correct URL was called (NestJS API, not AI service)
      expect(fetchSpy).toHaveBeenCalledWith(
        'http://api:3001/api/v1/reports/generate',
        expect.objectContaining({ method: 'POST' }),
      );
    });

    it('get_macro_data: calls NestJS API with GET and returns macro data schema', async () => {
      const handler = getToolHandler(reportsServer, 'get_macro_data');
      const result = await handler({
        province: 'Bình Dương',
        categories: ['gdp'],
        fromYear: 2024,
        toYear: 2024,
      });

      expect(result.isError).toBeFalsy();
      const data = parseToolResult(result);

      // Schema validation
      expect(data).toHaveProperty('province');
      expect(data).toHaveProperty('period');
      expect(data).toHaveProperty('data');
      expect(data).toHaveProperty('highlights');
      expect(data.province).toBe('Bình Dương');

      const period = data.period as Record<string, number>;
      expect(period.from).toBe(2024);
      expect(period.to).toBe(2024);

      const macroData = data.data as Record<string, unknown[]>;
      expect(macroData).toHaveProperty('gdp');
      expect(macroData.gdp).toHaveLength(1);

      const gdpPoint = macroData.gdp[0] as Record<string, unknown>;
      expect(gdpPoint).toHaveProperty('year');
      expect(gdpPoint).toHaveProperty('value');
      expect(gdpPoint).toHaveProperty('unit');
      expect(gdpPoint).toHaveProperty('yoyChange');

      // Verify it used GET (not POST)
      const macroCall = fetchSpy.mock.calls.find(
        (call) => (call[0] as string).includes('/reports/macro-data'),
      );
      expect(macroCall).toBeDefined();
      expect((macroCall![1] as RequestInit).method).toBe('GET');
    });
  });

  // -----------------------------------------------------------------------
  // 4. Env var routing: industrial tools → AI_SERVICE_URL, reports → API_BASE_URL
  // -----------------------------------------------------------------------
  describe('env var routing', () => {
    it('industrial tools call aiServiceBaseUrl (AI_SERVICE_URL)', async () => {
      const analyzeCall = fetchSpy.mock.calls.find(
        (call) => (call[0] as string).includes('/industrial/analyze-location'),
      );
      expect(analyzeCall).toBeDefined();
      expect((analyzeCall![0] as string).startsWith('http://ai-service:8000')).toBe(true);

      const rentCall = fetchSpy.mock.calls.find(
        (call) => (call[0] as string).includes('/industrial/estimate-rent'),
      );
      expect(rentCall).toBeDefined();
      expect((rentCall![0] as string).startsWith('http://ai-service:8000')).toBe(true);
    });

    it('report tools call apiBaseUrl (API_BASE_URL)', async () => {
      const reportCall = fetchSpy.mock.calls.find(
        (call) => (call[0] as string).includes('/reports/generate'),
      );
      expect(reportCall).toBeDefined();
      expect((reportCall![0] as string).startsWith('http://api:3001')).toBe(true);

      const macroCall = fetchSpy.mock.calls.find(
        (call) => (call[0] as string).includes('/reports/macro-data'),
      );
      expect(macroCall).toBeDefined();
      expect((macroCall![0] as string).startsWith('http://api:3001')).toBe(true);
    });
  });

  // -----------------------------------------------------------------------
  // 5. Registry simulation — verify all servers can be registered
  // -----------------------------------------------------------------------
  describe('registry integration', () => {
    it('McpRegistryService registers industrial-parks and reports servers', async () => {
      // Simulate what McpRegistryService.onModuleInit does
      const servers = new Map<string, unknown>();

      servers.set(
        'property-search',
        createPropertySearchServer({
          typesenseClient: typesenseClient as unknown as TypesenseClient,
          collectionName: 'listings',
        }),
      );

      servers.set(
        'market-analytics',
        createMarketAnalyticsServer({
          typesenseClient: typesenseClient as unknown as TypesenseClient,
          collectionName: 'listings',
        }),
      );

      servers.set(
        'valuation',
        createValuationServer({ aiServiceBaseUrl: 'http://ai-service:8000' }),
      );

      servers.set(
        'industrial-parks',
        createIndustrialParksServer({
          typesenseClient: typesenseClient as unknown as TypesenseClient,
          collectionName: 'industrial_parks',
          aiServiceBaseUrl: 'http://ai-service:8000',
        }),
      );

      servers.set(
        'reports',
        createReportsServer({ apiBaseUrl: 'http://api:3001/api/v1' }),
      );

      // All 5 servers should be registered
      expect(servers.size).toBe(5);
      expect(Array.from(servers.keys()).sort()).toEqual([
        'industrial-parks',
        'market-analytics',
        'property-search',
        'reports',
        'valuation',
      ]);

      // Each server should be a valid McpServer instance
      for (const [name, server] of servers) {
        expect(server, `Server "${name}" should be defined`).toBeDefined();
      }
    });
  });
});
