import { describe, it, expect } from 'vitest';
import { readFileSync, existsSync } from 'node:fs';
import { join, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import { execSync } from 'node:child_process';

const root = join(dirname(fileURLToPath(import.meta.url)), '..', '..', '..');
const fishFile = readFileSync(join(root, 'completions', 'mcpctl.fish'), 'utf-8');
const bashFile = readFileSync(join(root, 'completions', 'mcpctl.bash'), 'utf-8');

describe('freshness', () => {
  it('committed completions match generator output', () => {
    const generatorPath = join(root, 'scripts', 'generate-completions.ts');
    expect(existsSync(generatorPath), 'generator script must exist').toBe(true);
    // Run the generator in --check mode; exit 0 means files are up to date
    execSync(`npx tsx ${generatorPath} --check`, { cwd: root, stdio: 'pipe' });
  });
});

describe('fish completions', () => {
  it('erases stale completions at the top', () => {
    const lines = fishFile.split('\n');
    const firstComplete = lines.findIndex((l) => l.startsWith('complete '));
    expect(lines[firstComplete]).toContain('-e');
  });

  it('does not offer resource types without __mcpctl_needs_resource_type guard', () => {
    const resourceTypes = ['servers', 'instances', 'secrets', 'templates', 'projects', 'users', 'groups', 'rbac', 'prompts', 'promptrequests'];
    const lines = fishFile.split('\n').filter((l) => l.startsWith('complete '));

    for (const line of lines) {
      // Find lines that offer resource types as positional args
      const offersResourceType = resourceTypes.some((r) => {
        // Match `-a "...servers..."` or `-a 'servers projects'`
        const aMatch = line.match(/-a\s+['"]([^'"]+)['"]/);
        if (!aMatch) return false;
        return aMatch[1].split(/\s+/).includes(r);
      });

      if (!offersResourceType) continue;

      // Skip the help completions line and the -e line
      if (line.includes('__fish_seen_subcommand_from help')) continue;
      // Skip project-scoped command offerings (those offer commands, not resource types)
      if (line.includes('attach-server') || line.includes('detach-server')) continue;
      // Skip lines that offer commands (not resource types)
      if (line.includes("-d 'Show") || line.includes("-d 'Manage") || line.includes("-d 'Authenticate") ||
          line.includes("-d 'Log out'") || line.includes("-d 'Get instance") || line.includes("-d 'Create a resource'") ||
          line.includes("-d 'Edit a resource'") || line.includes("-d 'Apply") || line.includes("-d 'Backup") ||
          line.includes("-d 'Restore") || line.includes("-d 'List resources") || line.includes("-d 'Delete a resource'")) continue;

      // Lines offering resource types MUST have __mcpctl_needs_resource_type in their condition
      expect(line, `Resource type completion missing guard: ${line}`).toContain('__mcpctl_needs_resource_type');
    }
  });

  it('resource name completions require resource type to be selected', () => {
    const lines = fishFile.split('\n').filter((l) => l.startsWith('complete') && l.includes('__mcpctl_resource_names'));
    expect(lines.length).toBeGreaterThan(0);
    for (const line of lines) {
      expect(line).toContain('not __mcpctl_needs_resource_type');
    }
  });

  it('defines --project option with -p shorthand', () => {
    expect(fishFile).toContain("-s p -l project");
  });

  it('attach-server command only shows with --project', () => {
    // Only check lines that OFFER attach-server as a command (via -a attach-server), not argument completions
    const lines = fishFile.split('\n').filter((l) =>
      l.startsWith('complete') && l.includes("-a attach-server"));
    expect(lines.length).toBeGreaterThan(0);
    for (const line of lines) {
      expect(line).toContain('__mcpctl_has_project');
    }
  });

  it('detach-server command only shows with --project', () => {
    const lines = fishFile.split('\n').filter((l) =>
      l.startsWith('complete') && l.includes("-a detach-server"));
    expect(lines.length).toBeGreaterThan(0);
    for (const line of lines) {
      expect(line).toContain('__mcpctl_has_project');
    }
  });

  it('resource name functions use jq to extract names and avoid nested matches', () => {
    const resourceNamesFn = fishFile.match(/function __mcpctl_resource_names[\s\S]*?^end/m)?.[0] ?? '';
    const projectNamesFn = fishFile.match(/function __mcpctl_project_names[\s\S]*?^end/m)?.[0] ?? '';

    // Resource names: uses .[].name for most resources, .[][].server.name for instances
    expect(resourceNamesFn, '__mcpctl_resource_names must use jq for name extraction').toContain("jq -r");
    expect(resourceNamesFn, '__mcpctl_resource_names must not use string match on name').not.toMatch(/string match.*"name"/);

    expect(projectNamesFn, '__mcpctl_project_names must use jq for name extraction').toContain("jq -r");
    expect(projectNamesFn, '__mcpctl_project_names must not use string match on name').not.toMatch(/string match.*"name"/);
  });

  it('instances use server.name instead of name', () => {
    const resourceNamesFn = fishFile.match(/function __mcpctl_resource_names[\s\S]*?^end/m)?.[0] ?? '';
    expect(resourceNamesFn, 'must handle instances via server.name').toContain('.server.name');
  });

  it('attach-server completes with available (unattached) servers and guards against repeat', () => {
    const attachLine = fishFile.split('\n').find((l) =>
      l.startsWith('complete') && l.includes('__fish_seen_subcommand_from attach-server'));
    expect(attachLine, 'attach-server argument completion must exist').toBeDefined();
    expect(attachLine, 'attach-server must use __mcpctl_available_servers').toContain('__mcpctl_available_servers');
    expect(attachLine, 'attach-server must guard with __mcpctl_needs_server_arg').toContain('__mcpctl_needs_server_arg');
  });

  it('detach-server completes with project servers and guards against repeat', () => {
    const detachLine = fishFile.split('\n').find((l) =>
      l.startsWith('complete') && l.includes('__fish_seen_subcommand_from detach-server'));
    expect(detachLine, 'detach-server argument completion must exist').toBeDefined();
    expect(detachLine, 'detach-server must use __mcpctl_project_servers').toContain('__mcpctl_project_servers');
    expect(detachLine, 'detach-server must guard with __mcpctl_needs_server_arg').toContain('__mcpctl_needs_server_arg');
  });

  it('non-project commands do not show with --project', () => {
    const nonProjectCmds = ['status', 'login', 'logout', 'config', 'apply', 'backup'];
    const lines = fishFile.split('\n').filter((l) => l.startsWith('complete') && l.includes('-a '));

    for (const cmd of nonProjectCmds) {
      const cmdLines = lines.filter((l) => {
        const aMatch = l.match(/-a\s+(\S+)/);
        return aMatch && aMatch[1].replace(/['"]/g, '') === cmd;
      });
      for (const line of cmdLines) {
        expect(line, `${cmd} should require 'not __mcpctl_has_project'`).toContain('not __mcpctl_has_project');
      }
    }
  });
});

describe('bash completions', () => {
  it('separates project commands from regular commands', () => {
    expect(bashFile).toContain('project_commands=');
    expect(bashFile).toContain('attach-server detach-server');
  });

  it('checks has_project before offering project commands', () => {
    expect(bashFile).toContain('if $has_project');
    expect(bashFile).toContain('$project_commands');
  });

  it('fetches resource names dynamically after resource type', () => {
    expect(bashFile).toContain('_mcpctl_resource_names');
    // get, describe, and delete should each use resource_names when resource_type is set
    for (const cmd of ['get', 'describe', 'delete']) {
      const block = bashFile.match(new RegExp(`${cmd}\\)[\\s\\S]*?return ;;`))?.[0] ?? '';
      expect(block, `${cmd} case must use _mcpctl_resource_names`).toContain('_mcpctl_resource_names');
    }
  });

  it('attach-server filters out already-attached servers and guards against repeat', () => {
    const attachBlock = bashFile.match(/attach-server\)[\s\S]*?return ;;/)?.[0] ?? '';
    expect(attachBlock, 'attach-server must use _mcpctl_get_project_value').toContain('_mcpctl_get_project_value');
    expect(attachBlock, 'attach-server must query project servers to exclude').toContain('--project');
    expect(attachBlock, 'attach-server must check position to prevent repeat').toContain('cword - subcmd_pos');
  });

  it('detach-server shows only project servers and guards against repeat', () => {
    const detachBlock = bashFile.match(/detach-server\)[\s\S]*?return ;;/)?.[0] ?? '';
    expect(detachBlock, 'detach-server must use _mcpctl_get_project_value').toContain('_mcpctl_get_project_value');
    expect(detachBlock, 'detach-server must query project servers').toContain('--project');
    expect(detachBlock, 'detach-server must check position to prevent repeat').toContain('cword - subcmd_pos');
  });

  it('instances use server.name instead of name', () => {
    const fnMatch = bashFile.match(/_mcpctl_resource_names\(\)[\s\S]*?\n\s*\}/)?.[0] ?? '';
    expect(fnMatch, 'must handle instances via .server.name').toContain('.server.name');
  });

  it('defines --project option', () => {
    expect(bashFile).toContain('--project');
  });

  it('resource name function uses jq to extract names and avoid nested matches', () => {
    const fnMatch = bashFile.match(/_mcpctl_resource_names\(\)[\s\S]*?\n\s*\}/)?.[0] ?? '';
    expect(fnMatch, '_mcpctl_resource_names must use jq for name extraction').toContain("jq -r");
    expect(fnMatch, '_mcpctl_resource_names must not use grep on name').not.toMatch(/grep.*"name"/);
  });
});
