import { describe, it, expect } from 'vitest';
import type { DatasetTask } from '@ump/shared';
import {
  allowedActions,
  clampPriority,
  PIPELINE_STATE_META,
  QUEUE_STATUS_META,
  matchesScope,
  matchesAssignee,
  searchTasks,
  groupTasks,
  paginate,
  tasksAssignedByStage,
  shortId,
  stageTone,
  type TaskActionInput,
} from './taskView';

function mkTask(over: Partial<DatasetTask> = {}): DatasetTask {
  return {
    id: 'task-0000-1111-2222-3333',
    name: 'scan.nii',
    fileId: 'file-1',
    fileLogicalPath: 'scan.nii',
    currentStageId: 'stage-label',
    currentStageName: 'Gán nhãn',
    pipelineState: 'inLabel',
    queueStatus: 'assigned',
    assigneeUserId: null,
    assigneeName: null,
    assignmentMode: 'manual',
    priority: 0,
    isReferenceStandard: false,
    ...over,
  };
}

const base: TaskActionInput = {
  pipelineState: 'inLabel',
  queueStatus: 'assigned',
  assigneeUserId: null,
};

describe('allowedActions', () => {
  it('an unassigned Label task can be finalized, skipped and claimed (not reviewed)', () => {
    const a = allowedActions(base);
    expect(a).toMatchObject({
      canFinalize: true,
      canReview: false,
      canSkip: true,
      canAssign: true,
      canUnassign: false,
      canSetReference: false,
      isTerminal: false,
    });
  });

  it('an assigned Review task can be reviewed and unassigned (not finalized)', () => {
    const a = allowedActions({ ...base, pipelineState: 'inReview', assigneeUserId: 'u1' });
    expect(a.canReview).toBe(true);
    expect(a.canFinalize).toBe(false);
    expect(a.canUnassign).toBe(true);
    expect(a.canAssign).toBe(false);
    expect(a.canSkip).toBe(true);
  });

  it('a Ground Truth task is terminal — only reference-standard toggling', () => {
    const a = allowedActions({ ...base, pipelineState: 'groundTruth' });
    expect(a).toMatchObject({
      canFinalize: false,
      canReview: false,
      canSkip: false,
      canAssign: false,
      canSetReference: true,
      isTerminal: true,
    });
  });

  it('an already-skipped task cannot be skipped again but can still be finalized', () => {
    const a = allowedActions({ ...base, queueStatus: 'skipped' });
    expect(a.canSkip).toBe(false);
    expect(a.canFinalize).toBe(true);
  });
});

describe('clampPriority', () => {
  it('clamps to [0, 1] and defaults NaN to 0', () => {
    expect(clampPriority(0.5)).toBe(0.5);
    expect(clampPriority(2)).toBe(1);
    expect(clampPriority(-1)).toBe(0);
    expect(clampPriority(Number.NaN)).toBe(0);
  });
});

describe('display metadata', () => {
  it('has a Vietnamese label + tone for every pipeline state and queue status', () => {
    for (const meta of Object.values(PIPELINE_STATE_META)) {
      expect(meta.label.length).toBeGreaterThan(0);
      expect(meta.tone).toBeTruthy();
    }
    for (const meta of Object.values(QUEUE_STATUS_META)) {
      expect(meta.label.length).toBeGreaterThan(0);
      expect(meta.tone).toBeTruthy();
    }
  });
});

describe('matchesScope', () => {
  const mine = mkTask({ assigneeUserId: 'u1' });
  const unassigned = mkTask({ assigneeUserId: null });
  const review = mkTask({ pipelineState: 'inReview' });

  it('all → everything; mine → only the caller; unassigned → only empty assignee', () => {
    expect(matchesScope(mine, 'all', 'u1')).toBe(true);
    expect(matchesScope(mine, 'mine', 'u1')).toBe(true);
    expect(matchesScope(unassigned, 'mine', 'u1')).toBe(false);
    expect(matchesScope(unassigned, 'unassigned', 'u1')).toBe(true);
    expect(matchesScope(mine, 'unassigned', 'u1')).toBe(false);
  });

  it('a pipeline-state scope matches by state', () => {
    expect(matchesScope(review, 'inReview', null)).toBe(true);
    expect(matchesScope(review, 'inLabel', null)).toBe(false);
  });

  it('mine is false when there is no current user', () => {
    expect(matchesScope(mine, 'mine', null)).toBe(false);
  });
});

describe('matchesAssignee', () => {
  const t = mkTask({ assigneeUserId: 'u2' });
  it('all passes, unassigned needs empty, an id must match', () => {
    expect(matchesAssignee(t, 'all')).toBe(true);
    expect(matchesAssignee(t, 'unassigned')).toBe(false);
    expect(matchesAssignee(t, 'u2')).toBe(true);
    expect(matchesAssignee(t, 'u9')).toBe(false);
    expect(matchesAssignee(mkTask({ assigneeUserId: null }), 'unassigned')).toBe(true);
  });
});

describe('searchTasks', () => {
  const list = [
    mkTask({ id: 'aaaa', name: 'Brain.nii', fileLogicalPath: 'brain.nii' }),
    mkTask({ id: 'bbbb', name: 'Liver.dcm', fileLogicalPath: 'liver.dcm' }),
  ];
  it('matches name, logical path or id, case-insensitively; blank returns all', () => {
    expect(searchTasks(list, '').length).toBe(2);
    expect(searchTasks(list, 'brain').map((t) => t.id)).toEqual(['aaaa']);
    expect(searchTasks(list, 'BBBB').map((t) => t.id)).toEqual(['bbbb']);
    expect(searchTasks(list, 'liver.dcm').map((t) => t.id)).toEqual(['bbbb']);
    expect(searchTasks(list, 'nomatch')).toEqual([]);
  });
});

describe('groupTasks', () => {
  const list = [
    mkTask({ id: '1', currentStageId: 's1', currentStageName: 'Gán nhãn', assigneeUserId: 'u1', assigneeName: 'A' }),
    mkTask({ id: '2', currentStageId: 's1', currentStageName: 'Gán nhãn', assigneeUserId: null, assigneeName: null }),
    mkTask({ id: '3', currentStageId: 's2', currentStageName: 'Rà soát', pipelineState: 'inReview', assigneeUserId: 'u1', assigneeName: 'A' }),
  ];
  it('none yields a single flat group', () => {
    const g = groupTasks(list, 'none');
    expect(g).toHaveLength(1);
    expect(g[0].tasks).toHaveLength(3);
  });
  it('by stage / assignee / state partitions the list and preserves counts', () => {
    expect(groupTasks(list, 'stage').map((g) => g.tasks.length).sort()).toEqual([1, 2]);
    const byAssignee = groupTasks(list, 'assignee');
    expect(byAssignee.find((g) => g.label === 'Chưa giao')?.tasks).toHaveLength(1);
    const byState = groupTasks(list, 'state');
    expect(byState.map((g) => g.key).sort()).toEqual(['inLabel', 'inReview']);
  });
});

describe('paginate', () => {
  const items = Array.from({ length: 25 }, (_, i) => i);
  it('slices a page and reports 1-based from/to + clamps overflow', () => {
    const p1 = paginate(items, 1, 10);
    expect(p1.items).toEqual([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
    expect([p1.from, p1.to, p1.pageCount, p1.total]).toEqual([1, 10, 3, 25]);
    const last = paginate(items, 3, 10);
    expect(last.items).toEqual([20, 21, 22, 23, 24]);
    expect([last.from, last.to]).toEqual([21, 25]);
    expect(paginate(items, 99, 10).page).toBe(3); // clamped
  });
  it('empty input reports from/to 0 and one page', () => {
    expect(paginate([], 1, 20)).toMatchObject({ from: 0, to: 0, pageCount: 1, total: 0 });
  });
});

describe('tasksAssignedByStage', () => {
  const list = [
    mkTask({ assigneeUserId: 'u1', currentStageId: 's1', currentStageName: 'Gán nhãn' }),
    mkTask({ assigneeUserId: 'u1', currentStageId: 's1', currentStageName: 'Gán nhãn' }),
    mkTask({ assigneeUserId: 'u1', currentStageId: 's2', currentStageName: 'Rà soát' }),
    mkTask({ assigneeUserId: 'u2', currentStageId: 's1', currentStageName: 'Gán nhãn' }),
  ];
  it('counts only the user’s tasks, grouped by stage', () => {
    const r = tasksAssignedByStage(list, 'u1');
    expect(r.total).toBe(3);
    expect(r.rows.find((x) => x.stageId === 's1')?.count).toBe(2);
    expect(r.rows.find((x) => x.stageId === 's2')?.count).toBe(1);
  });
  it('returns empty for a null user', () => {
    expect(tasksAssignedByStage(list, null)).toEqual({ rows: [], total: 0 });
  });
});

describe('shortId + stageTone', () => {
  it('shortId truncates long ids with an ellipsis and leaves short ones', () => {
    expect(shortId('abc')).toBe('abc');
    expect(shortId('0123456789-0123456789-0123456789')).toBe('0123456789-0123456…');
  });
  it('stageTone maps review→review and everything else→label', () => {
    expect(stageTone('review')).toBe('review');
    expect(stageTone('label')).toBe('label');
    expect(stageTone(undefined)).toBe('label');
  });
});
