import { describe, expect, it, vi } from 'vitest';
import { parseGenerateFlags } from '../src/cli/generate/flags.js';
import { inferNameFromCommand } from '../src/cli/generate/name-utils.js';
import { normalizeDefinition } from '../src/cli/generate/definition.js';
import { buildGenerateCliCommand } from '../src/cli/generate/template-data.js';
import { printGenerateCliHelp } from '../src/cli/generate-cli-runner.js';
import { serializeDefinition } from '../src/cli-metadata.js';
import type { SerializedServerDefinition } from '../src/cli-metadata.js';

describe('generate-cli runner internals', () => {
  it('parses generate-cli flags including bundle/compile toggles', () => {
    const args = [
      '--server',
      'linear',
      '--command',
      'https://example.com/mcp.getComponents()',
      '--bundle',
      '--compile',
      '--minify',
    ];
    const parsed = parseGenerateFlags([...args]);
    expect(parsed.server).toBe('linear');
    expect(parsed.command).toBe('https://example.com/mcp');
    expect(parsed.bundle).toBe(true);
    expect(parsed.compile).toBe(true);
    expect(parsed.minify).toBe(true);
  });

  it('documents timeout and minify flags in generate-cli help', () => {
    const spy = vi.spyOn(console, 'error').mockImplementation(() => {});
    let output = '';
    try {
      printGenerateCliHelp();
      output = String(spy.mock.calls[0]?.[0] ?? '');
    } finally {
      spy.mockRestore();
    }
    expect(output).toContain('--timeout <ms>');
    expect(output).toContain('--minify / --no-minify');
  });

  it('normalizes inferred names from URLs', () => {
    const args = ['--command', 'https://api.linear.app/mcp.getComponents'];
    const parsed = parseGenerateFlags([...args]);
    expect(parsed.command).toContain('https://');
    const inferred = inferNameFromCommand(parsed.command ?? '');
    expect(inferred).toBe('linear');
  });

  it('splits stdio commands and infers names from args', () => {
    const args = ['--command', 'npx -y chrome-devtools-mcp@latest'];
    const parsed = parseGenerateFlags([...args]);
    expect(parsed.command).toBeDefined();
    expect(typeof parsed.command).toBe('object');
    const spec = parsed.command as { command: string; args?: string[] };
    expect(spec.command).toBe('npx');
    expect(spec.args).toEqual(['-y', 'chrome-devtools-mcp@latest']);
    const inferred = parsed.command !== undefined ? inferNameFromCommand(parsed.command) : undefined;
    expect(inferred).toContain('chrome-devtools');
  });

  it('parses local script commands with extra args', () => {
    const args = ['--command', 'bun run ./servers/local-cli.ts --stdio --name local'];
    const parsed = parseGenerateFlags([...args]);
    const spec = parsed.command as { command: string; args?: string[] };
    expect(spec.command).toBe('bun');
    expect(spec.args).toEqual(['run', './servers/local-cli.ts', '--stdio', '--name', 'local']);
    const inferred = parsed.command !== undefined ? inferNameFromCommand(parsed.command) : undefined;
    expect(inferred).toBe('local-cli');
  });

  it('infers package names from scoped arguments', () => {
    const args = ['--command', 'npx -y @demo/tools@latest serve'];
    const parsed = parseGenerateFlags([...args]);
    const spec = parsed.command as { command: string; args?: string[] };
    expect(spec.args).toEqual(['-y', '@demo/tools@latest', 'serve']);
    const inferred = parsed.command !== undefined ? inferNameFromCommand(parsed.command) : undefined;
    expect(inferred).toBe('demo-tools');
  });

  it('infers npm package names without version specifiers in inline commands', () => {
    const args = ['--command', 'npx -y chrome-devtools-mcp'];
    const parsed = parseGenerateFlags([...args]);
    const spec = parsed.command as { command: string; args?: string[] };
    expect(spec.args).toEqual(['-y', 'chrome-devtools-mcp']);
    const inferred = parsed.command !== undefined ? inferNameFromCommand(parsed.command) : undefined;
    expect(inferred).toBe('chrome-devtools-mcp');
  });

  it('normalizes scheme-less HTTP selectors passed to --command', () => {
    const args = ['--command', 'shadcn.io/api/mcp.getComponents'];
    const parsed = parseGenerateFlags([...args]);
    expect(typeof parsed.command).toBe('string');
    expect((parsed.command as string).startsWith('https://')).toBe(true);
    const inferred = parsed.command !== undefined ? inferNameFromCommand(parsed.command) : undefined;
    expect(inferred).toBe('shadcn');
  });

  it('preserves HTTP fetch compatibility metadata in generated CLI definitions', () => {
    const definition = normalizeDefinition({
      name: 'sunsama',
      command: 'https://api.sunsama.com/mcp',
      httpFetch: 'node-http1',
    });

    expect(definition.httpFetch).toBe('node-http1');
    expect(serializeDefinition(definition).httpFetch).toBe('node-http1');
  });

  it('preserves refreshable bearer metadata in generated CLI definitions', () => {
    const definition = normalizeDefinition({
      name: 'stdio-refresh',
      command: 'node',
      args: ['server.js'],
      auth: 'refreshable_bearer',
      refresh: {
        token_endpoint: 'https://auth.example.com/token',
        access_token_env: 'EXAMPLE_ACCESS_TOKEN',
      },
    });

    expect(definition.auth).toBe('refreshable_bearer');
    expect(definition.refresh).toEqual({
      tokenEndpoint: 'https://auth.example.com/token',
      accessTokenEnv: 'EXAMPLE_ACCESS_TOKEN',
    });
    expect(serializeDefinition(definition).refresh).toEqual(definition.refresh);
  });

  it('wraps single-token stdio commands when passed via --command', () => {
    const args = ['--command', './scripts/mcp-server.ts'];
    const parsed = parseGenerateFlags([...args]);
    expect(parsed.command).toBeDefined();
    const spec = parsed.command as { command: string; args?: string[] };
    expect(spec).toEqual({ command: './scripts/mcp-server.ts' });
    const inferred = parsed.command !== undefined ? inferNameFromCommand(parsed.command) : undefined;
    expect(inferred).toBe('mcp-server');
  });

  it('treats positional inline commands as generate-cli targets', () => {
    const args = ['npx -y chrome-devtools-mcp@latest'];
    const parsed = parseGenerateFlags([...args]);
    expect(parsed.command).toBeDefined();
    expect(parsed.server).toBeUndefined();
    const spec = parsed.command as { command: string; args?: string[] };
    expect(spec.command).toBe('npx');
    expect(spec.args).toEqual(['-y', 'chrome-devtools-mcp@latest']);
  });

  it('keeps bare names positional when no whitespace is present', () => {
    const args = ['linear'];
    const parsed = parseGenerateFlags([...args]);
    expect(parsed.server).toBe('linear');
    expect(parsed.command).toBeUndefined();
  });

  it('handles inline commands with extra interior whitespace', () => {
    const args = ['  bun   run   ./cli.ts   --stdio  '];
    const parsed = parseGenerateFlags([...args]);
    const spec = parsed.command as { command: string; args?: string[] };
    expect(spec.command).toBe('bun');
    expect(spec.args).toEqual(['run', './cli.ts', '--stdio']);
  });

  it('treats positional HTTPS URLs as ad-hoc servers and infers names', () => {
    const args = ['https://mcp.context7.com/mcp'];
    const parsed = parseGenerateFlags([...args]);
    expect(parsed.command).toBe('https://mcp.context7.com/mcp');
    expect(parsed.server).toBeUndefined();
    const inferred = parsed.command !== undefined ? inferNameFromCommand(parsed.command) : undefined;
    expect(inferred).toBe('context7');
  });

  it('builds regenerate commands honoring global flags and invocation overrides', () => {
    const definition: SerializedServerDefinition = {
      name: 'demo',
      description: 'Demo server',
      command: { kind: 'http', url: 'https://demo.mcp' },
    };
    const invocation = {
      outputPath: 'out.ts',
      runtime: 'bun' as const,
      timeoutMs: 45_000,
      minify: true,
      bundle: 'out.bundle.js',
      includeTools: ['alpha', 'beta'],
    };
    const command = buildGenerateCliCommand(invocation, definition, { '--config': '/tmp/mcporter.json' });
    expect(command).toContain('--config /tmp/mcporter.json');
    expect(command).toContain('--server demo');
    expect(command).toContain('--bundle out.bundle.js');
    expect(command).toContain('--timeout 45000');
    expect(command).toContain('--minify');
    expect(command).toContain("--include-tools 'alpha,beta'");
  });

  it('parses include/exclude tool lists', () => {
    const args = ['--include-tools', 'a,b', '--include-tools', 'b,c', '--exclude-tools', 'x, y'];
    const parsed = parseGenerateFlags([...args]);
    expect(parsed.includeTools).toEqual(['a', 'b', 'c']);
    expect(parsed.excludeTools).toEqual(['x', 'y']);
  });
});
