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

import {
  buildTeamChangeRequestPlan,
  buildTeamChangesTasksFingerprint,
  TEAM_CHANGES_MAX_REQUESTS,
  TEAM_CHANGES_UNKNOWN_SCAN_LIMIT,
} from '../teamChangesRequestPlan';

import type { TeamTaskWithKanban } from '@shared/types';

function task(overrides: Partial<TeamTaskWithKanban> & { id: string }): TeamTaskWithKanban {
  const { id, subject, status, ...rest } = overrides;
  return {
    id,
    subject: subject ?? `Task ${id}`,
    status: status ?? 'pending',
    ...rest,
  };
}

function changedTasks(count: number): TeamTaskWithKanban[] {
  return Array.from({ length: count }, (_, index) =>
    task({
      id: `changed-${index}`,
      status: 'completed',
      changePresence: 'has_changes',
      updatedAt: `2026-05-09T08:${String(index).padStart(2, '0')}:00.000Z`,
    })
  );
}

describe('buildTeamChangeRequestPlan', () => {
  it('scans unknown pending tasks only when they have work evidence', () => {
    const plan = buildTeamChangeRequestPlan(
      [
        task({ id: 'plain-pending', status: 'pending', changePresence: 'unknown' }),
        task({
          id: 'worked-pending',
          status: 'pending',
          changePresence: 'unknown',
          workIntervals: [{ startedAt: '2026-05-09T08:00:00.000Z' }],
        }),
      ],
      0,
      false
    );

    expect(plan.requests.map((request) => request.taskId)).toEqual(['worked-pending']);
    expect([...plan.eligibleTaskIds]).toEqual(['worked-pending']);
  });

  it('keeps known changed tasks even when they are currently pending', () => {
    const plan = buildTeamChangeRequestPlan(
      [task({ id: 'known-changed', status: 'pending', changePresence: 'has_changes' })],
      0,
      false
    );

    expect(plan.requests.map((request) => request.taskId)).toEqual(['known-changed']);
    expect(plan.eligibleTaskIds.has('known-changed')).toBe(true);
  });

  it('skips deleted and duplicate tasks before counting candidates', () => {
    const plan = buildTeamChangeRequestPlan(
      [
        task({ id: 'changed', status: 'completed', changePresence: 'has_changes' }),
        task({ id: 'changed', status: 'completed', changePresence: 'has_changes' }),
        task({ id: 'deleted', status: 'deleted', changePresence: 'has_changes' }),
      ],
      0,
      false
    );

    expect(plan.requests.map((request) => request.taskId)).toEqual(['changed']);
    expect(plan.eligibleCount).toBe(1);
    expect(plan.deferredCount).toBe(0);
  });

  it('caps selected requests and reports deferred candidates', () => {
    const plan = buildTeamChangeRequestPlan(changedTasks(TEAM_CHANGES_MAX_REQUESTS + 5), 0, false);

    expect(plan.requests).toHaveLength(TEAM_CHANGES_MAX_REQUESTS);
    expect(plan.eligibleCount).toBe(TEAM_CHANGES_MAX_REQUESTS + 5);
    expect(plan.deferredCount).toBe(5);
  });

  it('supports smaller first-pass caps without changing eligible counts', () => {
    const plan = buildTeamChangeRequestPlan(changedTasks(30), 0, false, {
      maxRequests: 12,
      unknownScanLimit: 6,
    });

    expect(plan.requests).toHaveLength(12);
    expect(plan.eligibleCount).toBe(30);
    expect(plan.deferredCount).toBe(18);
  });

  it('skips satisfied candidates without counting them as deferred', () => {
    const tasks = changedTasks(30);
    const satisfiedTaskIds = new Set(
      Array.from({ length: 12 }, (_, index) => `changed-${29 - index}`)
    );

    const plan = buildTeamChangeRequestPlan(tasks, 0, false, { satisfiedTaskIds });

    expect(plan.requests).toHaveLength(18);
    expect(plan.requests.some((request) => satisfiedTaskIds.has(request.taskId))).toBe(false);
    expect(plan.eligibleCount).toBe(30);
    expect(plan.deferredCount).toBe(0);
    expect([...satisfiedTaskIds].every((taskId) => plan.eligibleTaskIds.has(taskId))).toBe(true);
  });

  it('keeps forceFresh request options while skipping same-chain satisfied candidates', () => {
    const tasks = changedTasks(30);
    const satisfiedTaskIds = new Set(['changed-29', 'changed-28', 'changed-27']);

    const plan = buildTeamChangeRequestPlan(tasks, 0, true, {
      maxRequests: 9,
      satisfiedTaskIds,
    });

    expect(plan.requests).toHaveLength(9);
    expect(plan.requests.some((request) => satisfiedTaskIds.has(request.taskId))).toBe(false);
    expect(plan.requests.every((request) => request.options?.forceFresh === true)).toBe(true);
  });

  it('rotates unknown scans and preserves summary-only request options', () => {
    const tasks = Array.from({ length: TEAM_CHANGES_UNKNOWN_SCAN_LIMIT + 4 }, (_, index) =>
      task({
        id: `task-${index}`,
        status: 'completed',
        changePresence: 'unknown',
        updatedAt: `2026-05-09T08:${String(index).padStart(2, '0')}:00.000Z`,
      })
    );

    const firstPass = buildTeamChangeRequestPlan(tasks, 0, true);
    const secondPass = buildTeamChangeRequestPlan(tasks, firstPass.nextUnknownScanCursor, false);

    expect(firstPass.requests).toHaveLength(TEAM_CHANGES_UNKNOWN_SCAN_LIMIT);
    expect(firstPass.requests[0].options?.summaryOnly).toBe(true);
    expect(firstPass.requests[0].options?.forceFresh).toBe(true);
    expect(secondPass.requests[0].taskId).toBe('task-3');
  });

  it('changes fingerprint when review state changes without timestamp changes', () => {
    const baseTask = task({
      id: 'reviewing',
      status: 'completed',
      changePresence: 'unknown',
      updatedAt: '2026-05-09T08:00:00.000Z',
      reviewState: 'none',
    });

    expect(buildTeamChangesTasksFingerprint([baseTask])).not.toBe(
      buildTeamChangesTasksFingerprint([{ ...baseTask, reviewState: 'review' }])
    );
  });

  it('keeps fingerprint stable for task reorder and irrelevant history events', () => {
    const first = task({
      id: 'task-a',
      status: 'pending',
      historyEvents: [
        {
          id: 'event-created',
          type: 'task_created',
          timestamp: '2026-05-09T08:00:00.000Z',
          status: 'pending',
        },
        {
          id: 'event-owner',
          type: 'owner_changed',
          timestamp: '2026-05-09T08:01:00.000Z',
          from: 'alice',
          to: 'bob',
        },
      ],
    });
    const second = task({
      id: 'task-b',
      status: 'completed',
      historyEvents: [
        {
          id: 'event-status',
          type: 'status_changed',
          timestamp: '2026-05-09T08:02:00.000Z',
          from: 'in_progress',
          to: 'completed',
        },
      ],
    });

    expect(buildTeamChangesTasksFingerprint([first, second])).toBe(
      buildTeamChangesTasksFingerprint([
        second,
        {
          ...first,
          historyEvents: [
            ...(first.historyEvents ?? []),
            {
              id: 'event-owner-2',
              type: 'owner_changed',
              timestamp: '2026-05-09T08:03:00.000Z',
              from: 'bob',
              to: 'carol',
            },
          ],
        },
      ])
    );
  });
});
