/**
 * Integration Tests: LadybugDB Connection Pool — Parallel Stability
 *
 * Tests concurrency fixes from PR #349:
 *   - Pre-warmed pool handles max parallel queries
 *   - Waiter queue drains under overload
 *   - Concurrent initLbug deduplication
 *   - stdout.write restoration after parallel operations
 *   - No connection leaks over sequential workloads
 *   - Atomic pool entry visibility (pool.set last)
 *   - Mixed query types at full concurrency
 *
 * Connection budget: LadybugDB's native mmap budget caps at ~56
 * simultaneous Connection objects per process.  Tests that only need
 * a ready pool share a single 'shared-repo' init (8 connections) to
 * stay well under the limit.
 */
import { it, expect, afterAll } from 'vitest';
import {
  initLbug,
  executeQuery,
  executeParameterized,
  closeLbug,
  isLbugReady,
} from '../../src/mcp/core/lbug-adapter.js';
import { withTestLbugDB } from '../helpers/test-indexed-db.js';

const SEED_DATA = [
  `CREATE (f:File {id: 'file:index.ts', name: 'index.ts', filePath: 'src/index.ts', content: ''})`,
  `CREATE (fn:Function {id: 'func:main', name: 'main', filePath: 'src/index.ts', startLine: 1, endLine: 10, isExported: true, content: '', description: ''})`,
  `CREATE (fn2:Function {id: 'func:helper', name: 'helper', filePath: 'src/utils.ts', startLine: 1, endLine: 5, isExported: true, content: '', description: ''})`,
  `MATCH (a:Function), (b:Function)
    WHERE a.id = 'func:main' AND b.id = 'func:helper'
    CREATE (a)-[:CodeRelation {type: 'CALLS', confidence: 1.0, reason: 'direct', step: 0}]->(b)`,
];

// ─── Shared-pool tests: reuse one init across 5 tests (8 connections total) ──

withTestLbugDB(
  'pool-stability',
  (handle) => {
    const REPO = 'shared-par';
    let inited = false;

    const ensurePool = async () => {
      if (!inited) {
        await initLbug(REPO, handle.dbPath);
        inited = true;
      }
    };

    afterAll(async () => {
      try {
        await closeLbug(REPO);
      } catch {
        /* best-effort */
      }
    });

    it('8 simultaneous queries complete without crashes', async () => {
      await ensurePool();
      const queries = Array.from({ length: 8 }, () =>
        executeQuery(REPO, 'MATCH (n:Function) RETURN n.name AS name'),
      );
      const results = await Promise.all(queries);
      expect(results).toHaveLength(8);
      for (const rows of results) {
        expect(rows.length).toBeGreaterThanOrEqual(2);
      }
    });

    it('12 parallel queries overflow into waiter queue and all complete', async () => {
      await ensurePool();
      const queries = Array.from({ length: 12 }, () =>
        executeQuery(REPO, 'MATCH (n:Function) RETURN n.name AS name'),
      );
      const results = await Promise.all(queries);
      expect(results).toHaveLength(12);
      for (const rows of results) {
        expect(rows.length).toBeGreaterThanOrEqual(2);
      }
    });

    it('process.stdout.write is functional after init and parallel queries', async () => {
      await ensurePool();
      const queries = Array.from({ length: 8 }, () =>
        executeQuery(REPO, 'MATCH (n:Function) RETURN n.name AS name'),
      );
      await Promise.all(queries);
      // stdout.write should be the real function, not the silenced no-op
      expect(typeof process.stdout.write).toBe('function');
    });

    it('50 sequential queries do not leak connections', async () => {
      await ensurePool();
      for (let i = 0; i < 50; i++) {
        await executeQuery(REPO, 'MATCH (n:Function) RETURN n.name AS name');
      }
      // If connections leaked, these 8 parallel queries would exhaust the pool
      const queries = Array.from({ length: 8 }, () =>
        executeQuery(REPO, 'MATCH (n:Function) RETURN n.name AS name'),
      );
      const results = await Promise.all(queries);
      expect(results).toHaveLength(8);
    });

    it('mixed executeQuery + executeParameterized at full concurrency', async () => {
      await ensurePool();
      const queries = [
        executeQuery(REPO, 'MATCH (n:Function) RETURN n.name AS name'),
        executeParameterized(
          REPO,
          'MATCH (n:Function) WHERE n.name = $name RETURN n.name AS name',
          { name: 'main' },
        ),
        executeQuery(REPO, 'MATCH (n:Function) RETURN n.name AS name'),
        executeParameterized(
          REPO,
          'MATCH (n:Function) WHERE n.name = $name RETURN n.name AS name',
          { name: 'helper' },
        ),
        executeQuery(REPO, 'MATCH (n:Function) RETURN n.name AS name'),
        executeParameterized(
          REPO,
          'MATCH (n:Function) WHERE n.name = $name RETURN n.name AS name',
          { name: 'main' },
        ),
        executeQuery(REPO, 'MATCH (n:Function) RETURN n.name AS name'),
        executeQuery(REPO, 'MATCH (n:Function) RETURN n.name AS name'),
      ];
      const results = await Promise.all(queries);
      expect(results).toHaveLength(8);
    });

    // ─── Regression tests for #308 / #314 / #347 ──────────────────────────
    // The impact command's enrichment phase runs 3 concurrent queries via
    // Promise.all (local-backend.ts:1415). Before PR #349's pool pre-warming,
    // this triggered lazy createConnection → silenceStdout → SIGSEGV.

    it('3 concurrent queries via Promise.all (impact enrichment pattern, #308/#314/#347)', async () => {
      await ensurePool();
      // Mirrors the exact Promise.all pattern from local-backend.ts:1415
      // that caused SIGSEGV in issues #308, #314, #347 before pool pre-warming.
      const [r1, r2, r3] = await Promise.all([
        executeQuery(REPO, 'MATCH (n:Function) RETURN n.name AS name LIMIT 5').catch(() => []),
        executeQuery(REPO, 'MATCH (n:Function) RETURN n.id AS id LIMIT 5').catch(() => []),
        executeQuery(REPO, 'MATCH ()-[r:CodeRelation]->() RETURN r.type AS type LIMIT 5').catch(
          () => [],
        ),
      ]);
      expect(r1.length).toBeGreaterThanOrEqual(1);
      expect(r2.length).toBeGreaterThanOrEqual(1);
      expect(r3.length).toBeGreaterThanOrEqual(1);
    });

    // ─── Fresh-state tests: need their own init ──────────────────────────

    it('concurrent initLbug calls for the same repoId deduplicate', async () => {
      const promises = Array.from({ length: 6 }, () => initLbug('dedup-repo', handle.dbPath));
      await Promise.all(promises);
      expect(isLbugReady('dedup-repo')).toBe(true);
      const rows = await executeQuery('dedup-repo', 'MATCH (n:Function) RETURN n.name AS name');
      expect(rows.length).toBeGreaterThanOrEqual(2);
      await closeLbug('dedup-repo');
    });

    it('pool entry is not visible until initLbug fully resolves', async () => {
      const initPromise = initLbug('atomic-repo', handle.dbPath);
      // Synchronously, pool should NOT be ready (pool.set is the last operation)
      expect(isLbugReady('atomic-repo')).toBe(false);
      await initPromise;
      expect(isLbugReady('atomic-repo')).toBe(true);
      await closeLbug('atomic-repo');
    });
  },
  {
    seed: SEED_DATA,
    poolAdapter: true,
  },
);
