/**
 * taskManagerTool tests for search + backfill-search-index.
 *
 * Pins the wiring contracts only — the TaskIndexer's internals have
 * their own dedicated suite. We test:
 *   - search dispatches to indexer when embeddings are enabled
 *   - search falls back to substring-on-title/description when disabled
 *   - status filter forwards correctly
 *   - missing query → clean error
 *   - hits hydrate back to live tasks from agent.taskList
 *   - backfill walks agent.taskList.tasks + reports counts
 *
 * Mocks: stub TaskIndexer at the module boundary.
 */

import { jest, describe, test, expect, beforeEach } from '@jest/globals';

const mockIndexerInstance = {
  isEnabled: true,
  indexOne:  jest.fn().mockResolvedValue({ indexed: true }),
  deleteOne: jest.fn().mockResolvedValue({ deleted: true }),
  backfill:  jest.fn().mockResolvedValue({ ok: true, indexed: 0, skipped: 0, failed: 0 }),
  search:    jest.fn().mockResolvedValue({ enabled: true, hits: [] }),
};
const MockTaskIndexer = jest.fn(() => mockIndexerInstance);
jest.unstable_mockModule('../../services/embeddings/indexers/taskIndexer.js', () => ({
  TaskIndexer: MockTaskIndexer,
  taskToText: () => '',
}));

const { default: TaskManagerTool } = await import('../taskManagerTool.js');

function makeAgent(tasks = []) {
  return {
    id: 'agent-1',
    taskList: { tasks },
    status: 'active',
  };
}

function makeTool({ embeddingsEnabled = true } = {}) {
  const tool = new TaskManagerTool();
  mockIndexerInstance.isEnabled = embeddingsEnabled;
  mockIndexerInstance.search.mockClear();
  mockIndexerInstance.backfill.mockClear();
  MockTaskIndexer.mockClear();
  tool.aiService = { getEmbeddingService: () => ({ isEnabled: embeddingsEnabled }) };
  tool._taskIndexer = null;
  return tool;
}

const tasks = [
  { id: 't1', title: 'Refactor auth flow',       description: 'Move to OAuth 2.1',   status: 'pending',     priority: 'high'   },
  { id: 't2', title: 'Investigate Telegram dedup', description: 'Why are we flooding?', status: 'in_progress', priority: 'high'   },
  { id: 't3', title: 'Update README',             description: 'Add embeddings docs', status: 'completed',   priority: 'medium' },
];

describe('taskManagerTool — search wiring', () => {
  test('missing query → clean error', async () => {
    const tool = makeTool();
    const r = await tool.searchTasks(makeAgent(tasks), {});
    expect(r.success).toBe(false);
    expect(r.error).toMatch(/query/i);
  });

  test('disabled embeddings → substring fallback over title/description', async () => {
    const tool = makeTool({ embeddingsEnabled: false });
    const r = await tool.searchTasks(makeAgent(tasks), { query: 'telegram' });
    expect(mockIndexerInstance.search).not.toHaveBeenCalled();
    expect(r.mode).toBe('substring-fallback');
    expect(r.results.some(t => t.id === 't2')).toBe(true);
  });

  test('disabled fallback respects status filter', async () => {
    const tool = makeTool({ embeddingsEnabled: false });
    const r = await tool.searchTasks(makeAgent(tasks), { query: 'auth', status: 'pending' });
    expect(r.results.every(t => t.status === 'pending')).toBe(true);
  });

  test('enabled embeddings → forwards query + topK + hybrid + statusFilter to indexer', async () => {
    const tool = makeTool();
    mockIndexerInstance.search.mockResolvedValue({ enabled: true, hits: [] });
    await tool.searchTasks(makeAgent(tasks), {
      query: 'telegram', topK: 3, hybrid: false, status: 'in_progress',
    });
    expect(mockIndexerInstance.search).toHaveBeenCalledWith(
      'agent-1', 'telegram',
      { topK: 3, hybrid: false, statusFilter: 'in_progress' },
    );
  });

  test('enabled embeddings → hydrates hits back to live tasks', async () => {
    const tool = makeTool();
    mockIndexerInstance.search.mockResolvedValue({
      enabled: true,
      hits: [
        { id: 't2', score: 0.9, metadata: { title: 'snap', status: 'in_progress' } },
        { id: 'gone', score: 0.5, metadata: { title: 'archived', status: 'pending' } },
      ],
    });
    const r = await tool.searchTasks(makeAgent(tasks), { query: 'q' });
    expect(r.results[0]).toMatchObject({ id: 't2', title: 'Investigate Telegram dedup', score: 0.9 });
    expect(r.results[1]).toMatchObject({ id: 'gone', missing: true });
  });

  test('indexer error surfaces in result', async () => {
    const tool = makeTool();
    mockIndexerInstance.search.mockResolvedValue({ enabled: true, hits: [], error: 'rate-limited' });
    const r = await tool.searchTasks(makeAgent(tasks), { query: 'q' });
    expect(r.success).toBe(false);
    expect(r.error).toBe('rate-limited');
  });
});

describe('taskManagerTool — backfill-search-index wiring', () => {
  test('disabled → clean error', async () => {
    const tool = makeTool({ embeddingsEnabled: false });
    const r = await tool.backfillTasksSearchIndex(makeAgent(tasks));
    expect(r.success).toBe(false);
    expect(r.message).toMatch(/not enabled/i);
    expect(mockIndexerInstance.backfill).not.toHaveBeenCalled();
  });

  test('enabled → walks agent.taskList.tasks + calls indexer.backfill', async () => {
    const tool = makeTool();
    mockIndexerInstance.backfill.mockResolvedValue({ ok: true, indexed: 3, skipped: 0, failed: 0 });
    const r = await tool.backfillTasksSearchIndex(makeAgent(tasks));
    expect(mockIndexerInstance.backfill).toHaveBeenCalledWith('agent-1', tasks);
    expect(r.success).toBe(true);
    expect(r.indexed).toBe(3);
  });

  test('reports failure counts honestly', async () => {
    const tool = makeTool();
    mockIndexerInstance.backfill.mockResolvedValue({
      ok: false, indexed: 0, skipped: 0, failed: 3, error: 'rate-limited',
    });
    const r = await tool.backfillTasksSearchIndex(makeAgent(tasks));
    expect(r.success).toBe(false);
    expect(r.failed).toBe(3);
    expect(r.error).toBe('rate-limited');
  });
});
