/**
 * Regression tests for shape_check false positives.
 *
 * 1. errorPathKeys exclusion: consumer accessing error-path keys (e.g. 'error')
 *    must land in errorPathKeys — NOT mismatched — and must NOT trigger MISMATCH status.
 *
 * 2. Blocklist doesn't suppress legitimate API fields: fields like 'type' and 'href'
 *    are blocklisted from DOM-method filtering in consumer extraction, but when a route
 *    actually returns them, shape_check must recognise them as valid matches.
 */
import { describe, it, expect, beforeAll, vi } from 'vitest';
import { LocalBackend } from '../../src/mcp/local/local-backend.js';
import { listRegisteredRepos } from '../../src/storage/repo-manager.js';
import { withTestLbugDB } from '../helpers/test-indexed-db.js';

vi.mock('../../src/storage/repo-manager.js', () => ({
  listRegisteredRepos: vi.fn().mockResolvedValue([]),
  cleanupOldKuzuFiles: vi.fn().mockResolvedValue({ found: false, needsReindex: false }),
  findSiblingClones: vi.fn().mockResolvedValue([]),
}));

// ─── Seed data ────────────────────────────────────────────────────────────────

const SEED: string[] = [
  // Files
  `CREATE (f:File {id: 'file:app/api/orders/route.ts', name: 'route.ts', filePath: 'app/api/orders/route.ts', content: 'GET handler'})`,
  `CREATE (f:File {id: 'file:app/api/links/route.ts', name: 'route.ts', filePath: 'app/api/links/route.ts', content: 'GET handler'})`,
  `CREATE (f:File {id: 'file:components/OrderStatus.tsx', name: 'OrderStatus.tsx', filePath: 'components/OrderStatus.tsx', content: 'consumer'})`,
  `CREATE (f:File {id: 'file:components/LinkList.tsx', name: 'LinkList.tsx', filePath: 'components/LinkList.tsx', content: 'consumer'})`,

  // ─── Route: /api/orders ──────────────────────────────────────────────────
  // Success keys: [orderId, status, items]
  // Error keys:   [error, code]
  `CREATE (r:Route {id: 'Route:/api/orders', name: '/api/orders', filePath: 'app/api/orders/route.ts', responseKeys: ['orderId', 'status', 'items'], errorKeys: ['error', 'code'], middleware: []})`,

  // ─── Route: /api/links ───────────────────────────────────────────────────
  // Returns fields that overlap with DOM property names: type, href, target
  `CREATE (r:Route {id: 'Route:/api/links', name: '/api/links', filePath: 'app/api/links/route.ts', responseKeys: ['type', 'href', 'target', 'label'], errorKeys: [], middleware: []})`,

  // ─── Consumer functions ──────────────────────────────────────────────────
  `CREATE (fn:Function {id: 'func:OrderStatus', name: 'OrderStatus', filePath: 'components/OrderStatus.tsx', startLine: 1, endLine: 10, isExported: true, content: 'export function OrderStatus()', description: 'Order status component'})`,
  `CREATE (fn:Function {id: 'func:LinkList', name: 'LinkList', filePath: 'components/LinkList.tsx', startLine: 1, endLine: 10, isExported: true, content: 'export function LinkList()', description: 'Link list component'})`,

  // ─── Handler functions ───────────────────────────────────────────────────
  `CREATE (fn:Function {id: 'func:orders-GET', name: 'GET', filePath: 'app/api/orders/route.ts', startLine: 1, endLine: 8, isExported: true, content: 'export async function GET()', description: 'Orders GET handler'})`,
  `CREATE (fn:Function {id: 'func:links-GET', name: 'GET', filePath: 'app/api/links/route.ts', startLine: 1, endLine: 8, isExported: true, content: 'export async function GET()', description: 'Links GET handler'})`,

  // ─── FETCHES edges ───────────────────────────────────────────────────────

  // OrderStatus accesses 'orderId', 'status', and 'error' — error is in errorKeys, not responseKeys.
  // This must NOT cause a MISMATCH; 'error' should appear in errorPathKeys only.
  `MATCH (a:Function), (r:Route) WHERE a.id = 'func:OrderStatus' AND r.id = 'Route:/api/orders'
   CREATE (a)-[:CodeRelation {type: 'FETCHES', confidence: 1.0, reason: 'fetch-url-match|keys:orderId,status,error', step: 0}]->(r)`,

  // LinkList accesses 'type', 'href', 'target', 'label' — all are legitimate route responseKeys.
  // These field names overlap with DOM properties but are real API fields here.
  `MATCH (a:Function), (r:Route) WHERE a.id = 'func:LinkList' AND r.id = 'Route:/api/links'
   CREATE (a)-[:CodeRelation {type: 'FETCHES', confidence: 1.0, reason: 'fetch-url-match|keys:type,href,target,label', step: 0}]->(r)`,

  // ─── HANDLES_ROUTE edges ─────────────────────────────────────────────────
  `MATCH (fn:Function), (r:Route) WHERE fn.id = 'func:orders-GET' AND r.id = 'Route:/api/orders'
   CREATE (fn)-[:CodeRelation {type: 'HANDLES_ROUTE', confidence: 1.0, reason: 'nextjs-app-router', step: 0}]->(r)`,
  `MATCH (fn:Function), (r:Route) WHERE fn.id = 'func:links-GET' AND r.id = 'Route:/api/links'
   CREATE (fn)-[:CodeRelation {type: 'HANDLES_ROUTE', confidence: 1.0, reason: 'nextjs-app-router', step: 0}]->(r)`,
];

// ─── Tests ────────────────────────────────────────────────────────────────────

withTestLbugDB(
  'shape-check-regression',
  (handle) => {
    let backend: LocalBackend;

    beforeAll(async () => {
      const ext = handle as typeof handle & { _backend?: LocalBackend };
      if (!ext._backend) {
        throw new Error(
          'LocalBackend not initialized — afterSetup did not attach _backend to handle',
        );
      }
      backend = ext._backend;
    });

    // ─── Test 1: errorPathKeys exclusion from mismatched ──────────────────

    describe('errorPathKeys exclusion from mismatched', () => {
      it('error-path key appears in errorPathKeys, not mismatched', async () => {
        const result = await backend.callTool('shape_check', { route: '/api/orders' });

        expect(result.routes).toBeDefined();
        const ordersRoute = result.routes.find((r: any) => r.route === '/api/orders');
        expect(ordersRoute).toBeDefined();

        const consumer = ordersRoute!.consumers.find(
          (c: any) => c.filePath === 'components/OrderStatus.tsx',
        );
        expect(consumer).toBeDefined();

        // 'error' is in the route's errorKeys — consumer accessing it is valid
        // It must appear in errorPathKeys, NOT in mismatched
        expect(consumer!.errorPathKeys).toBeDefined();
        expect(consumer!.errorPathKeys).toContain('error');

        // 'error' must NOT appear in mismatched
        if (consumer!.mismatched) {
          expect(consumer!.mismatched).not.toContain('error');
        }
      });

      it('route with only error-path differences has no MISMATCH status', async () => {
        const result = await backend.callTool('shape_check', { route: '/api/orders' });

        const ordersRoute = result.routes.find((r: any) => r.route === '/api/orders');
        expect(ordersRoute).toBeDefined();

        // All consumer keys are known (either in responseKeys or errorKeys)
        // So the route must NOT be flagged as MISMATCH
        expect(ordersRoute!.status).toBeUndefined();
      });

      it('no global mismatches count when only error-path keys differ', async () => {
        const result = await backend.callTool('shape_check', { route: '/api/orders' });

        // Top-level mismatches count should be absent (0 mismatches)
        expect(result.mismatches).toBeUndefined();
      });
    });

    // ─── Test 2: blocklist doesn't suppress legitimate API fields ─────────

    describe('blocklist does not suppress legitimate API fields', () => {
      it('DOM-like field names in route response are valid matches', async () => {
        const result = await backend.callTool('shape_check', { route: '/api/links' });

        expect(result.routes).toBeDefined();
        const linksRoute = result.routes.find((r: any) => r.route === '/api/links');
        expect(linksRoute).toBeDefined();

        const consumer = linksRoute!.consumers.find(
          (c: any) => c.filePath === 'components/LinkList.tsx',
        );
        expect(consumer).toBeDefined();

        // All accessed keys (type, href, target, label) are in the route's responseKeys
        // None should be treated as mismatched
        if (consumer!.mismatched) {
          expect(consumer!.mismatched).not.toContain('type');
          expect(consumer!.mismatched).not.toContain('href');
          expect(consumer!.mismatched).not.toContain('target');
          expect(consumer!.mismatched).not.toContain('label');
        }
      });

      it('route with DOM-like fields has no MISMATCH status', async () => {
        const result = await backend.callTool('shape_check', { route: '/api/links' });

        const linksRoute = result.routes.find((r: any) => r.route === '/api/links');
        expect(linksRoute).toBeDefined();

        // No mismatches — all consumer keys match route responseKeys
        expect(linksRoute!.status).toBeUndefined();
      });

      it('no errorPathKeys when all accessed keys are in responseKeys', async () => {
        const result = await backend.callTool('shape_check', { route: '/api/links' });

        const linksRoute = result.routes.find((r: any) => r.route === '/api/links');
        const consumer = linksRoute!.consumers.find(
          (c: any) => c.filePath === 'components/LinkList.tsx',
        );
        expect(consumer).toBeDefined();

        // All keys are in responseKeys (not errorKeys), so no errorPathKeys
        expect(consumer!.errorPathKeys).toBeUndefined();
      });
    });
  },
  {
    seed: SEED,
    poolAdapter: true,
    afterSetup: async (handle) => {
      vi.mocked(listRegisteredRepos).mockResolvedValue([
        {
          name: 'test-shape-regression',
          path: '/test/shape-regression',
          storagePath: handle.tmpHandle.dbPath,
          indexedAt: new Date().toISOString(),
          lastCommit: 'abc123',
          stats: { files: 4, nodes: 8, communities: 0, processes: 0 },
        },
      ]);

      const backend = new LocalBackend();
      await backend.init();
      (handle as any)._backend = backend;
    },
  },
);
