import test from 'node:test';
import assert from 'node:assert/strict';

import { buildVersionDataScope, buildVersionDataScopeMap, selectVersionDataScope } from './version-data-scope';
import type { Bug } from './bug';
import type { DevTask } from './dev-task';
import type { OvertimeRecord } from './overtime';
import type { Requirement } from './requirement';
import type { TestCase } from './test-case';
import type { VersionPlan } from './version-plan';

function requirement(id: string, versionId: string | undefined): Requirement {
  return {
    id,
    code: id.toUpperCase(),
    title: `Requirement ${id}`,
    description: '',
    productId: 'product-1',
    projectId: 'project-1',
    versionId,
    sourceType: 'internal',
    sourceTarget: 'product',
    platforms: ['web'],
    typeId: 'type-1',
    status: 'planned',
    priority: 'P1',
    effort: 'M',
    creator: 'PM',
    createdAt: '2026-07-02T09:00:00.000Z',
  };
}

function plan(id: string, versionId: string, type: VersionPlan['type']): VersionPlan {
  return {
    id,
    versionId,
    type,
    title: `Plan ${id}`,
    owner: 'PM',
    startTime: '2026-07-02T09:00:00.000Z',
    endTime: '2026-07-02T18:00:00.000Z',
    status: 'pending',
    createdAt: '2026-07-02T09:00:00.000Z',
    addedBy: 'PM',
  };
}

function devTask(id: string, patch: Partial<DevTask>): DevTask {
  return {
    id,
    taskNo: id.toUpperCase(),
    versionId: undefined,
    requirementId: '',
    title: `Task ${id}`,
    categoryId: 'frontend',
    assigneeId: 'Alice',
    priority: 'P1',
    expectedStartAt: '',
    expectedEndAt: '',
    status: 'todo',
    isBlocked: false,
    createdBy: 'PM',
    createdAt: '2026-07-02T09:00:00.000Z',
    updatedAt: '2026-07-02T09:00:00.000Z',
    ...patch,
  };
}

function testCase(id: string, versionId: string): TestCase {
  return {
    id,
    caseNo: id.toUpperCase(),
    versionId,
    title: `Case ${id}`,
    categoryId: 'testing',
    priority: 'P1',
    status: 'pending',
    createdBy: 'QA',
    createdAt: '2026-07-02T09:00:00.000Z',
    updatedAt: '2026-07-02T09:00:00.000Z',
  };
}

function bug(id: string, versionId: string): Bug {
  return {
    id,
    bugNo: id.toUpperCase(),
    versionId,
    testCaseId: 'tc-1',
    title: `Bug ${id}`,
    description: '',
    severity: 'major',
    priority: 'P1',
    reportedBy: 'QA',
    assigneeId: 'Bob',
    status: 'open',
    createdAt: '2026-07-02T09:00:00.000Z',
    updatedAt: '2026-07-02T09:00:00.000Z',
  };
}

function overtime(id: string, versionId: string): OvertimeRecord {
  return {
    id,
    projectId: 'project-1',
    versionId,
    person: 'Alice',
    startTime: '2026-07-02T19:00:00.000Z',
    endTime: '2026-07-02T21:00:00.000Z',
    duration: 2,
    reasonId: 'reason-4',
    createdAt: '2026-07-02T21:00:00.000Z',
  };
}

test('buildVersionDataScope scopes version detail data once', () => {
  const scope = buildVersionDataScope({
    versionId: 'version-1',
    requirements: [requirement('req-1', 'version-1'), requirement('req-2', 'version-2')],
    plans: [plan('plan-1', 'version-1', 'research'), plan('plan-2', 'version-1', 'product'), plan('plan-3', 'version-2', 'ui')],
    devTasks: [
      devTask('dev-direct', { versionId: 'version-1', requirementId: '' }),
      devTask('dev-legacy', { requirementId: 'req-1' }),
      devTask('dev-other', { versionId: 'version-2', requirementId: 'req-2' }),
    ],
    testCases: [testCase('tc-1', 'version-1'), testCase('tc-2', 'version-2')],
    bugs: [bug('bug-1', 'version-1'), bug('bug-2', 'version-2')],
    overtimeRecords: [overtime('ot-1', 'version-1'), overtime('ot-2', 'version-2')],
  });

  assert.deepEqual(scope.requirementIds, ['req-1']);
  assert.deepEqual(scope.plans.map((item) => item.id), ['plan-1', 'plan-2']);
  assert.deepEqual(scope.plansByType.research.map((item) => item.id), ['plan-1']);
  assert.deepEqual(scope.plansByType.product.map((item) => item.id), ['plan-2']);
  assert.deepEqual(scope.devTasks.map((item) => item.id), ['dev-direct', 'dev-legacy']);
  assert.deepEqual(scope.testCases.map((item) => item.id), ['tc-1']);
  assert.deepEqual(scope.bugs.map((item) => item.id), ['bug-1']);
  assert.deepEqual(scope.overtimeRecords.map((item) => item.id), ['ot-1']);
});

test('buildVersionDataScopeMap indexes many versions in one pass', () => {
  const scopeMap = buildVersionDataScopeMap({
    versionIds: ['version-1', 'version-2', 'version-empty'],
    requirements: [
      requirement('req-1', 'version-1'),
      requirement('req-2', 'version-2'),
      requirement('req-outside', 'version-3'),
    ],
    plans: [
      plan('plan-1', 'version-1', 'research'),
      plan('plan-2', 'version-2', 'ui'),
      plan('plan-outside', 'version-3', 'product'),
    ],
    devTasks: [
      devTask('dev-direct', { versionId: 'version-2', requirementId: 'req-1' }),
      devTask('dev-legacy', { requirementId: 'req-1' }),
      devTask('dev-outside', { requirementId: 'req-outside' }),
    ],
    testCases: [testCase('tc-1', 'version-1'), testCase('tc-2', 'version-2'), testCase('tc-outside', 'version-3')],
    bugs: [bug('bug-1', 'version-1'), bug('bug-2', 'version-2'), bug('bug-outside', 'version-3')],
    overtimeRecords: [overtime('ot-1', 'version-1'), overtime('ot-2', 'version-2'), overtime('ot-outside', 'version-3')],
  });

  assert.deepEqual(scopeMap['version-1'].requirements.map((item) => item.id), ['req-1']);
  assert.deepEqual(scopeMap['version-1'].devTasks.map((item) => item.id), ['dev-legacy']);
  assert.deepEqual(scopeMap['version-2'].requirements.map((item) => item.id), ['req-2']);
  assert.deepEqual(scopeMap['version-2'].devTasks.map((item) => item.id), ['dev-direct']);
  assert.deepEqual(scopeMap['version-2'].plansByType.ui.map((item) => item.id), ['plan-2']);
  assert.deepEqual(scopeMap['version-empty'].requirements, []);
  assert.deepEqual(Object.keys(scopeMap).sort(), ['version-1', 'version-2', 'version-empty']);
});

test('selectVersionDataScope prefers non-empty V2.2 scope but keeps AppData overtime records', () => {
  const appDataScope = buildVersionDataScope({
    versionId: 'version-1',
    requirements: [requirement('req-app', 'version-1')],
    plans: [plan('plan-app', 'version-1', 'research')],
    devTasks: [devTask('dev-app', { versionId: 'version-1' })],
    testCases: [testCase('tc-app', 'version-1')],
    bugs: [bug('bug-app', 'version-1')],
    overtimeRecords: [overtime('ot-app', 'version-1')],
  });
  const v22Scope = buildVersionDataScope({
    versionId: 'version-1',
    requirements: [requirement('req-v22', 'version-1')],
    plans: [plan('plan-v22', 'version-1', 'product')],
    devTasks: [devTask('dev-v22', { versionId: 'version-1' })],
    testCases: [],
    bugs: [],
    overtimeRecords: [],
  });

  const selected = selectVersionDataScope({ appDataScope, v22Scope });

  assert.equal(selected?.requirements[0].id, 'req-v22');
  assert.equal(selected?.plans[0].id, 'plan-v22');
  assert.deepEqual(selected?.overtimeRecords.map((item) => item.id), ['ot-app']);
});

test('selectVersionDataScope falls back to AppData when V2.2 scope is empty', () => {
  const appDataScope = buildVersionDataScope({
    versionId: 'version-1',
    requirements: [requirement('req-app', 'version-1')],
    plans: [],
    devTasks: [],
    testCases: [],
    bugs: [],
  });
  const emptyV22Scope = buildVersionDataScope({
    versionId: 'version-1',
    requirements: [],
    plans: [],
    devTasks: [],
    testCases: [],
    bugs: [],
  });

  const selected = selectVersionDataScope({ appDataScope, v22Scope: emptyV22Scope });

  assert.equal(selected?.requirements[0].id, 'req-app');
});
