/**
 * @license
 * Copyright 2026 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { Config } from '../config/config.js';
import { MessageBus } from '../confirmation-bus/message-bus.js';
import type { PolicyEngine } from '../policy/policy-engine.js';
import {
  TrackerCreateTaskTool,
  TrackerListTasksTool,
  TrackerUpdateTaskTool,
  TrackerVisualizeTool,
  TrackerAddDependencyTool,
  buildTodosReturnDisplay,
} from './trackerTools.js';
import * as fs from 'node:fs/promises';
import * as path from 'node:path';
import * as os from 'node:os';

import { TaskStatus, TaskType } from '../services/trackerTypes.js';
import type { TrackerService } from '../services/trackerService.js';

describe('Tracker Tools Integration', () => {
  let tempDir: string;
  let config: Config;
  let messageBus: MessageBus;

  beforeEach(async () => {
    tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'tracker-tools-test-'));
    config = new Config({
      sessionId: 'test-session',
      targetDir: tempDir,
      cwd: tempDir,
      model: 'gemini-3-flash',
      debugMode: false,
    });
    await config.initialize();
    messageBus = new MessageBus(null as unknown as PolicyEngine, false);
  });

  afterEach(async () => {
    await fs.rm(tempDir, { recursive: true, force: true });
  });

  const getSignal = () => new AbortController().signal;

  it('creates and lists tasks', async () => {
    const createTool = new TrackerCreateTaskTool(config, messageBus);
    const createResult = await createTool.buildAndExecute(
      {
        title: 'Test Task',
        description: 'Test Description',
        type: TaskType.TASK,
      },
      getSignal(),
    );

    expect(createResult.llmContent).toContain('Created task');

    const listTool = new TrackerListTasksTool(config, messageBus);
    const listResult = await listTool.buildAndExecute({}, getSignal());
    expect(listResult.llmContent).toContain('Test Task');
    expect(listResult.llmContent).toContain(`(${TaskStatus.OPEN})`);
  });

  it('updates task status', async () => {
    const createTool = new TrackerCreateTaskTool(config, messageBus);
    await createTool.buildAndExecute(
      {
        title: 'Update Me',
        description: '...',
        type: TaskType.TASK,
      },
      getSignal(),
    );

    const tasks = await config.getTrackerService().listTasks();
    const taskId = tasks[0].id;

    const updateTool = new TrackerUpdateTaskTool(config, messageBus);
    const updateResult = await updateTool.buildAndExecute(
      {
        id: taskId,
        status: TaskStatus.IN_PROGRESS,
      },
      getSignal(),
    );

    expect(updateResult.llmContent).toContain(
      `Status: ${TaskStatus.IN_PROGRESS}`,
    );

    const task = await config.getTrackerService().getTask(taskId);
    expect(task?.status).toBe(TaskStatus.IN_PROGRESS);
  });

  it('adds dependencies and visualizes the graph', async () => {
    const createTool = new TrackerCreateTaskTool(config, messageBus);

    // Create Parent
    await createTool.buildAndExecute(
      {
        title: 'Parent Task',
        description: '...',
        type: TaskType.TASK,
      },
      getSignal(),
    );

    // Create Child
    await createTool.buildAndExecute(
      {
        title: 'Child Task',
        description: '...',
        type: TaskType.TASK,
      },
      getSignal(),
    );

    const tasks = await config.getTrackerService().listTasks();
    const parentTask = tasks.find((t) => t.title === 'Parent Task');
    const childTask = tasks.find((t) => t.title === 'Child Task');

    expect(parentTask).toBeDefined();
    expect(childTask).toBeDefined();

    const parentId = parentTask!.id;
    const childId = childTask!.id;

    // Add Dependency
    const addDepTool = new TrackerAddDependencyTool(config, messageBus);
    await addDepTool.buildAndExecute(
      {
        taskId: parentId,
        dependencyId: childId,
      },
      getSignal(),
    );

    const updatedParent = await config.getTrackerService().getTask(parentId);
    expect(updatedParent?.dependencies).toContain(childId);

    // Visualize
    const vizTool = new TrackerVisualizeTool(config, messageBus);
    const vizResult = await vizTool.buildAndExecute({}, getSignal());

    expect(vizResult.llmContent).toContain('Parent Task');
    expect(vizResult.llmContent).toContain('Child Task');
    expect(vizResult.llmContent).toContain(childId);
  });

  describe('buildTodosReturnDisplay', () => {
    it('returns empty list for no tasks', async () => {
      const mockService = {
        listTasks: async () => [],
      } as unknown as TrackerService;
      const result = await buildTodosReturnDisplay(mockService);
      expect(result.todos).toEqual([]);
    });

    it('returns formatted todos', async () => {
      const parent = {
        id: 'p1',
        title: 'Parent',
        type: TaskType.TASK,
        status: TaskStatus.IN_PROGRESS,
        dependencies: [],
      };
      const child = {
        id: 'c1',
        title: 'Child',
        type: TaskType.EPIC,
        status: TaskStatus.OPEN,
        parentId: 'p1',
        dependencies: [],
      };
      const closedLeaf = {
        id: 'leaf',
        title: 'Closed Leaf',
        type: TaskType.BUG,
        status: TaskStatus.CLOSED,
        parentId: 'c1',
        dependencies: [],
      };

      const mockService = {
        listTasks: async () => [parent, child, closedLeaf],
      } as unknown as TrackerService;
      const display = await buildTodosReturnDisplay(mockService);

      expect(display.todos).toEqual([
        {
          description: `task: Parent (p1)`,
          status: 'in_progress',
        },
        {
          description: `  epic: Child (c1)`,
          status: 'pending',
        },
        {
          description: `    bug: Closed Leaf (leaf)`,
          status: 'completed',
        },
      ]);
    });

    it('sorts tasks by status', async () => {
      const t1 = {
        id: 't1',
        title: 'T1',
        type: TaskType.TASK,
        status: TaskStatus.CLOSED,
        dependencies: [],
      };
      const t2 = {
        id: 't2',
        title: 'T2',
        type: TaskType.TASK,
        status: TaskStatus.OPEN,
        dependencies: [],
      };
      const t3 = {
        id: 't3',
        title: 'T3',
        type: TaskType.TASK,
        status: TaskStatus.IN_PROGRESS,
        dependencies: [],
      };
      const t4 = {
        id: 't4',
        title: 'T4',
        type: TaskType.TASK,
        status: TaskStatus.BLOCKED,
        dependencies: [],
      };

      const mockService = {
        listTasks: async () => [t1, t2, t3, t4],
      } as unknown as TrackerService;
      const display = await buildTodosReturnDisplay(mockService);

      expect(display.todos).toEqual([
        { description: `task: T3 (t3)`, status: 'in_progress' },
        { description: `task: T2 (t2)`, status: 'pending' },
        { description: `task: T4 (t4)`, status: 'blocked' },
        { description: `task: T1 (t1)`, status: 'completed' },
      ]);
    });

    it('detects cycles', async () => {
      // Since TrackerTask only has a single parentId, a true cycle is unreachable from roots.
      // We simulate a database corruption (two tasks with same ID, one root, one child)
      // just to exercise the protective cycle detection branch.
      const rootP1 = {
        id: 'p1',
        title: 'Parent',
        type: TaskType.TASK,
        status: TaskStatus.OPEN,
        dependencies: [],
      };
      const childP1 = { ...rootP1, parentId: 'p1' };

      const mockService = {
        listTasks: async () => [rootP1, childP1],
      } as unknown as TrackerService;
      const display = await buildTodosReturnDisplay(mockService);

      expect(display.todos).toEqual([
        {
          description: `task: Parent (p1)`,
          status: 'pending',
        },
        {
          description: `  [CYCLE DETECTED: p1]`,
          status: 'cancelled',
        },
      ]);
    });
  });
});
