import { describe, expect, it } from 'vitest';
import Parser from 'tree-sitter';
import TypeScript from 'tree-sitter-typescript';
import type { ParsedImport } from 'gitnexus-shared';
import { extractConvexEndpointProperties } from '../../src/core/ingestion/languages/typescript/convex-endpoint-metadata.js';
import type { SyntaxNode } from '../../src/core/ingestion/utils/ast-helpers.js';

const parser = new Parser();
parser.setLanguage(TypeScript.typescript as Parameters<Parser['setLanguage']>[0]);

function nodeOfType(source: string, type: string): SyntaxNode {
  const root = parser.parse(source).rootNode as unknown as SyntaxNode;
  const stack = [root];
  while (stack.length > 0) {
    const node = stack.pop()!;
    if (node.type === type) return node;
    for (let i = 0; i < node.namedChildCount; i++) {
      const child = node.namedChild(i);
      if (child) stack.push(child);
    }
  }
  throw new Error(`fixture has no ${type}`);
}

const namedImport = (
  targetRaw: string,
  importedName: string,
  localName = importedName,
): ParsedImport => ({
  kind: localName === importedName ? 'named' : 'alias',
  targetRaw,
  importedName,
  localName,
  ...(localName === importedName ? {} : { alias: localName }),
});

function extract(
  source: string,
  imports: readonly ParsedImport[],
  isExported = true,
  nodeLabel = 'Const',
  definitionType = 'export_statement',
) {
  return extractConvexEndpointProperties({
    nodeLabel,
    nodeName: 'updateDraft',
    definitionNode: nodeOfType(source, definitionType),
    parsedImports: imports,
    isExported,
  });
}

describe('Convex endpoint metadata extraction', () => {
  it('canonicalizes a generic convex/server factory across line-comment trivia', () => {
    expect(
      extract(
        `export const updateDraft = // legal trivia\n mutation({ handler: async () => null });`,
        [namedImport('convex/server', 'mutationGeneric', 'mutation')],
      ),
    ).toEqual({ convexEndpointFactory: 'mutation' });
  });

  it('preserves the canonical factory through a generated-server import alias', () => {
    expect(
      extract(`export const updateDraft = write({ handler: async () => null });`, [
        namedImport('../_generated/server', 'internalMutation', 'write'),
      ]),
    ).toEqual({ convexEndpointFactory: 'internalMutation' });
  });

  it('accepts generated-server package paths and httpAction', () => {
    expect(
      extract(`export const updateDraft = route(async () => null);`, [
        namedImport('convex/_generated/server', 'httpAction', 'route'),
      ]),
    ).toEqual({ convexEndpointFactory: 'httpAction' });
  });

  it.each(['arrow_function', 'function_expression'])('stamps a bare %s handler capture', (type) => {
    const expression =
      type === 'arrow_function' ? 'async () => null' : 'async function () { return null; }';
    expect(
      extract(
        `export const updateDraft = query(${expression});`,
        [namedImport('./_generated/server', 'query')],
        true,
        'Function',
        'export_statement',
      ),
    ).toEqual({ convexEndpointFactory: 'query' });
  });

  it.each([
    ['unrelated import', [namedImport('./database', 'query')], true],
    ['non-generic convex/server API', [namedImport('convex/server', 'query')], true],
    ['unexported declaration', [namedImport('./_generated/server', 'query')], false],
  ] as const)('rejects %s', (_case, imports, isExported) => {
    expect(
      extract(`export const updateDraft = query({ handler: () => null });`, imports, isExported),
    ).toBeUndefined();
  });

  it.each([
    'export const updateDraft = sdk.query({ handler: () => null });',
    'export const updateDraft = wrap(query({ handler: () => null }));',
    'export const updateDraft = query(buildConfig());',
  ])('rejects unsupported wrapper shape: %s', (source) => {
    expect(extract(source, [namedImport('./_generated/server', 'query')])).toBeUndefined();
  });

  it('does not search into a nested same-name declarator', () => {
    expect(
      extract(
        `export function updateDraft() {
          const updateDraft = query({ handler: () => null });
          return updateDraft;
        }`,
        [namedImport('./_generated/server', 'query')],
        true,
        'Function',
        'function_declaration',
      ),
    ).toBeUndefined();
  });
});
