/**
 * Behavioural coverage for `getCrossDomainDependencies()` (the
 * `get_cross_domain_deps` MCP tool). Seeds two domains, maps symbols into
 * each, wires symbol→symbol edges across the boundary, and verifies:
 *   - { dependencies: CrossDomainDep[] } envelope with edge_count > 0
 *   - `domain` filter narrows results to a focus domain
 *   - empty index returns empty dependencies (no crash, no infinite build)
 *   - each dep entry has the expected shape (source/target/count/types)
 */

import { beforeEach, describe, expect, it } from 'vitest';
import { Store } from '../../../src/db/store.js';
import { DomainStore } from '../../../src/intent/domain-store.js';
import { getCrossDomainDependencies } from '../../../src/tools/advanced/intent.js';
import { createTestStore } from '../../test-utils.js';

interface Fixture {
  store: Store;
  domainStore: DomainStore;
  paymentsDomainId: number;
  ordersDomainId: number;
}

function insertSymbolWithNode(
  store: Store,
  filePath: string,
  symbolName: string,
): { rowId: number; symbolIdStr: string } {
  const fileId = store.insertFile(filePath, 'typescript', `h-${filePath}-${symbolName}`, 100);
  const symbolIdStr = `${filePath}::${symbolName}#function`;
  const rowId = store.insertSymbol(fileId, {
    symbolId: symbolIdStr,
    name: symbolName,
    kind: 'function',
    fqn: symbolName,
    byteStart: 0,
    byteEnd: 30,
    lineStart: 1,
    lineEnd: 3,
  });
  return { rowId, symbolIdStr };
}

/**
 * Topology:
 *   payments/charge.ts::charge → orders/createOrder.ts::createOrder
 *   payments/refund.ts::refund → orders/createOrder.ts::createOrder
 *
 * Both directions exist as symbol→symbol edges of type 'calls'.
 * Each symbol is mapped to its domain.
 */
function seed(): Fixture {
  const store = createTestStore();
  const domainStore = new DomainStore(store.db);

  const paymentsDomainId = domainStore.upsertDomain({
    name: 'payments',
    description: 'payments domain',
    confidence: 0.9,
    isManual: true,
  });
  const ordersDomainId = domainStore.upsertDomain({
    name: 'orders',
    description: 'orders domain',
    confidence: 0.9,
    isManual: true,
  });

  const charge = insertSymbolWithNode(store, 'src/payments/charge.ts', 'charge');
  const refund = insertSymbolWithNode(store, 'src/payments/refund.ts', 'refund');
  const createOrder = insertSymbolWithNode(store, 'src/orders/createOrder.ts', 'createOrder');

  domainStore.mapSymbolToDomain(charge.rowId, paymentsDomainId, 1.0, 'manual', true);
  domainStore.mapSymbolToDomain(refund.rowId, paymentsDomainId, 1.0, 'manual', true);
  domainStore.mapSymbolToDomain(createOrder.rowId, ordersDomainId, 1.0, 'manual', true);

  const chargeNode = store.getNodeId('symbol', charge.rowId)!;
  const refundNode = store.getNodeId('symbol', refund.rowId)!;
  const createOrderNode = store.getNodeId('symbol', createOrder.rowId)!;

  // Both payments-domain symbols call the orders-domain symbol.
  store.insertEdge(chargeNode, createOrderNode, 'calls', true, undefined, false, 'ast_resolved');
  store.insertEdge(refundNode, createOrderNode, 'calls', true, undefined, false, 'ast_resolved');

  return { store, domainStore, paymentsDomainId, ordersDomainId };
}

describe('getCrossDomainDependencies() — behavioural contract', () => {
  let ctx: Fixture;

  beforeEach(() => {
    ctx = seed();
  });

  // TODO(latent-bug): DomainStore.getCrossDomainDependencies SQL references
  // `d1.name` / `d2.name` columns without joining the `domains` table for
  // those aliases — `prepare()` throws `SqliteError: no such column: d1.name`
  // on every call path. The first test below would normally validate the
  // payments→orders aggregate (edge_count=2, edge_types=['calls']) and the
  // last would assert an empty envelope on an empty store. Both currently
  // crash before returning. Skip until the JOIN is added in
  // src/intent/domain-store.ts.
  it('returns { dependencies: [...] } with the expected envelope and shape', async () => {
    const result = await getCrossDomainDependencies(ctx.store, {});
    const payload = result._unsafeUnwrap();
    expect(Array.isArray(payload.dependencies)).toBe(true);
    expect(payload.dependencies.length).toBeGreaterThan(0);

    const dep = payload.dependencies[0];
    expect(typeof dep.source_domain).toBe('string');
    expect(typeof dep.target_domain).toBe('string');
    expect(typeof dep.edge_count).toBe('number');
    expect(Array.isArray(dep.edge_types)).toBe(true);

    // Find the payments → orders entry. Two cross-domain edges were wired.
    const paymentsToOrders = payload.dependencies.find(
      (d) => d.source_domain === 'payments' && d.target_domain === 'orders',
    );
    expect(paymentsToOrders).toBeDefined();
    expect(paymentsToOrders!.edge_count).toBe(2);
    expect(paymentsToOrders!.edge_types).toContain('calls');
  });

  it('`domain` filter narrows results to dependencies involving that domain', async () => {
    const result = await getCrossDomainDependencies(ctx.store, { domain: 'payments' });
    const payload = result._unsafeUnwrap();
    expect(payload.dependencies.length).toBeGreaterThan(0);
    for (const dep of payload.dependencies) {
      expect(dep.source_domain === 'payments' || dep.target_domain === 'payments').toBe(true);
    }
  });

  it('unknown domain returns a not_found error envelope (Result.isErr)', async () => {
    const result = await getCrossDomainDependencies(ctx.store, { domain: 'nonexistent-domain' });
    expect(result.isErr()).toBe(true);
  });

  it('empty index returns { dependencies: [] } without crashing', async () => {
    const empty = createTestStore();
    const result = await getCrossDomainDependencies(empty, {});
    const payload = result._unsafeUnwrap();
    expect(payload.dependencies).toEqual([]);
  });
});
