import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import { TaskService, type TaskEvent } from '../src/task-service.js';

const AGENT_A = 'agent-a';
const AGENT_B = 'agent-b';
const REVIEWER = 'reviewer-1';

function createDefaults(overrides: Record<string, unknown> = {}) {
  return {
    orgId: 'org-1',
    title: 'Task',
    description: '',
    assignedAgentId: AGENT_A,
    reviewerId: REVIEWER,
    ...overrides,
  };
}

describe('TaskService - Dependencies & Timeouts', () => {
  let ts: TaskService;

  beforeEach(() => {
    ts = new TaskService();
    ts.setGovernancePolicy({
      enabled: false,
      defaultTier: 'auto',
      maxPendingTasksPerAgent: 100,
      maxTotalActiveTasks: 100,
      requireApprovalForPriority: [],
      requireRequirement: false,
      rules: [],
    });
  });

  afterEach(() => {
    ts.stopTimeoutChecker();
  });

  function createAndApprove(overrides: Record<string, unknown> = {}) {
    const task = ts.createTask(createDefaults(overrides) as any);
    return ts.getTask(task.id)!;
  }

  describe('blockedBy dependencies', () => {
    it('creates a task as blocked when it has blockers', () => {
      const dep = createAndApprove({ title: 'Dependency', description: 'dep' });
      const task = ts.createTask(createDefaults({
        title: 'Blocked Task',
        description: 'needs dep',
        blockedBy: [dep.id],
      }) as any);

      expect(ts.getTask(task.id)!.status).toBe('blocked');
      expect(ts.getTask(task.id)!.blockedBy).toContain(dep.id);
    });

    it('unblocks a task when all dependencies complete', () => {
      const dep1 = createAndApprove({ title: 'Dep 1' });
      const dep2 = createAndApprove({ title: 'Dep 2' });
      const blocked = ts.createTask(createDefaults({
        title: 'Blocked',
        blockedBy: [dep1.id, dep2.id],
      }) as any);

      expect(ts.getTask(blocked.id)!.status).toBe('blocked');

      ts.updateTaskStatus(dep1.id, 'review');
      ts.updateTaskStatus(dep1.id, 'completed');
      expect(ts.getTask(blocked.id)!.status).toBe('blocked');

      ts.updateTaskStatus(dep2.id, 'review');
      ts.updateTaskStatus(dep2.id, 'completed');
      expect(ts.getTask(blocked.id)!.status).toBe('in_progress');
    });

    it('unblocks with assigned agent transitions to in_progress', () => {
      const dep = createAndApprove({ title: 'Dep' });
      const blocked = ts.createTask(createDefaults({
        title: 'Blocked',
        blockedBy: [dep.id],
        assignedAgentId: AGENT_B,
      }) as any);

      expect(ts.getTask(blocked.id)!.status).toBe('blocked');

      ts.updateTaskStatus(dep.id, 'review');
      ts.updateTaskStatus(dep.id, 'completed');
      expect(ts.getTask(blocked.id)!.status).toBe('in_progress');
    });

    it('prevents starting a blocked task', () => {
      const dep = createAndApprove({ title: 'Dep' });
      const blocked = ts.createTask(createDefaults({
        title: 'Blocked',
        blockedBy: [dep.id],
      }) as any);

      expect(() => ts.updateTaskStatus(blocked.id, 'in_progress'))
        .toThrow('blocked by unfinished dependencies');
    });

    it('emits status_changed event when unblocked', () => {
      const events: TaskEvent[] = [];
      ts.onTaskEvent(e => events.push(e));

      const dep = createAndApprove({ title: 'Dep' });
      const blocked = ts.createTask(createDefaults({
        title: 'Blocked',
        blockedBy: [dep.id],
      }) as any);

      ts.updateTaskStatus(dep.id, 'review');
      ts.updateTaskStatus(dep.id, 'completed');

      const statusChanged = events.find(e => e.type === 'status_changed' && e.taskId === blocked.id);
      expect(statusChanged).toBeDefined();
      expect(statusChanged!.previousStatus).toBe('blocked');
    });
  });

  describe('task events / webhooks', () => {
    it('emits created event', () => {
      const events: TaskEvent[] = [];
      ts.onTaskEvent(e => events.push(e));

      ts.createTask(createDefaults({ title: 'New Task', description: 'test' }) as any);

      expect(events).toHaveLength(1);
      expect(events[0].type).toBe('created');
      expect(events[0].taskTitle).toBe('New Task');
    });

    it('emits completed event', () => {
      const events: TaskEvent[] = [];
      ts.onTaskEvent(e => events.push(e));

      const task = createAndApprove({ title: 'Task', description: 'test' });
      ts.updateTaskStatus(task.id, 'review');
      ts.updateTaskStatus(task.id, 'completed');

      const completed = events.find(e => e.type === 'completed');
      expect(completed).toBeDefined();
      expect(completed!.previousStatus).toBe('review');
    });

    it('emits failed event', () => {
      const events: TaskEvent[] = [];
      ts.onTaskEvent(e => events.push(e));

      const task = createAndApprove({ title: 'Task' });
      ts.updateTaskStatus(task.id, 'failed');

      const failed = events.find(e => e.type === 'failed');
      expect(failed).toBeDefined();
    });

    it('handles webhook errors gracefully', () => {
      ts.onTaskEvent(() => { throw new Error('webhook broke'); });
      expect(() => ts.createTask(createDefaults() as any)).not.toThrow();
    });
  });

  describe('startedAt tracking', () => {
    it('sets startedAt when task is approved and starts in_progress', () => {
      const task = ts.createTask(createDefaults({ title: 'Task' }) as any);
      expect(task.startedAt).toBeUndefined();
    });
  });

  describe('timeout detection', () => {
    it('detects timed-out tasks', () => {
      const task = createAndApprove({
        title: 'Timeout Task',
        timeoutMs: 100,
      });

      const t = ts.getTask(task.id)!;
      t.startedAt = new Date(Date.now() - 200).toISOString();

      const events: TaskEvent[] = [];
      ts.onTaskEvent(e => events.push(e));

      (ts as any).checkTimeouts();

      expect(ts.getTask(task.id)!.status).toBe('failed');
      const timeoutEvent = events.find(e => e.type === 'timeout');
      expect(timeoutEvent).toBeDefined();
      expect(timeoutEvent!.metadata?.reason).toBe('timeout');
    });

    it('does not timeout tasks without timeoutMs', () => {
      const task = createAndApprove({ title: 'No Timeout' });
      const t = ts.getTask(task.id)!;
      t.startedAt = new Date(Date.now() - 999999).toISOString();

      (ts as any).checkTimeouts();
      expect(ts.getTask(task.id)!.status).toBe('in_progress');
    });
  });

  describe('embedded subtasks', () => {
    it('can add, complete and cancel subtasks within a task', () => {
      const task = createAndApprove({ title: 'Parent' });

      const sub1 = ts.addSubtask(task.id, 'Sub 1');
      const sub2 = ts.addSubtask(task.id, 'Sub 2');

      expect(ts.getTask(task.id)!.subtasks).toHaveLength(2);
      expect(sub1.status).toBe('pending');
      expect(sub2.status).toBe('pending');

      ts.completeSubtask(task.id, sub1.id);
      expect(ts.getTask(task.id)!.subtasks.find(s => s.id === sub1.id)!.status).toBe('completed');

      ts.cancelSubtask(task.id, sub2.id);
      expect(ts.getTask(task.id)!.subtasks.find(s => s.id === sub2.id)!.status).toBe('cancelled');
    });

    it('can delete a subtask', () => {
      const task = createAndApprove({ title: 'Parent' });
      const sub = ts.addSubtask(task.id, 'Sub to delete');
      expect(ts.getTask(task.id)!.subtasks).toHaveLength(1);

      ts.deleteSubtask(task.id, sub.id);
      expect(ts.getTask(task.id)!.subtasks).toHaveLength(0);
    });
  });

  describe('findDuplicateTasks', () => {
    it('finds tasks with identical titles in same requirement/agent scope', () => {
      ts.createTask(createDefaults({
        title: 'Implement login',
        description: 'first',
        requirementId: 'req-1',
      }) as any);
      ts.createTask(createDefaults({
        title: 'Implement login',
        description: 'duplicate',
        requirementId: 'req-1',
      }) as any);

      const groups = ts.findDuplicateTasks('org-1');
      expect(groups.length).toBe(1);
      expect(groups[0].tasks.length).toBe(2);
    });

    it('finds tasks with similar titles (containment)', () => {
      ts.createTask(createDefaults({
        title: 'Add user authentication',
        requirementId: 'req-1',
      }) as any);
      ts.createTask(createDefaults({
        title: 'add user authentication module',
        requirementId: 'req-1',
      }) as any);

      const groups = ts.findDuplicateTasks('org-1');
      expect(groups.length).toBe(1);
    });

    it('does not flag unrelated tasks as duplicates', () => {
      ts.createTask(createDefaults({ title: 'Setup database' }) as any);
      ts.createTask(createDefaults({ title: 'Design frontend UI' }) as any);

      const groups = ts.findDuplicateTasks('org-1');
      expect(groups.length).toBe(0);
    });

    it('ignores completed/cancelled tasks', () => {
      const t1 = createAndApprove({ title: 'Same title' });
      ts.createTask(createDefaults({ title: 'Same title' }) as any);
      ts.updateTaskStatus(t1.id, 'review');
      ts.updateTaskStatus(t1.id, 'completed');

      const groups = ts.findDuplicateTasks('org-1');
      expect(groups.length).toBe(0);
    });
  });

  describe('cleanupDuplicateTasks', () => {
    it('cancels newer duplicates and keeps the oldest', () => {
      const t1 = ts.createTask(createDefaults({
        title: 'Build API',
        requirementId: 'req-1',
      }) as any);
      const t2 = ts.createTask(createDefaults({
        title: 'Build API',
        requirementId: 'req-1',
      }) as any);
      const t3 = ts.createTask(createDefaults({
        title: 'Build API',
        requirementId: 'req-1',
      }) as any);

      const result = ts.cleanupDuplicateTasks('org-1');
      expect(result.count).toBe(2);
      expect(result.cancelledIds).toContain(t2.id);
      expect(result.cancelledIds).toContain(t3.id);

      expect(ts.getTask(t1.id)!.status).toBe('in_progress');
      expect(ts.getTask(t2.id)!.status).toBe('cancelled');
      expect(ts.getTask(t3.id)!.status).toBe('cancelled');
    });

    it('returns empty when no duplicates', () => {
      ts.createTask(createDefaults({ title: 'Unique A' }) as any);
      ts.createTask(createDefaults({ title: 'Unique B' }) as any);

      const result = ts.cleanupDuplicateTasks('org-1');
      expect(result.count).toBe(0);
      expect(result.cancelledIds).toHaveLength(0);
    });
  });

  describe('getTaskBoardHealth', () => {
    it('returns board health summary with status counts', () => {
      ts.createTask(createDefaults({ title: 'Pending' }) as any);
      const dep = createAndApprove({ title: 'Dep' });
      const blocked = ts.createTask(createDefaults({
        title: 'Blocked',
        blockedBy: [dep.id],
      }) as any);

      const health = ts.getTaskBoardHealth('org-1') as any;
      expect(health.totalTasks).toBe(3);
      expect(health.statusCounts['in_progress']).toBe(2);
      expect(health.statusCounts['blocked']).toBe(1);
    });

    it('reports duplicate groups count', () => {
      ts.createTask(createDefaults({ title: 'Dup Task' }) as any);
      ts.createTask(createDefaults({ title: 'Dup Task' }) as any);

      const health = ts.getTaskBoardHealth('org-1') as any;
      expect(health.duplicateGroups).toBe(1);
    });
  });
});
