import { describe, it, expect } from 'vitest';
import { filterTasksForSpace, countRunningTasksForSpace } from './spaceTasks';
import type { LocalTask } from '../api';

function task(
  id: number,
  spaceId: string | null,
  updatedAt: string,
  status?: string,
  ownerId: string | null = 'u1',
): LocalTask {
  return {
    id,
    title: `task ${id}`,
    body: '',
    pieceName: 'auto',
    profile: 'auto',
    outputFormat: 'markdown',
    askPolicy: 'low',
    priority: 'medium',
    state: 'queued',
    workspacePath: null,
    spaceId,
    ownerId,
    createdAt: updatedAt,
    updatedAt,
    ...(status ? { latestJob: { id: `j${id}`, status } } : {}),
  } as LocalTask;
}

describe('filterTasksForSpace', () => {
  it('keeps only tasks matching the spaceId', () => {
    const tasks = [task(1, 'sp-a', '2026-01-01'), task(2, 'sp-b', '2026-01-02'), task(3, 'sp-a', '2026-01-03')];
    const result = filterTasksForSpace(tasks, 'sp-a');
    expect(result.map(t => t.id)).toEqual([3, 1]);
  });

  it('excludes null-space tasks under a case space (non-personal)', () => {
    const tasks = [task(1, null, '2026-01-01'), task(2, 'sp-a', '2026-01-02')];
    expect(filterTasksForSpace(tasks, 'sp-a').map(t => t.id)).toEqual([2]);
    // Explicitly non-personal still excludes null.
    expect(filterTasksForSpace(tasks, 'sp-a', false).map(t => t.id)).toEqual([2]);
  });

  it('includes null-space tasks under the personal space (spec §7)', () => {
    const tasks = [task(1, null, '2026-01-03'), task(2, 'personal', '2026-01-02'), task(3, 'sp-a', '2026-01-01')];
    const result = filterTasksForSpace(tasks, 'personal', true);
    expect(result.map(t => t.id)).toEqual([1, 2]);
  });

  it('includes other users\' personal-space tasks when caseSpaceIds is supplied (admin monitoring)', () => {
    // 個人バケツ = null か case でない space_id。caseSpaceIds={sp-a} を渡すと
    // 他ユーザーの個人スペース(personal-u2) も含まれ、case スペース(sp-a) は除外される。
    const tasks = [
      task(1, null, '2026-01-05', undefined, 'u1'),          // 自分の legacy null
      task(2, 'personal-u1', '2026-01-04', undefined, 'u1'), // 自分の個人スペース
      task(3, 'personal-u2', '2026-01-03', undefined, 'u2'), // 他ユーザーの個人スペース
      task(4, 'sp-a', '2026-01-02', undefined, 'u2'),        // case スペース → 除外
    ];
    const caseSpaceIds = new Set(['sp-a']);
    const result = filterTasksForSpace(tasks, 'personal-u1', true, caseSpaceIds);
    expect(result.map(t => t.id)).toEqual([1, 2, 3]);
  });

  it('without caseSpaceIds, personal space keeps only this-space-or-null (back-compat)', () => {
    const tasks = [
      task(1, null, '2026-01-03', undefined, 'u1'),
      task(2, 'personal-u1', '2026-01-02', undefined, 'u1'),
      task(3, 'personal-u2', '2026-01-01', undefined, 'u2'), // 他ユーザー個人 → 集合無しでは除外
    ];
    expect(filterTasksForSpace(tasks, 'personal-u1', true).map(t => t.id)).toEqual([1, 2]);
  });

  it('returns empty when no task matches', () => {
    expect(filterTasksForSpace([task(1, 'sp-x', '2026-01-01')], 'sp-y')).toEqual([]);
  });

  it('sorts matches by updatedAt descending', () => {
    const tasks = [task(1, 'sp', '2026-01-01'), task(2, 'sp', '2026-03-01'), task(3, 'sp', '2026-02-01')];
    expect(filterTasksForSpace(tasks, 'sp').map(t => t.id)).toEqual([2, 3, 1]);
  });
});

describe('countRunningTasksForSpace', () => {
  const caseSpace = { id: 'sp-a', kind: 'case' as const };
  const personalSpace = { id: 'personal', kind: 'personal' as const };

  it('counts only running tasks belonging to the case space', () => {
    const tasks = [
      task(1, 'sp-a', '2026-01-01', 'running'),
      task(2, 'sp-a', '2026-01-02', 'succeeded'),
      task(3, 'sp-a', '2026-01-03', 'running'),
      task(4, 'sp-b', '2026-01-04', 'running'), // other space
    ];
    expect(countRunningTasksForSpace(tasks, caseSpace)).toBe(2);
  });

  it('does NOT count null-space running tasks under a case space', () => {
    const tasks = [task(1, null, '2026-01-01', 'running'), task(2, 'sp-a', '2026-01-02', 'running')];
    expect(countRunningTasksForSpace(tasks, caseSpace)).toBe(1);
  });

  it('counts null-space running tasks under the personal space (spec §7)', () => {
    const tasks = [
      task(1, null, '2026-01-01', 'running'),
      task(2, null, '2026-01-02', 'queued'),   // not running
      task(3, 'personal', '2026-01-03', 'running'), // explicit personal id also counts
      task(4, 'sp-a', '2026-01-04', 'running'), // other space
    ];
    expect(countRunningTasksForSpace(tasks, personalSpace)).toBe(2);
  });

  it('scopes the personal badge to the viewer for non-admins', () => {
    // 非 admin: 共有されない限り他人を数えない。本人の null + 本人の個人スペース id のみ。
    const tasks = [
      task(1, null, '2026-01-01', 'running', 'u1'),          // 自分
      task(2, null, '2026-01-02', 'running', 'u2'),          // 他ユーザー → 除外
      task(3, 'personal', '2026-01-03', 'running', 'u1'),    // 自分の個人スペース id
    ];
    expect(countRunningTasksForSpace(tasks, personalSpace, { viewerId: 'u1' })).toBe(2);
    // viewerId 未指定なら従来どおり全件 (no-auth 単独利用)。
    expect(countRunningTasksForSpace(tasks, personalSpace)).toBe(3);
  });

  it('counts the whole personal bucket for admins (monitoring), incl. other users\' concrete personal ids', () => {
    // admin の個人スペースバッジは「他のメンバー」タブと同じ母集団を数える。
    // 個人バケツ = null または case でない space_id(= 誰かの個人スペース)。
    const tasks = [
      task(1, null, '2026-01-01', 'running', 'u1'),            // 自分 legacy null
      task(2, 'personal', '2026-01-02', 'running', 'u1'),      // 自分の個人スペース id
      task(3, 'personal-u2', '2026-01-03', 'running', 'u2'),   // 他ユーザーの個人スペース(具体 id)
      task(4, 'sp-a', '2026-01-04', 'running', 'u2'),          // case スペース → 除外
      task(5, 'personal-u3', '2026-01-05', 'queued', 'u3'),    // running でない → 除外
    ];
    const caseSpaceIds = new Set(['sp-a']);
    expect(
      countRunningTasksForSpace(tasks, personalSpace, { viewerId: 'u1', isAdmin: true, caseSpaceIds }),
    ).toBe(3);
    // 同じ集合でも非 admin なら本人(u1)のみ。
    expect(
      countRunningTasksForSpace(tasks, personalSpace, { viewerId: 'u1', isAdmin: false, caseSpaceIds }),
    ).toBe(2);
  });

  it('ignores tasks with no latestJob', () => {
    const tasks = [task(1, 'sp-a', '2026-01-01'), task(2, 'sp-a', '2026-01-02', 'running')];
    expect(countRunningTasksForSpace(tasks, caseSpace)).toBe(1);
  });

  it('returns 0 when nothing is running', () => {
    const tasks = [task(1, 'sp-a', '2026-01-01', 'succeeded'), task(2, 'sp-a', '2026-01-02', 'failed')];
    expect(countRunningTasksForSpace(tasks, caseSpace)).toBe(0);
  });
});
