import { describe, it, expect, vi, beforeEach } from 'vitest';
import { createDeleteCommand } from '../../src/commands/delete.js';
import { createLogsCommand } from '../../src/commands/logs.js';
import type { ApiClient } from '../../src/api-client.js';

function mockClient(): ApiClient {
  return {
    get: vi.fn(async () => []),
    post: vi.fn(async () => ({})),
    put: vi.fn(async () => ({})),
    delete: vi.fn(async () => {}),
  } as unknown as ApiClient;
}

describe('delete command', () => {
  let client: ReturnType<typeof mockClient>;
  let output: string[];
  const log = (...args: unknown[]) => output.push(args.map(String).join(' '));

  beforeEach(() => {
    client = mockClient();
    output = [];
  });

  it('deletes an instance by ID', async () => {
    const cmd = createDeleteCommand({ client, log });
    await cmd.parseAsync(['instance', 'inst-1'], { from: 'user' });
    expect(client.delete).toHaveBeenCalledWith('/api/v1/instances/inst-1');
    expect(output.join('\n')).toContain('deleted');
  });

  it('deletes a server by ID', async () => {
    const cmd = createDeleteCommand({ client, log });
    await cmd.parseAsync(['server', 'srv-1'], { from: 'user' });
    expect(client.delete).toHaveBeenCalledWith('/api/v1/servers/srv-1');
    expect(output.join('\n')).toContain('deleted');
  });

  it('resolves server name to ID', async () => {
    vi.mocked(client.get).mockResolvedValue([
      { id: 'srv-abc', name: 'ha-mcp' },
    ]);
    const cmd = createDeleteCommand({ client, log });
    await cmd.parseAsync(['server', 'ha-mcp'], { from: 'user' });
    expect(client.delete).toHaveBeenCalledWith('/api/v1/servers/srv-abc');
  });

  it('deletes a project', async () => {
    const cmd = createDeleteCommand({ client, log });
    await cmd.parseAsync(['project', 'proj-1'], { from: 'user' });
    expect(client.delete).toHaveBeenCalledWith('/api/v1/projects/proj-1');
  });

  it('accepts resource aliases', async () => {
    const cmd = createDeleteCommand({ client, log });
    await cmd.parseAsync(['srv', 'srv-1'], { from: 'user' });
    expect(client.delete).toHaveBeenCalledWith('/api/v1/servers/srv-1');
  });
});

describe('logs command', () => {
  let client: ReturnType<typeof mockClient>;
  let output: string[];
  const log = (...args: unknown[]) => output.push(args.map(String).join(' '));

  beforeEach(() => {
    client = mockClient();
    output = [];
  });

  it('shows logs by instance ID', async () => {
    vi.mocked(client.get)
      .mockResolvedValueOnce({ id: 'inst-1', status: 'RUNNING' } as never) // instance lookup
      .mockResolvedValueOnce({ stdout: 'hello world\n', stderr: '' } as never); // logs
    const cmd = createLogsCommand({ client, log });
    await cmd.parseAsync(['inst-1'], { from: 'user' });
    expect(client.get).toHaveBeenCalledWith('/api/v1/instances/inst-1');
    expect(client.get).toHaveBeenCalledWith('/api/v1/instances/inst-1/logs');
    expect(output.join('\n')).toContain('hello world');
  });

  it('resolves server name to instance ID', async () => {
    vi.mocked(client.get)
      .mockRejectedValueOnce(new Error('not found')) // instance lookup fails
      .mockResolvedValueOnce([{ id: 'srv-1', name: 'my-grafana' }] as never) // servers list
      .mockResolvedValueOnce([{ id: 'inst-1', status: 'RUNNING', containerId: 'abc' }] as never) // instances for server
      .mockResolvedValueOnce({ stdout: 'grafana logs\n', stderr: '' } as never); // logs
    const cmd = createLogsCommand({ client, log });
    await cmd.parseAsync(['my-grafana'], { from: 'user' });
    expect(client.get).toHaveBeenCalledWith('/api/v1/instances/inst-1/logs');
    expect(output.join('\n')).toContain('grafana logs');
  });

  it('picks RUNNING instance over others', async () => {
    vi.mocked(client.get)
      .mockRejectedValueOnce(new Error('not found'))
      .mockResolvedValueOnce([{ id: 'srv-1', name: 'ha-mcp' }] as never)
      .mockResolvedValueOnce([
        { id: 'inst-err', status: 'ERROR', containerId: null },
        { id: 'inst-ok', status: 'RUNNING', containerId: 'abc' },
      ] as never)
      .mockResolvedValueOnce({ stdout: 'running instance\n', stderr: '' } as never);
    const cmd = createLogsCommand({ client, log });
    await cmd.parseAsync(['ha-mcp'], { from: 'user' });
    expect(client.get).toHaveBeenCalledWith('/api/v1/instances/inst-ok/logs');
  });

  it('selects specific replica with --instance', async () => {
    vi.mocked(client.get)
      .mockRejectedValueOnce(new Error('not found'))
      .mockResolvedValueOnce([{ id: 'srv-1', name: 'ha-mcp' }] as never)
      .mockResolvedValueOnce([
        { id: 'inst-0', status: 'RUNNING', containerId: 'a' },
        { id: 'inst-1', status: 'RUNNING', containerId: 'b' },
      ] as never)
      .mockResolvedValueOnce({ stdout: 'replica 1\n', stderr: '' } as never);
    const cmd = createLogsCommand({ client, log });
    await cmd.parseAsync(['ha-mcp', '-i', '1'], { from: 'user' });
    expect(client.get).toHaveBeenCalledWith('/api/v1/instances/inst-1/logs');
  });

  it('throws on out-of-range --instance index', async () => {
    vi.mocked(client.get)
      .mockRejectedValueOnce(new Error('not found'))
      .mockResolvedValueOnce([{ id: 'srv-1', name: 'ha-mcp' }] as never)
      .mockResolvedValueOnce([{ id: 'inst-0', status: 'RUNNING' }] as never);
    const cmd = createLogsCommand({ client, log });
    await expect(cmd.parseAsync(['ha-mcp', '-i', '5'], { from: 'user' })).rejects.toThrow('out of range');
  });

  it('throws when server has no instances', async () => {
    vi.mocked(client.get)
      .mockRejectedValueOnce(new Error('not found'))
      .mockResolvedValueOnce([{ id: 'srv-1', name: 'empty-srv' }] as never)
      .mockResolvedValueOnce([] as never);
    const cmd = createLogsCommand({ client, log });
    await expect(cmd.parseAsync(['empty-srv'], { from: 'user' })).rejects.toThrow('No instances found');
  });

  it('passes tail option', async () => {
    vi.mocked(client.get)
      .mockResolvedValueOnce({ id: 'inst-1' } as never)
      .mockResolvedValueOnce({ stdout: '', stderr: '' } as never);
    const cmd = createLogsCommand({ client, log });
    await cmd.parseAsync(['inst-1', '-t', '50'], { from: 'user' });
    expect(client.get).toHaveBeenCalledWith('/api/v1/instances/inst-1/logs?tail=50');
  });
});
