/**
 * End-to-end validation of the INLINE provider source-scan containment path
 * (#2276).
 *
 * The unit suite (`test/unit/group/http-route-extractor.test.ts`) proves the
 * resolver logic by MOCKING `CONTAINING_QUERY` with hand-picked spans. That
 * leaves one assumption unverified: that the REAL ingestion pipeline records a
 * Go enclosing function with a 0-based span that actually contains the emitted
 * call-site line. This test closes that gap.
 *
 * It runs the real pipeline over a Go file whose `http.HandleFunc` handler is an
 * inline `func(){…}` (the issue's headline Go example), persists the resulting
 * graph into a real LadybugDB, and runs the production `HttpRouteExtractor`
 * against the real executor. The provider must resolve to the containing
 * `main()` symbol via line-span containment (`source_scan_resolved`) — not the
 * file-level fallback. Go does not index anonymous func literals as symbols
 * (only `function_declaration`/`method_declaration`), so the innermost
 * containing symbol is `main` itself.
 */
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import fs from 'fs/promises';
import path from 'path';
import os from 'os';
import { runPipelineFromRepo } from '../../src/core/ingestion/pipeline.js';
import { HttpRouteExtractor } from '../../src/core/group/extractors/http-route-extractor.js';
import type { CypherExecutor } from '../../src/core/group/contract-extractor.js';
import type { RepoHandle } from '../../src/core/group/types.js';

let tmpBase: string;
let repoDir: string;
let storagePath: string;
let dbPath: string;

beforeAll(async () => {
  // Atomic, unique temp dir (fs.mkdtemp) — avoids the predictable
  // os.tmpdir()+name pattern CodeQL flags as an insecure temporary file.
  tmpBase = await fs.mkdtemp(path.join(os.tmpdir(), 'gitnexus-http-inline-e2e-'));
  repoDir = path.join(tmpBase, 'repo');
  storagePath = path.join(tmpBase, '.gitnexus');
  dbPath = path.join(storagePath, 'lbug');
  await fs.mkdir(path.join(repoDir, 'cmd'), { recursive: true });
  await fs.mkdir(dbPath, { recursive: true });

  // net/http inline handler INSIDE main() — the #2276 Go example. Before this
  // change the func literal was not even captured; now it emits name:null + the
  // call-site line so it resolves to main() by containment.
  await fs.writeFile(
    path.join(repoDir, 'cmd', 'server.go'),
    `package main

import "net/http"

func main() {
\thttp.HandleFunc("/api/health", func(w http.ResponseWriter, r *http.Request) {
\t\tw.Write([]byte("ok"))
\t})
\thttp.ListenAndServe(":8080", nil)
}
`,
  );

  const result = await runPipelineFromRepo(repoDir, () => {}, {});
  const adapter = await import('../../src/core/lbug/lbug-adapter.js');
  await adapter.initLbug(dbPath);
  await adapter.loadGraphToLbug(result.graph, repoDir, storagePath);
}, 120_000);

afterAll(async () => {
  try {
    const adapter = await import('../../src/core/lbug/lbug-adapter.js');
    await adapter.closeLbug();
  } catch {
    /* may not have opened */
  }
  if (tmpBase) {
    for (let attempt = 0; attempt < 5; attempt++) {
      try {
        await fs.rm(tmpBase, { recursive: true, force: true });
        return;
      } catch {
        if (attempt < 4) await new Promise((r) => setTimeout(r, 200 * (attempt + 1)));
      }
    }
  }
});

describe('inline Go provider handler resolves via real source-scan containment (#2276)', () => {
  it('resolves an inline http.HandleFunc closure to the containing main() with a real symbolUid', async () => {
    const adapter = await import('../../src/core/lbug/lbug-adapter.js');
    // Param-aware executor (CONTAINING_QUERY binds $filePath) — the same shape
    // production passes to ContractExtractors.
    const dbExecutor: CypherExecutor = (query, params = {}) =>
      adapter.executePrepared(query, params);
    const repo: RepoHandle = {
      id: 'test-repo',
      path: 'repo',
      repoPath: repoDir,
      storagePath,
    };

    const contracts = await new HttpRouteExtractor().extract(dbExecutor, repoDir, repo);
    const provider = contracts.find(
      (c) => c.role === 'provider' && c.contractId === 'http::GET::/api/health',
    );

    expect(provider).toBeDefined();
    // The real pipeline indexed main() with its true 0-based span; the emitted
    // call-site line lands inside it, so containment yields a real symbolUid
    // rather than the empty file-level fallback.
    expect(provider?.symbolUid).toBeTruthy();
    expect(provider?.symbolName).toBe('main');
    expect(provider?.meta.extractionStrategy).toBe('source_scan_resolved');
  });
});
