import { describe, it, expect, vi } from 'vitest';
import { PaseoClient, PaseoClientError } from '../paseo-client.js';

/**
 * Create a PaseoClient whose runCli method is replaced with a mock.
 * The mock is returned as the second tuple element so tests can
 * control and inspect it directly.
 */
function makeClient(config?: { paseoBin?: string; cliHost?: string }): {
  client: PaseoClient;
  mockRunCli: ReturnType<typeof vi.fn>;
} {
  const client = new PaseoClient(config);
  const mockRunCli = vi.fn();
  (client as any).runCli = mockRunCli;
  return { client, mockRunCli };
}

describe('PaseoClient', () => {
  describe('listAgents', () => {
    it('returns parsed agent list from paseo ls --json', async () => {
      const agents = [
        { id: 'abc-123', shortId: 'abc', name: 'Agent 1', provider: 'opencode', status: 'running' },
        { id: 'def-456', shortId: 'def', name: 'Agent 2', provider: 'claude', status: 'idle' },
      ];
      const { client, mockRunCli } = makeClient();
      mockRunCli.mockResolvedValue(JSON.stringify(agents));

      const result = await client.listAgents();

      expect(mockRunCli).toHaveBeenCalledWith(['ls', '--json']);
      expect(result).toEqual(agents);
    });

    it('throws PaseoClientError on non-JSON output', async () => {
      const { client, mockRunCli } = makeClient();
      mockRunCli.mockResolvedValue('not json');

      await expect(client.listAgents()).rejects.toThrow(PaseoClientError);
      await expect(client.listAgents()).rejects.toThrow(/invalid JSON/);
    });

    it('propagates runCli rejection as-is', async () => {
      const { client, mockRunCli } = makeClient();
      const err = new PaseoClientError('ls failed: connection refused', 'ls', 1, 'connection refused');
      mockRunCli.mockRejectedValue(err);

      await expect(client.listAgents()).rejects.toThrow(PaseoClientError);
      await expect(client.listAgents()).rejects.toThrow(/ls failed/);
    });
  });

  describe('getAgentStatus', () => {
    it('returns parsed agent detail from paseo inspect --json', async () => {
      const detail = {
        Id: 'abc-123', Name: 'Agent 1', Provider: 'opencode',
        Status: 'idle', Archived: false,
        CreatedAt: '2026-01-01T00:00:00Z', UpdatedAt: '2026-01-01T01:00:00Z',
      };
      const { client, mockRunCli } = makeClient();
      mockRunCli.mockResolvedValue(JSON.stringify(detail));

      const result = await client.getAgentStatus('abc-123');

      expect(mockRunCli).toHaveBeenCalledWith(['inspect', '--json', 'abc-123']);
      expect(result.Id).toBe('abc-123');
      expect(result.Status).toBe('idle');
    });
  });

  describe('health', () => {
    it('returns ok when paseo ls succeeds', async () => {
      const { client, mockRunCli } = makeClient();
      mockRunCli.mockResolvedValue('[]');

      const result = await client.health();

      expect(result).toEqual({ status: 'ok' });
    });

    it('returns error when runCli throws', async () => {
      const { client, mockRunCli } = makeClient();
      mockRunCli.mockRejectedValue(new Error('connection refused'));

      const result = await client.health();

      expect(result).toEqual({ status: 'error' });
    });
  });

  describe('importAgent', () => {
    it('calls paseo import with provider and labels', async () => {
      const agentResult = { Id: 'new-789', Name: 'Imported', Provider: 'opencode', Status: 'idle' };
      const { client, mockRunCli } = makeClient();
      mockRunCli.mockResolvedValue(JSON.stringify(agentResult));

      const result = await client.importAgent('ses-001', 'opencode', {
        origin: 'boocode',
        project: 'proj-1',
      });

      expect(mockRunCli).toHaveBeenCalledWith([
        'import', '--json',
        '--provider', 'opencode',
        '--label', 'origin=boocode',
        '--label', 'project=proj-1',
        'ses-001',
      ]);
      expect(result.Id).toBe('new-789');
    });

    it('works without labels', async () => {
      const { client, mockRunCli } = makeClient();
      mockRunCli.mockResolvedValue(JSON.stringify({ Id: 'new-789' }));

      const result = await client.importAgent('ses-001', 'claude');

      expect(mockRunCli).toHaveBeenCalledWith([
        'import', '--json',
        '--provider', 'claude',
        'ses-001',
      ]);
      expect(result.Id).toBe('new-789');
    });
  });

  describe('archiveAgent', () => {
    it('calls paseo archive --json', async () => {
      const { client, mockRunCli } = makeClient();
      mockRunCli.mockResolvedValue('{}');

      await client.archiveAgent('abc-123');

      expect(mockRunCli).toHaveBeenCalledWith(['archive', '--json', 'abc-123']);
    });
  });

  describe('sendPrompt', () => {
    it('sends prompt and parses JSON result', async () => {
      const sendResult = { text: 'Hello!', ok: true };
      const { client, mockRunCli } = makeClient();
      mockRunCli.mockResolvedValue(JSON.stringify(sendResult));

      const result = await client.sendPrompt('abc-123', 'Hello');

      expect(mockRunCli).toHaveBeenCalledWith(['send', '--json', 'abc-123', 'Hello'], undefined);
      expect(result).toEqual(sendResult);
    });

    it('falls back to plain text on non-JSON output', async () => {
      const { client, mockRunCli } = makeClient();
      mockRunCli.mockResolvedValue('plain text response');

      const result = await client.sendPrompt('abc-123', 'Hi');

      expect(result).toEqual({ text: 'plain text response', ok: true });
    });

    it('supports --no-wait flag', async () => {
      const { client, mockRunCli } = makeClient();
      mockRunCli.mockResolvedValue('{}');

      await client.sendPrompt('abc-123', 'Hi', { noWait: true });

      expect(mockRunCli).toHaveBeenCalledWith([
        'send', '--json', '--no-wait',
        'abc-123', 'Hi',
      ], undefined);
    });
  });

  describe('stopAgent', () => {
    it('calls paseo stop', async () => {
      const { client, mockRunCli } = makeClient();
      mockRunCli.mockResolvedValue('');

      await client.stopAgent('abc-123');

      expect(mockRunCli).toHaveBeenCalledWith(['stop', 'abc-123']);
    });
  });

  describe('cliHost config', () => {
    it('includes --host flag in args when cliHost is set', async () => {
      const { client, mockRunCli } = makeClient({ cliHost: 'tcp://localhost:6767?ssl=true' });
      mockRunCli.mockResolvedValue('[]');

      await client.listAgents();

      expect(mockRunCli).toHaveBeenCalledWith([
        'ls', '--json', '--host', 'tcp://localhost:6767?ssl=true',
      ]);
    });
  });
});
