// Tests for TauriAdapter — verifies correct Tauri command names + param mapping

import { describe, it, expect, vi, beforeEach } from 'vitest';

const { mockInvoke, mockListen } = vi.hoisted(() => ({
  mockInvoke: vi.fn(),
  mockListen: vi.fn(),
}));

vi.mock('@tauri-apps/api/core', () => ({ invoke: mockInvoke }));
vi.mock('@tauri-apps/api/event', () => ({ listen: mockListen }));

import { TauriAdapter } from '../TauriAdapter';
import type { BackendAdapter } from '@agor/types';

let adapter: TauriAdapter;

beforeEach(() => {
  vi.clearAllMocks();
  adapter = new TauriAdapter();
});

describe('TauriAdapter', () => {
  it('implements BackendAdapter interface', () => {
    const ba: BackendAdapter = adapter;
    expect(ba.getSetting).toBeTypeOf('function');
    expect(ba.startAgent).toBeTypeOf('function');
    expect(ba.createPty).toBeTypeOf('function');
    expect(ba.btmsgSend).toBeTypeOf('function');
    expect(ba.bttaskList).toBeTypeOf('function');
    expect(ba.memoraAvailable).toBeTypeOf('function');
  });

  it('has correct capabilities', () => {
    expect(adapter.capabilities.supportsPtyMultiplexing).toBe(true);
    expect(adapter.capabilities.supportsOsKeychain).toBe(true);
    expect(adapter.capabilities.supportsDesktopNotifications).toBe(true);
    expect(adapter.capabilities.supportsTelemetry).toBe(true);
  });

  // ── Settings ──────────────────────────────────────────────────────────

  describe('settings', () => {
    it('getSetting invokes settings_get', async () => {
      mockInvoke.mockResolvedValue('dark');
      expect(await adapter.getSetting('theme')).toBe('dark');
      expect(mockInvoke).toHaveBeenCalledWith('settings_get', { key: 'theme' });
    });

    it('setSetting invokes settings_set', async () => {
      mockInvoke.mockResolvedValue(undefined);
      await adapter.setSetting('theme', 'mocha');
      expect(mockInvoke).toHaveBeenCalledWith('settings_set', { key: 'theme', value: 'mocha' });
    });

    it('getAllSettings invokes settings_list and returns map', async () => {
      mockInvoke.mockResolvedValue([['theme', 'mocha'], ['shell', '/bin/bash']]);
      expect(await adapter.getAllSettings()).toEqual({ theme: 'mocha', shell: '/bin/bash' });
    });
  });

  // ── Agent ─────────────────────────────────────────────────────────────

  describe('agent', () => {
    it('startAgent invokes agent_query with mapped options', async () => {
      mockInvoke.mockResolvedValue(undefined);
      const result = await adapter.startAgent({ sessionId: 's1', prompt: 'Hello', cwd: '/tmp', provider: 'claude', maxTurns: 10 });
      expect(mockInvoke).toHaveBeenCalledWith('agent_query', {
        options: expect.objectContaining({ session_id: 's1', prompt: 'Hello', provider: 'claude', max_turns: 10 }),
      });
      expect(result).toEqual({ ok: true });
    });

    it('startAgent returns error on failure', async () => {
      mockInvoke.mockRejectedValue(new Error('Sidecar not running'));
      const result = await adapter.startAgent({ sessionId: 's2', prompt: 'test' });
      expect(result.ok).toBe(false);
      expect(result.error).toContain('Sidecar not running');
    });

    it('stopAgent invokes agent_stop', async () => {
      mockInvoke.mockResolvedValue(undefined);
      expect(await adapter.stopAgent('s1')).toEqual({ ok: true });
      expect(mockInvoke).toHaveBeenCalledWith('agent_stop', { sessionId: 's1' });
    });

    it('isAgentReady invokes agent_ready', async () => {
      mockInvoke.mockResolvedValue(true);
      expect(await adapter.isAgentReady()).toBe(true);
      expect(mockInvoke).toHaveBeenCalledWith('agent_ready');
    });

    it('queryAgent routes local vs remote', async () => {
      mockInvoke.mockResolvedValue(undefined);
      await adapter.queryAgent({ session_id: 's1', prompt: 'hi' });
      expect(mockInvoke).toHaveBeenCalledWith('agent_query', { options: { session_id: 's1', prompt: 'hi' } });

      mockInvoke.mockClear();
      await adapter.queryAgent({ session_id: 's1', prompt: 'hi', remote_machine_id: 'm1' });
      expect(mockInvoke).toHaveBeenCalledWith('remote_agent_query', {
        machineId: 'm1', options: { session_id: 's1', prompt: 'hi' },
      });
    });
  });

  // ── PTY ───────────────────────────────────────────────────────────────

  describe('pty', () => {
    it('createPty invokes pty_spawn', async () => {
      mockInvoke.mockResolvedValue('pty-1');
      expect(await adapter.createPty({ sessionId: 's1', cols: 80, rows: 24 })).toBe('pty-1');
      expect(mockInvoke).toHaveBeenCalledWith('pty_spawn', { options: expect.objectContaining({ cols: 80, rows: 24 }) });
    });

    it('writePty/resizePty/closePty invoke correct commands', async () => {
      mockInvoke.mockResolvedValue(undefined);
      await adapter.writePty('p1', 'hi');
      expect(mockInvoke).toHaveBeenCalledWith('pty_write', { id: 'p1', data: 'hi' });

      await adapter.resizePty('p1', 120, 40);
      expect(mockInvoke).toHaveBeenCalledWith('pty_resize', { id: 'p1', cols: 120, rows: 40 });

      await adapter.closePty('p1');
      expect(mockInvoke).toHaveBeenCalledWith('pty_kill', { id: 'p1' });
    });

    it('spawnPty uses remote_pty_spawn for remote', async () => {
      mockInvoke.mockResolvedValue('rpty-1');
      await adapter.spawnPty({ remote_machine_id: 'm1', cols: 80, rows: 24 });
      expect(mockInvoke).toHaveBeenCalledWith('remote_pty_spawn', { machineId: 'm1', options: { cols: 80, rows: 24 } });
    });
  });

  // ── Files ─────────────────────────────────────────────────────────────

  describe('files', () => {
    it('listDirectory invokes list_directory_children and maps fields', async () => {
      mockInvoke.mockResolvedValue([{ name: 'f.ts', path: '/tmp/f.ts', is_dir: false, size: 100, ext: 'ts' }]);
      const result = await adapter.listDirectory('/tmp');
      expect(mockInvoke).toHaveBeenCalledWith('list_directory_children', { path: '/tmp' });
      expect(result).toEqual([{ name: 'f.ts', path: '/tmp/f.ts', isDir: false, size: 100, ext: 'ts' }]);
    });

    it('readFile/writeFile invoke correct commands', async () => {
      mockInvoke.mockResolvedValue({ type: 'Text', content: 'hello' });
      expect(await adapter.readFile('/tmp/f.ts')).toEqual({ type: 'Text', content: 'hello' });
      expect(mockInvoke).toHaveBeenCalledWith('read_file_content', { path: '/tmp/f.ts' });

      mockInvoke.mockResolvedValue(undefined);
      await adapter.writeFile('/tmp/f.ts', 'x');
      expect(mockInvoke).toHaveBeenCalledWith('write_file_content', { path: '/tmp/f.ts', content: 'x' });
    });
  });

  // ── Btmsg ─────────────────────────────────────────────────────────────

  describe('btmsg', () => {
    it('btmsgGetAgents invokes btmsg_get_agents', async () => {
      mockInvoke.mockResolvedValue([]);
      await adapter.btmsgGetAgents('g1' as any);
      expect(mockInvoke).toHaveBeenCalledWith('btmsg_get_agents', { groupId: 'g1' });
    });

    it('btmsgSend invokes btmsg_send with camelCase params', async () => {
      mockInvoke.mockResolvedValue('msg-1');
      expect(await adapter.btmsgSend('a1' as any, 'a2' as any, 'hello')).toBe('msg-1');
      expect(mockInvoke).toHaveBeenCalledWith('btmsg_send', { fromAgent: 'a1', toAgent: 'a2', content: 'hello' });
    });

    it('btmsgHistory/btmsgMarkRead/btmsgCreateChannel/btmsgRecordHeartbeat use correct commands', async () => {
      mockInvoke.mockResolvedValue([]);
      await adapter.btmsgHistory('a1' as any, 'a2' as any, 50);
      expect(mockInvoke).toHaveBeenCalledWith('btmsg_history', { agentId: 'a1', otherId: 'a2', limit: 50 });

      mockInvoke.mockResolvedValue(undefined);
      await adapter.btmsgMarkRead('r' as any, 's' as any);
      expect(mockInvoke).toHaveBeenCalledWith('btmsg_mark_read', { readerId: 'r', senderId: 's' });

      mockInvoke.mockResolvedValue('ch-1');
      expect(await adapter.btmsgCreateChannel('gen', 'g1' as any, 'admin' as any)).toBe('ch-1');
      expect(mockInvoke).toHaveBeenCalledWith('btmsg_create_channel', { name: 'gen', groupId: 'g1', createdBy: 'admin' });

      mockInvoke.mockResolvedValue(undefined);
      await adapter.btmsgRecordHeartbeat('a1' as any);
      expect(mockInvoke).toHaveBeenCalledWith('btmsg_record_heartbeat', { agentId: 'a1' });
    });
  });

  // ── Bttask ────────────────────────────────────────────────────────────

  describe('bttask', () => {
    it('bttaskList/bttaskComments invoke correct commands', async () => {
      mockInvoke.mockResolvedValue([]);
      await adapter.bttaskList('g1' as any);
      expect(mockInvoke).toHaveBeenCalledWith('bttask_list', { groupId: 'g1' });

      await adapter.bttaskComments('t1');
      expect(mockInvoke).toHaveBeenCalledWith('bttask_comments', { taskId: 't1' });
    });

    it('bttaskCreate invokes bttask_create with all params', async () => {
      mockInvoke.mockResolvedValue('t1');
      expect(await adapter.bttaskCreate('Fix', 'Desc', 'high', 'g1' as any, 'admin' as any, 'dev' as any)).toBe('t1');
      expect(mockInvoke).toHaveBeenCalledWith('bttask_create', {
        title: 'Fix', description: 'Desc', priority: 'high', groupId: 'g1', createdBy: 'admin', assignedTo: 'dev',
      });
    });

    it('bttaskUpdateStatus uses optimistic locking', async () => {
      mockInvoke.mockResolvedValue(2);
      expect(await adapter.bttaskUpdateStatus('t1', 'review', 1)).toBe(2);
      expect(mockInvoke).toHaveBeenCalledWith('bttask_update_status', { taskId: 't1', status: 'review', version: 1 });
    });

    it('bttaskReviewQueueCount invokes bttask_review_queue_count', async () => {
      mockInvoke.mockResolvedValue(3);
      expect(await adapter.bttaskReviewQueueCount('g1' as any)).toBe(3);
    });
  });

  // ── Memora ────────────────────────────────────────────────────────────

  describe('memora', () => {
    it('memoraAvailable/memoraList/memoraSearch/memoraGet invoke correct commands', async () => {
      mockInvoke.mockResolvedValue(true);
      expect(await adapter.memoraAvailable()).toBe(true);
      expect(mockInvoke).toHaveBeenCalledWith('memora_available');

      mockInvoke.mockResolvedValue({ nodes: [], total: 0 });
      await adapter.memoraList();
      expect(mockInvoke).toHaveBeenCalledWith('memora_list', { tags: null, limit: 50, offset: 0 });

      await adapter.memoraSearch('arch', { tags: ['agor'], limit: 10 });
      expect(mockInvoke).toHaveBeenCalledWith('memora_search', { query: 'arch', tags: ['agor'], limit: 10 });

      mockInvoke.mockResolvedValue({ id: 5, content: 'test', tags: [] });
      expect(await adapter.memoraGet(5)).toEqual({ id: 5, content: 'test', tags: [] });
      expect(mockInvoke).toHaveBeenCalledWith('memora_get', { id: 5 });
    });
  });

  // ── Secrets + Groups ──────────────────────────────────────────────────

  describe('secrets', () => {
    it('storeSecret/getSecret invoke correct commands', async () => {
      mockInvoke.mockResolvedValue(undefined);
      await adapter.storeSecret('key', 'val');
      expect(mockInvoke).toHaveBeenCalledWith('secrets_store', { key: 'key', value: 'val' });

      mockInvoke.mockResolvedValue('val');
      expect(await adapter.getSecret('key')).toBe('val');
      expect(mockInvoke).toHaveBeenCalledWith('secrets_get', { key: 'key' });
    });
  });

  describe('groups', () => {
    it('loadGroups/saveGroups invoke correct commands', async () => {
      const groups = { version: 1, groups: [], activeGroupId: 'default' };
      mockInvoke.mockResolvedValue(groups);
      expect(await adapter.loadGroups()).toEqual(groups);
      expect(mockInvoke).toHaveBeenCalledWith('groups_load');

      mockInvoke.mockResolvedValue(undefined);
      await adapter.saveGroups(groups as any);
      expect(mockInvoke).toHaveBeenCalledWith('groups_save', { config: groups });
    });
  });
});
