import { describe, it, expect, vi } from 'vitest';
import { createGetCommand } from '../../src/commands/get.js';
import type { GetCommandDeps } from '../../src/commands/get.js';

function makeDeps(items: unknown[] = []): GetCommandDeps & { output: string[] } {
  const output: string[] = [];
  return {
    output,
    fetchResource: vi.fn(async () => items),
    log: (...args: string[]) => output.push(args.join(' ')),
  };
}

describe('get command', () => {
  it('lists servers in table format', async () => {
    const deps = makeDeps([
      { id: 'srv-1', name: 'slack', transport: 'STDIO', packageName: '@slack/mcp', dockerImage: null },
      { id: 'srv-2', name: 'github', transport: 'SSE', packageName: null, dockerImage: 'ghcr.io/github-mcp' },
    ]);
    const cmd = createGetCommand(deps);
    await cmd.parseAsync(['node', 'test', 'servers']);

    expect(deps.fetchResource).toHaveBeenCalledWith('servers', undefined, undefined);
    expect(deps.output[0]).toContain('NAME');
    expect(deps.output[0]).toContain('TRANSPORT');
    expect(deps.output.join('\n')).toContain('slack');
    expect(deps.output.join('\n')).toContain('github');
  });

  it('resolves resource aliases', async () => {
    const deps = makeDeps([]);
    const cmd = createGetCommand(deps);
    await cmd.parseAsync(['node', 'test', 'srv']);
    expect(deps.fetchResource).toHaveBeenCalledWith('servers', undefined, undefined);
  });

  it('passes ID when provided', async () => {
    const deps = makeDeps([{ id: 'srv-1', name: 'slack' }]);
    const cmd = createGetCommand(deps);
    await cmd.parseAsync(['node', 'test', 'servers', 'srv-1']);
    expect(deps.fetchResource).toHaveBeenCalledWith('servers', 'srv-1', undefined);
  });

  it('outputs apply-compatible JSON format (multi-doc)', async () => {
    const deps = makeDeps([{ id: 'srv-1', name: 'slack', createdAt: '2025-01-01', updatedAt: '2025-01-01', version: 1 }]);
    const cmd = createGetCommand(deps);
    await cmd.parseAsync(['node', 'test', 'servers', '-o', 'json']);

    const parsed = JSON.parse(deps.output[0] ?? '');
    // Array of documents with kind field, internal fields stripped
    expect(Array.isArray(parsed)).toBe(true);
    expect(parsed[0].kind).toBe('server');
    expect(parsed[0].name).toBe('slack');
    expect(parsed[0]).not.toHaveProperty('id');
    expect(parsed[0]).not.toHaveProperty('createdAt');
    expect(parsed[0]).not.toHaveProperty('updatedAt');
    expect(parsed[0]).not.toHaveProperty('version');
  });

  it('outputs apply-compatible YAML format (multi-doc)', async () => {
    const deps = makeDeps([{ id: 'srv-1', name: 'slack', createdAt: '2025-01-01' }]);
    const cmd = createGetCommand(deps);
    await cmd.parseAsync(['node', 'test', 'servers', '-o', 'yaml']);
    const text = deps.output[0];
    expect(text).toContain('kind: server');
    expect(text).toContain('name: slack');
    expect(text).not.toContain('id:');
    expect(text).not.toContain('createdAt:');
  });

  it('lists instances with correct columns', async () => {
    const deps = makeDeps([
      { id: 'inst-1', serverId: 'srv-1', server: { name: 'my-grafana' }, status: 'RUNNING', containerId: 'abc123def456', port: 3000 },
    ]);
    const cmd = createGetCommand(deps);
    await cmd.parseAsync(['node', 'test', 'instances']);
    expect(deps.output[0]).toContain('NAME');
    expect(deps.output[0]).toContain('STATUS');
    expect(deps.output.join('\n')).toContain('my-grafana');
    expect(deps.output.join('\n')).toContain('RUNNING');
  });

  it('shows no results message for empty list', async () => {
    const deps = makeDeps([]);
    const cmd = createGetCommand(deps);
    await cmd.parseAsync(['node', 'test', 'servers']);
    expect(deps.output[0]).toContain('No servers found');
  });

  it('lists users with correct columns (no ROLE column)', async () => {
    const deps = makeDeps([
      { id: 'usr-1', email: 'alice@test.com', name: 'Alice', provider: null },
      { id: 'usr-2', email: 'bob@test.com', name: null, provider: 'oidc' },
    ]);
    const cmd = createGetCommand(deps);
    await cmd.parseAsync(['node', 'test', 'users']);

    expect(deps.fetchResource).toHaveBeenCalledWith('users', undefined, undefined);
    const text = deps.output.join('\n');
    expect(text).toContain('EMAIL');
    expect(text).toContain('NAME');
    expect(text).not.toContain('ROLE');
    expect(text).toContain('PROVIDER');
    expect(text).toContain('alice@test.com');
    expect(text).toContain('Alice');
    expect(text).toContain('bob@test.com');
    expect(text).toContain('oidc');
  });

  it('resolves user alias', async () => {
    const deps = makeDeps([]);
    const cmd = createGetCommand(deps);
    await cmd.parseAsync(['node', 'test', 'user']);
    expect(deps.fetchResource).toHaveBeenCalledWith('users', undefined, undefined);
  });

  it('lists groups with correct columns', async () => {
    const deps = makeDeps([
      {
        id: 'grp-1',
        name: 'dev-team',
        description: 'Developers',
        members: [{ user: { email: 'alice@test.com' } }, { user: { email: 'bob@test.com' } }],
      },
      { id: 'grp-2', name: 'ops-team', description: 'Operations', members: [] },
    ]);
    const cmd = createGetCommand(deps);
    await cmd.parseAsync(['node', 'test', 'groups']);

    expect(deps.fetchResource).toHaveBeenCalledWith('groups', undefined, undefined);
    const text = deps.output.join('\n');
    expect(text).toContain('NAME');
    expect(text).toContain('MEMBERS');
    expect(text).toContain('DESCRIPTION');
    expect(text).toContain('dev-team');
    expect(text).toContain('2');
    expect(text).toContain('ops-team');
    expect(text).toContain('0');
  });

  it('resolves group alias', async () => {
    const deps = makeDeps([]);
    const cmd = createGetCommand(deps);
    await cmd.parseAsync(['node', 'test', 'group']);
    expect(deps.fetchResource).toHaveBeenCalledWith('groups', undefined, undefined);
  });

  it('lists rbac definitions with correct columns', async () => {
    const deps = makeDeps([
      {
        id: 'rbac-1',
        name: 'admins',
        subjects: [{ kind: 'User', name: 'admin@test.com' }],
        roleBindings: [{ role: 'edit', resource: '*' }],
      },
    ]);
    const cmd = createGetCommand(deps);
    await cmd.parseAsync(['node', 'test', 'rbac']);

    expect(deps.fetchResource).toHaveBeenCalledWith('rbac', undefined, undefined);
    const text = deps.output.join('\n');
    expect(text).toContain('NAME');
    expect(text).toContain('SUBJECTS');
    expect(text).toContain('BINDINGS');
    expect(text).toContain('admins');
    expect(text).toContain('User:admin@test.com');
    expect(text).toContain('edit:*');
  });

  it('resolves rbac-definition alias', async () => {
    const deps = makeDeps([]);
    const cmd = createGetCommand(deps);
    await cmd.parseAsync(['node', 'test', 'rbac-definition']);
    expect(deps.fetchResource).toHaveBeenCalledWith('rbac', undefined, undefined);
  });

  it('lists projects with new columns', async () => {
    const deps = makeDeps([{
      id: 'proj-1',
      name: 'smart-home',
      description: 'Home automation',
      ownerId: 'usr-1',
      servers: [{ server: { name: 'grafana' } }],
    }]);
    const cmd = createGetCommand(deps);
    await cmd.parseAsync(['node', 'test', 'projects']);

    const text = deps.output.join('\n');
    expect(text).toContain('SERVERS');
    expect(text).toContain('smart-home');
    expect(text).toContain('1');
  });

  it('displays mixed resource and operation bindings', async () => {
    const deps = makeDeps([
      {
        id: 'rbac-1',
        name: 'admin-access',
        subjects: [{ kind: 'Group', name: 'admin' }],
        roleBindings: [
          { role: 'edit', resource: '*' },
          { role: 'run', action: 'logs' },
          { role: 'run', action: 'backup' },
        ],
      },
    ]);
    const cmd = createGetCommand(deps);
    await cmd.parseAsync(['node', 'test', 'rbac']);

    const text = deps.output.join('\n');
    expect(text).toContain('edit:*');
    expect(text).toContain('run>logs');
    expect(text).toContain('run>backup');
  });

  it('displays name-scoped resource bindings', async () => {
    const deps = makeDeps([
      {
        id: 'rbac-1',
        name: 'ha-viewer',
        subjects: [{ kind: 'User', name: 'alice@test.com' }],
        roleBindings: [{ role: 'view', resource: 'servers', name: 'my-ha' }],
      },
    ]);
    const cmd = createGetCommand(deps);
    await cmd.parseAsync(['node', 'test', 'rbac']);

    const text = deps.output.join('\n');
    expect(text).toContain('view:servers:my-ha');
  });

  it('shows no results message for empty users list', async () => {
    const deps = makeDeps([]);
    const cmd = createGetCommand(deps);
    await cmd.parseAsync(['node', 'test', 'users']);
    expect(deps.output[0]).toContain('No users found');
  });

  it('shows no results message for empty groups list', async () => {
    const deps = makeDeps([]);
    const cmd = createGetCommand(deps);
    await cmd.parseAsync(['node', 'test', 'groups']);
    expect(deps.output[0]).toContain('No groups found');
  });

  it('shows no results message for empty rbac list', async () => {
    const deps = makeDeps([]);
    const cmd = createGetCommand(deps);
    await cmd.parseAsync(['node', 'test', 'rbac']);
    expect(deps.output[0]).toContain('No rbac found');
  });

  it('lists prompts with project name column', async () => {
    const deps = makeDeps([
      { id: 'p-1', name: 'debug-guide', projectId: 'proj-1', project: { name: 'smart-home' }, createdAt: '2025-01-01T00:00:00Z' },
      { id: 'p-2', name: 'global-rules', projectId: null, project: null, createdAt: '2025-01-01T00:00:00Z' },
    ]);
    const cmd = createGetCommand(deps);
    await cmd.parseAsync(['node', 'test', 'prompts']);

    const text = deps.output.join('\n');
    expect(text).toContain('NAME');
    expect(text).toContain('PROJECT');
    expect(text).toContain('debug-guide');
    expect(text).toContain('smart-home');
    expect(text).toContain('global-rules');
    expect(text).toContain('(global)');
  });

  it('lists promptrequests with project name column', async () => {
    const deps = makeDeps([
      { id: 'pr-1', name: 'new-rule', projectId: 'proj-1', project: { name: 'my-project' }, createdBySession: 'sess-abc123def456', createdAt: '2025-01-01T00:00:00Z' },
    ]);
    const cmd = createGetCommand(deps);
    await cmd.parseAsync(['node', 'test', 'promptrequests']);

    const text = deps.output.join('\n');
    expect(text).toContain('new-rule');
    expect(text).toContain('my-project');
    expect(text).toContain('sess-abc123d');
  });

  it('passes --project option to fetchResource', async () => {
    const deps = makeDeps([]);
    const cmd = createGetCommand(deps);
    await cmd.parseAsync(['node', 'test', 'prompts', '--project', 'smart-home']);

    expect(deps.fetchResource).toHaveBeenCalledWith('prompts', undefined, { project: 'smart-home' });
  });

  it('does not pass project when --project is not specified', async () => {
    const deps = makeDeps([]);
    const cmd = createGetCommand(deps);
    await cmd.parseAsync(['node', 'test', 'prompts']);

    expect(deps.fetchResource).toHaveBeenCalledWith('prompts', undefined, undefined);
  });

  it('passes --all flag to fetchResource', async () => {
    const deps = makeDeps([]);
    const cmd = createGetCommand(deps);
    await cmd.parseAsync(['node', 'test', 'prompts', '-A']);

    expect(deps.fetchResource).toHaveBeenCalledWith('prompts', undefined, { all: true });
  });

  it('passes both --project and --all when both given', async () => {
    const deps = makeDeps([]);
    const cmd = createGetCommand(deps);
    await cmd.parseAsync(['node', 'test', 'prompts', '--project', 'my-proj', '-A']);

    expect(deps.fetchResource).toHaveBeenCalledWith('prompts', undefined, { project: 'my-proj', all: true });
  });

  it('resolves prompt alias', async () => {
    const deps = makeDeps([]);
    const cmd = createGetCommand(deps);
    await cmd.parseAsync(['node', 'test', 'prompt']);
    expect(deps.fetchResource).toHaveBeenCalledWith('prompts', undefined, undefined);
  });

  it('resolves pr alias to promptrequests', async () => {
    const deps = makeDeps([]);
    const cmd = createGetCommand(deps);
    await cmd.parseAsync(['node', 'test', 'pr']);
    expect(deps.fetchResource).toHaveBeenCalledWith('promptrequests', undefined, undefined);
  });

  it('shows no results message for empty prompts list', async () => {
    const deps = makeDeps([]);
    const cmd = createGetCommand(deps);
    await cmd.parseAsync(['node', 'test', 'prompts']);
    expect(deps.output[0]).toContain('No prompts found');
  });

  it('lists projects with PLUGIN column showing resolved proxyModel', async () => {
    const deps = makeDeps([{
      id: 'proj-1',
      name: 'home',
      description: '',
      proxyModel: '',
      gated: true,
      ownerId: 'usr-1',
      servers: [],
    }]);
    const cmd = createGetCommand(deps);
    await cmd.parseAsync(['node', 'test', 'projects']);

    const text = deps.output.join('\n');
    expect(text).toContain('PLUGIN');
    expect(text).not.toContain('GATED');
    // proxyModel is empty but gated=true, table shows 'default'
    expect(text).toContain('default');
  });

  it('project JSON output resolves proxyModel from gated=true', async () => {
    const deps = makeDeps([{
      id: 'proj-1',
      name: 'home',
      description: '',
      proxyModel: '',
      gated: true,
      ownerId: 'usr-1',
      servers: [],
    }]);
    const cmd = createGetCommand(deps);
    await cmd.parseAsync(['node', 'test', 'projects', '-o', 'json']);

    const parsed = JSON.parse(deps.output[0] ?? '') as Array<Record<string, unknown>>;
    expect(parsed[0]!.proxyModel).toBe('default');
    expect(parsed[0]).not.toHaveProperty('gated');
  });

  it('project JSON output resolves proxyModel from gated=false', async () => {
    const deps = makeDeps([{
      id: 'proj-1',
      name: 'tools',
      description: '',
      proxyModel: '',
      gated: false,
      ownerId: 'usr-1',
      servers: [],
    }]);
    const cmd = createGetCommand(deps);
    await cmd.parseAsync(['node', 'test', 'projects', '-o', 'json']);

    const parsed = JSON.parse(deps.output[0] ?? '') as Array<Record<string, unknown>>;
    expect(parsed[0]!.proxyModel).toBe('content-pipeline');
    expect(parsed[0]).not.toHaveProperty('gated');
  });

  it('project JSON output preserves explicit proxyModel and drops gated', async () => {
    const deps = makeDeps([{
      id: 'proj-1',
      name: 'custom',
      description: '',
      proxyModel: 'gate',
      gated: true,
      ownerId: 'usr-1',
      servers: [],
    }]);
    const cmd = createGetCommand(deps);
    await cmd.parseAsync(['node', 'test', 'projects', '-o', 'json']);

    const parsed = JSON.parse(deps.output[0] ?? '') as Array<Record<string, unknown>>;
    expect(parsed[0]!.proxyModel).toBe('gate');
    expect(parsed[0]).not.toHaveProperty('gated');
  });
});
