#!/usr/bin/env node
import { createRequire } from 'node:module';
import { mkdir, writeFile } from 'node:fs/promises';
import { dirname, resolve } from 'node:path';

const FIXTURE_VERSION = 'v2.6-large-data-fixture-1';
const ID_PREFIX = 'perf-';
const BASE_DATE = new Date('2026-07-01T09:00:00.000Z');
const OUTPUT_PATH = '.tmp/perf-fixture.json';

const SIZE_PRESETS = {
  small: {
    products: 1,
    projectsPerProduct: 2,
    versionsPerProject: 2,
    requirementsPerVersion: 20,
    looseRequirementsPerProduct: 20,
    plansPerVersion: 3,
    devTasksPerRequirement: 2,
    testCasesPerRequirement: 2,
    bugsPerVersion: 12,
    activitiesPerVersion: 16,
    users: 8,
  },
  medium: {
    products: 2,
    projectsPerProduct: 8,
    versionsPerProject: 4,
    requirementsPerVersion: 80,
    looseRequirementsPerProduct: 300,
    plansPerVersion: 3,
    devTasksPerRequirement: 3,
    testCasesPerRequirement: 3,
    bugsPerVersion: 80,
    activitiesPerVersion: 120,
    users: 40,
  },
  large: {
    products: 4,
    projectsPerProduct: 20,
    versionsPerProject: 6,
    requirementsPerVersion: 150,
    looseRequirementsPerProduct: 2000,
    plansPerVersion: 3,
    devTasksPerRequirement: 4,
    testCasesPerRequirement: 4,
    bugsPerVersion: 180,
    activitiesPerVersion: 300,
    users: 120,
  },
};

const DEV_STATUSES = ['todo', 'in_progress', 'testing', 'submitted'];
const TEST_STATUSES = ['pending', 'running', 'passed', 'failed', 'blocked'];
const BUG_STATUSES = ['open', 'fixing', 'fixed', 'verifying', 'closed'];
const BUG_SEVERITIES = ['normal', 'major', 'critical'];
const PLAN_TYPES = ['research', 'product', 'ui'];
const REQUIREMENT_STATUSES = ['adopted', 'planned', 'developing', 'testing', 'released'];

function parseArgs(argv) {
  const args = {
    size: 'small',
    dryRun: false,
    json: false,
    output: OUTPUT_PATH,
  };

  for (let index = 0; index < argv.length; index += 1) {
    const arg = argv[index];
    if (arg === '--dry-run') {
      args.dryRun = true;
      continue;
    }
    if (arg === '--json') {
      args.json = true;
      continue;
    }
    if (arg === '--size') {
      args.size = argv[++index] ?? args.size;
      continue;
    }
    if (arg.startsWith('--size=')) {
      args.size = arg.slice('--size='.length);
      continue;
    }
    if (arg === '--output') {
      args.output = argv[++index] ?? args.output;
      continue;
    }
    if (arg.startsWith('--output=')) {
      args.output = arg.slice('--output='.length);
      continue;
    }
    if (arg === '--help' || arg === '-h') {
      printHelp();
      process.exit(0);
    }
    throw new Error(`Unknown argument: ${arg}`);
  }

  if (!SIZE_PRESETS[args.size]) {
    throw new Error(`Unsupported size "${args.size}". Use one of: ${Object.keys(SIZE_PRESETS).join(', ')}`);
  }
  return args;
}

function printHelp() {
  console.log(`Usage: node scripts/seed-large-dataset.mjs [--size small|medium|large] [--dry-run] [--json]

Creates deterministic V2.6 performance fixture data using perf-* ids.

Options:
  --size       Fixture size. Defaults to small.
  --dry-run    Build and summarize rows without writing PostgreSQL.
  --json       Print the summary as JSON.
  --output     Metadata path written after a real seed. Defaults to ${OUTPUT_PATH}.
`);
}

function pad(value, width = 3) {
  return String(value).padStart(width, '0');
}

function hoursAfter(hours) {
  return new Date(BASE_DATE.getTime() + hours * 60 * 60 * 1000);
}

function daysAfter(days) {
  return hoursAfter(days * 24);
}

function priority(index) {
  return index % 5;
}

function buildFixture(sizeName) {
  const preset = SIZE_PRESETS[sizeName];
  const users = buildUsers(preset.users);
  const products = [];
  const projects = [];
  const versions = [];
  const requirements = [];
  const versionPlans = [];
  const devTasks = [];
  const testCases = [];
  const bugs = [];
  const workActivities = [];
  const xiaobaoRiskSummaries = [];

  for (let productNo = 1; productNo <= preset.products; productNo += 1) {
    const productId = `${ID_PREFIX}product-${pad(productNo)}`;
    products.push({
      id: productId,
      name: `Performance Product ${pad(productNo)}`,
      description: `Deterministic ${sizeName} fixture product ${productNo}`,
      createdAt: daysAfter(productNo),
      updatedAt: daysAfter(productNo),
    });

    for (let looseNo = 1; looseNo <= preset.looseRequirementsPerProduct; looseNo += 1) {
      requirements.push(buildRequirement({
        productId,
        projectId: null,
        versionId: null,
        productNo,
        projectNo: 0,
        versionNo: 0,
        requirementNo: looseNo,
        loose: true,
        creatorId: users[looseNo % users.length].id,
      }));
    }

    for (let projectNo = 1; projectNo <= preset.projectsPerProduct; projectNo += 1) {
      const projectId = `${ID_PREFIX}project-${pad(productNo)}-${pad(projectNo)}`;
      projects.push({
        id: projectId,
        productId,
        name: `Performance Project ${pad(productNo)}-${pad(projectNo)}`,
        description: `Hot-path project ${projectNo}`,
        createdAt: daysAfter(projectNo),
        updatedAt: daysAfter(projectNo),
      });

      for (let versionNo = 1; versionNo <= preset.versionsPerProject; versionNo += 1) {
        const versionId = `${ID_PREFIX}version-${pad(productNo)}-${pad(projectNo)}-${pad(versionNo)}`;
        const version = buildVersion({
          productId,
          projectId,
          versionId,
          productNo,
          projectNo,
          versionNo,
          users,
        });
        versions.push(version);

        for (let planNo = 1; planNo <= preset.plansPerVersion; planNo += 1) {
          versionPlans.push(buildVersionPlan({
            productId,
            projectId,
            versionId,
            planNo,
            ownerId: users[(planNo + versionNo) % users.length].id,
          }));
        }

        const versionRequirementIds = [];
        for (let requirementNo = 1; requirementNo <= preset.requirementsPerVersion; requirementNo += 1) {
          const req = buildRequirement({
            productId,
            projectId,
            versionId,
            productNo,
            projectNo,
            versionNo,
            requirementNo,
            loose: false,
            creatorId: users[requirementNo % users.length].id,
          });
          requirements.push(req);
          versionRequirementIds.push(req.id);

          for (let taskNo = 1; taskNo <= preset.devTasksPerRequirement; taskNo += 1) {
            devTasks.push(buildDevTask({
              productId,
              projectId,
              versionId,
              requirementId: req.id,
              requirementProductId: productId,
              requirementNo,
              taskNo,
              assigneeId: users[(requirementNo + taskNo) % users.length].id,
            }));
          }

          for (let caseNo = 1; caseNo <= preset.testCasesPerRequirement; caseNo += 1) {
            testCases.push(buildTestCase({
              productId,
              projectId,
              versionId,
              requirementId: req.id,
              requirementProductId: productId,
              requirementNo,
              caseNo,
              assigneeId: users[(requirementNo + caseNo + 2) % users.length].id,
            }));
          }
        }

        for (let bugNo = 1; bugNo <= preset.bugsPerVersion; bugNo += 1) {
          bugs.push(buildBug({
            productId,
            projectId,
            versionId,
            bugNo,
            assigneeId: users[(bugNo + versionNo) % users.length].id,
            testCaseId: testCases.find((row) => row.versionId === versionId)?.id,
          }));
        }

        for (let activityNo = 1; activityNo <= preset.activitiesPerVersion; activityNo += 1) {
          const source = devTasks.find((row) => row.versionId === versionId && row.assigneeId === users[activityNo % users.length].id)
            ?? devTasks.find((row) => row.versionId === versionId);
          workActivities.push(buildWorkActivity({
            productId,
            projectId,
            versionId,
            activityNo,
            actor: users[activityNo % users.length],
            source,
          }));
        }

        xiaobaoRiskSummaries.push(buildXiaobaoSummary({
          version,
          bugCount: preset.bugsPerVersion,
          unfinishedCount: versionRequirementIds.length,
        }));
      }
    }
  }

  return {
    size: sizeName,
    anchors: {
      productId: `${ID_PREFIX}product-001`,
      projectId: `${ID_PREFIX}project-001-001`,
      versionId: `${ID_PREFIX}version-001-001-001`,
      userId: `${ID_PREFIX}user-dev-01`,
    },
    rows: {
      users,
      products,
      projects,
      versions,
      requirements,
      versionPlans,
      devTasks,
      testCases,
      bugs,
      workActivities,
      xiaobaoRiskSummaries,
    },
  };
}

function buildUsers(count) {
  return Array.from({ length: count }, (_, index) => {
    const no = index + 1;
    return {
      id: `${ID_PREFIX}user-dev-${pad(no, 2)}`,
      email: `perf-user-${pad(no, 2)}@example.test`,
      name: `Perf User ${pad(no, 2)}`,
      username: `perf_user_${pad(no, 2)}`,
      departmentId: no % 3 === 0 ? 'dept-2-3' : no % 2 === 0 ? 'dept-2-2' : 'dept-2-1',
      roleId: no % 3 === 0 ? 'role-test' : 'role-dev',
      phone: '',
      password: 'Perf@2026',
      isSystem: false,
      createdAt: BASE_DATE,
      updatedAt: BASE_DATE,
    };
  });
}

function buildVersion(input) {
  const releaseDate = daysAfter(20 + input.versionNo);
  return {
    id: input.versionId,
    productId: input.productId,
    projectId: input.projectId,
    name: `V${input.productNo}.${input.projectNo}.${input.versionNo}`,
    description: 'Performance hot-path version',
    status: input.versionNo % 4 === 0 ? 'paused' : 'developing',
    currentStage: input.versionNo % 2 === 0 ? 'testing' : 'development',
    startDate: daysAfter(input.versionNo),
    expectedReleaseDate: releaseDate,
    releaseDate,
    members: input.users.slice(0, Math.min(6, input.users.length)).map((user, index) => ({
      id: user.id,
      name: user.name,
      role: index % 3 === 0 ? 'testing' : index % 2 === 0 ? 'backend' : 'frontend',
    })),
    progress: [],
    priority: priority(input.versionNo),
    links: {},
    createdAt: daysAfter(input.versionNo),
    updatedAt: daysAfter(input.versionNo + 1),
  };
}

function buildRequirement(input) {
  const scoped = input.loose
    ? `LOOSE-${pad(input.productNo)}-${pad(input.requirementNo, 5)}`
    : `${pad(input.productNo)}-${pad(input.projectNo)}-${pad(input.versionNo)}-${pad(input.requirementNo, 5)}`;
  return {
    id: `${ID_PREFIX}req-${scoped}`,
    productId: input.productId,
    projectId: input.projectId,
    versionId: input.versionId,
    code: `REQ-${scoped}`,
    title: `Requirement ${scoped}`,
    description: `Deterministic requirement ${scoped}`,
    status: input.loose ? 'adopted' : REQUIREMENT_STATUSES[input.requirementNo % REQUIREMENT_STATUSES.length],
    priority: priority(input.requirementNo),
    type: input.requirementNo % 2 === 0 ? 'feature' : 'optimization',
    sourceType: input.requirementNo % 3 === 0 ? 'customer' : 'internal',
    sourceTarget: input.requirementNo % 3 === 0 ? 'perf-customer' : 'perf-team',
    platform: input.requirementNo % 2 === 0 ? 'web,ios' : 'web',
    creatorId: input.creatorId,
    createdAt: hoursAfter(input.requirementNo),
    updatedAt: hoursAfter(input.requirementNo + 1),
  };
}

function buildVersionPlan(input) {
  const type = PLAN_TYPES[(input.planNo - 1) % PLAN_TYPES.length];
  return {
    id: `${ID_PREFIX}plan-${input.versionId}-${type}`,
    versionId: input.versionId,
    productId: input.productId,
    projectId: input.projectId,
    type,
    title: `${type} plan for ${input.versionId}`,
    status: input.planNo === 1 ? 'completed' : 'in_progress',
    ownerId: input.ownerId,
    expectedStartAt: daysAfter(input.planNo),
    expectedEndAt: daysAfter(input.planNo + 3),
    actualStartAt: daysAfter(input.planNo),
    completedAt: input.planNo === 1 ? daysAfter(input.planNo + 2) : null,
    resultUrl: input.planNo === 1 ? 'https://example.test/prototype' : null,
    requirementCoverage: [],
    logs: [],
    createdAt: daysAfter(input.planNo),
    updatedAt: daysAfter(input.planNo + 1),
  };
}

function buildDevTask(input) {
  const status = DEV_STATUSES[(input.requirementNo + input.taskNo) % DEV_STATUSES.length];
  const blocked = status !== 'submitted' && input.taskNo % 11 === 0;
  return {
    id: `${ID_PREFIX}dev-${input.versionId}-${pad(input.requirementNo, 5)}-${pad(input.taskNo, 2)}`,
    versionId: input.versionId,
    productId: input.productId,
    projectId: input.projectId,
    requirementId: input.requirementId,
    requirementProductId: input.requirementProductId,
    categoryId: null,
    code: `DEV-${pad(input.requirementNo, 5)}-${pad(input.taskNo, 2)}`,
    title: `Dev task ${pad(input.requirementNo, 5)}-${pad(input.taskNo, 2)}`,
    description: 'Performance fixture development task',
    status,
    priority: priority(input.requirementNo + input.taskNo),
    assigneeId: input.assigneeId,
    creatorId: input.assigneeId,
    isBlocked: blocked,
    blockReason: blocked ? 'Fixture blocker' : null,
    expectedStartAt: daysAfter(input.taskNo),
    expectedEndAt: daysAfter(input.taskNo + 2),
    startDate: status === 'todo' ? null : daysAfter(input.taskNo),
    completedAt: status === 'submitted' ? daysAfter(input.taskNo + 2) : null,
    estimateHours: 4 + (input.taskNo % 5),
    aiEstimateHours: 3 + (input.taskNo % 4),
    references: [{ type: 'requirement', id: input.requirementId }],
    aiDraft: false,
    aiDraftAt: null,
    createdAt: hoursAfter(input.requirementNo + input.taskNo),
    updatedAt: hoursAfter(input.requirementNo + input.taskNo + 1),
  };
}

function buildTestCase(input) {
  const status = TEST_STATUSES[(input.requirementNo + input.caseNo) % TEST_STATUSES.length];
  return {
    id: `${ID_PREFIX}case-${input.versionId}-${pad(input.requirementNo, 5)}-${pad(input.caseNo, 2)}`,
    versionId: input.versionId,
    productId: input.productId,
    projectId: input.projectId,
    requirementId: input.requirementId,
    requirementProductId: input.requirementProductId,
    categoryId: null,
    code: `TC-${pad(input.requirementNo, 5)}-${pad(input.caseNo, 2)}`,
    title: `Test case ${pad(input.requirementNo, 5)}-${pad(input.caseNo, 2)}`,
    description: 'Performance fixture test case',
    status,
    roundNo: input.caseNo % 3 === 0 ? 2 : 1,
    priority: priority(input.requirementNo + input.caseNo),
    assigneeId: input.assigneeId,
    creatorId: input.assigneeId,
    plannedTestAt: daysAfter(input.caseNo + 3),
    plannedEndAt: daysAfter(input.caseNo + 4),
    startedAt: status === 'pending' ? null : daysAfter(input.caseNo + 3),
    completedAt: ['passed', 'failed', 'blocked'].includes(status) ? daysAfter(input.caseNo + 4) : null,
    estimateHours: 2 + (input.caseNo % 4),
    aiEstimateHours: 1 + (input.caseNo % 3),
    references: [{ type: 'requirement', id: input.requirementId }],
    aiDraft: false,
    aiDraftAt: null,
    createdAt: hoursAfter(input.requirementNo + input.caseNo),
    updatedAt: hoursAfter(input.requirementNo + input.caseNo + 1),
  };
}

function buildBug(input) {
  const status = BUG_STATUSES[input.bugNo % BUG_STATUSES.length];
  const severity = BUG_SEVERITIES[input.bugNo % BUG_SEVERITIES.length];
  return {
    id: `${ID_PREFIX}bug-${input.versionId}-${pad(input.bugNo, 5)}`,
    versionId: input.versionId,
    productId: input.productId,
    projectId: input.projectId,
    testCaseId: input.testCaseId ?? null,
    testCaseVersionId: input.testCaseId ? input.versionId : null,
    code: `BUG-${pad(input.bugNo, 5)}`,
    title: `Bug ${pad(input.bugNo, 5)}`,
    description: 'Performance fixture bug',
    status,
    severity,
    priority: priority(input.bugNo),
    assigneeId: input.assigneeId,
    reporterId: input.assigneeId,
    plannedFixAt: daysAfter(input.bugNo % 10),
    resolvedAt: ['fixed', 'verifying', 'closed'].includes(status) ? daysAfter((input.bugNo % 10) + 1) : null,
    closedAt: status === 'closed' ? daysAfter((input.bugNo % 10) + 2) : null,
    resolution: status === 'closed' ? 'fixed' : null,
    createdAt: hoursAfter(input.bugNo),
    updatedAt: hoursAfter(input.bugNo + 1),
  };
}

function buildWorkActivity(input) {
  const source = input.source;
  return {
    id: `${ID_PREFIX}activity-${input.versionId}-${pad(input.activityNo, 5)}`,
    versionId: input.versionId,
    productId: input.productId,
    projectId: input.projectId,
    actorId: input.actor.id,
    actorName: input.actor.name,
    sourceType: 'dev_task',
    sourceId: source?.id ?? `${ID_PREFIX}missing-source`,
    sourceVersionId: input.versionId,
    action: input.activityNo % 5 === 0 ? 'blocked' : 'progress',
    title: `Activity ${pad(input.activityNo, 5)}`,
    metadata: {
      category: input.activityNo % 5 === 0 ? 'risk' : 'progress',
      summary: `Performance activity ${input.activityNo}`,
    },
    occurredAt: hoursAfter(input.activityNo),
    createdAt: hoursAfter(input.activityNo),
  };
}

function buildXiaobaoSummary(input) {
  const riskScore = Math.min(100, 40 + (input.bugCount % 50));
  const riskLevel = riskScore >= 75 ? 'likely_delayed' : 'at_risk';
  const riskSignature = `${input.version.id}|${riskLevel}|${riskScore}|${input.unfinishedCount}`;
  return {
    versionId: input.version.id,
    riskLevel,
    riskScore,
    confidence: 72,
    forecastReleaseDate: daysAfter(24),
    riskSignature,
    summary: {
      versionId: input.version.id,
      versionName: input.version.name,
      riskLevel,
      riskScore,
      confidence: 72,
      riskSignature,
      dirty: false,
      signals: {
        unfinishedCount: input.unfinishedCount,
        openBugCount: input.bugCount,
        criticalBugCount: Math.floor(input.bugCount / 12),
        failedTestCount: Math.floor(input.unfinishedCount / 8),
        blockedCount: Math.floor(input.unfinishedCount / 15),
        silentRiskCount: Math.floor(input.unfinishedCount / 20),
      },
      reasons: [
        {
          key: 'remaining_work',
          title: 'Remaining fixture work',
          detail: `Fixture has ${input.unfinishedCount} scoped requirements.`,
          severity: 'warning',
        },
      ],
      currentSnapshot: {
        versionId: input.version.id,
        date: BASE_DATE.toISOString().slice(0, 10),
        riskScore,
        riskLevel,
        openBugCount: input.bugCount,
        criticalBugCount: Math.floor(input.bugCount / 12),
        failedTestCount: Math.floor(input.unfinishedCount / 8),
        blockedCount: Math.floor(input.unfinishedCount / 15),
        silentRiskCount: Math.floor(input.unfinishedCount / 20),
        confidence: 72,
        createdAt: BASE_DATE.toISOString(),
      },
    },
    dirty: false,
    recomputedAt: BASE_DATE,
    updatedAt: BASE_DATE,
  };
}

function summarizeFixture(fixture) {
  return {
    fixtureVersion: FIXTURE_VERSION,
    size: fixture.size,
    anchors: fixture.anchors,
    counts: Object.fromEntries(
      Object.entries(fixture.rows).map(([key, rows]) => [key, rows.length]),
    ),
  };
}

async function seedDatabase(fixture, outputPath) {
  const serverRequire = createRequire(new URL('../apps/server/package.json', import.meta.url));
  const { PrismaClient } = serverRequire('@prisma/client');
  const prisma = new PrismaClient();

  try {
    await clearPerfRows(prisma);
    await prisma.user.createMany({ data: fixture.rows.users, skipDuplicates: true });
    await prisma.product.createMany({ data: fixture.rows.products, skipDuplicates: true });
    await prisma.project.createMany({ data: fixture.rows.projects, skipDuplicates: true });
    await prisma.version.createMany({ data: fixture.rows.versions, skipDuplicates: true });
    await prisma.requirement.createMany({ data: fixture.rows.requirements, skipDuplicates: true });
    await prisma.versionPlan.createMany({ data: fixture.rows.versionPlans, skipDuplicates: true });
    await prisma.devTask.createMany({ data: fixture.rows.devTasks, skipDuplicates: true });
    await prisma.testCase.createMany({ data: fixture.rows.testCases, skipDuplicates: true });
    await prisma.bug.createMany({ data: fixture.rows.bugs, skipDuplicates: true });
    await prisma.workActivity.createMany({ data: fixture.rows.workActivities, skipDuplicates: true });
    await prisma.xiaobaoRiskSummary.createMany({ data: fixture.rows.xiaobaoRiskSummaries, skipDuplicates: true });
  } finally {
    await prisma.$disconnect();
  }

  const summary = summarizeFixture(fixture);
  await mkdir(dirname(outputPath), { recursive: true });
  await writeFile(outputPath, `${JSON.stringify(summary, null, 2)}\n`, 'utf8');
  return summary;
}

async function clearPerfRows(prisma) {
  await prisma.$transaction([
    prisma.xiaobaoRiskSummary.deleteMany({ where: { versionId: { startsWith: `${ID_PREFIX}version-` } } }),
    prisma.workActivity.deleteMany({ where: { id: { startsWith: `${ID_PREFIX}activity-` } } }),
    prisma.bug.deleteMany({ where: { id: { startsWith: `${ID_PREFIX}bug-` } } }),
    prisma.testCase.deleteMany({ where: { id: { startsWith: `${ID_PREFIX}case-` } } }),
    prisma.devTask.deleteMany({ where: { id: { startsWith: `${ID_PREFIX}dev-` } } }),
    prisma.versionPlan.deleteMany({ where: { id: { startsWith: `${ID_PREFIX}plan-` } } }),
    prisma.requirement.deleteMany({ where: { id: { startsWith: `${ID_PREFIX}req-` } } }),
    prisma.version.deleteMany({ where: { id: { startsWith: `${ID_PREFIX}version-` } } }),
    prisma.project.deleteMany({ where: { id: { startsWith: `${ID_PREFIX}project-` } } }),
    prisma.product.deleteMany({ where: { id: { startsWith: `${ID_PREFIX}product-` } } }),
    prisma.user.deleteMany({ where: { id: { startsWith: `${ID_PREFIX}user-` } } }),
  ]);
}

async function main() {
  const args = parseArgs(process.argv.slice(2));
  const fixture = buildFixture(args.size);
  const summary = summarizeFixture(fixture);

  if (args.dryRun) {
    if (args.json) {
      console.log(JSON.stringify(summary, null, 2));
    } else {
      printSummary(summary, 'dry-run');
    }
    return;
  }

  const outputPath = resolve(process.cwd(), args.output);
  const seeded = await seedDatabase(fixture, outputPath);
  if (args.json) {
    console.log(JSON.stringify(seeded, null, 2));
  } else {
    printSummary(seeded, `seeded; metadata=${args.output}`);
  }
}

function printSummary(summary, mode) {
  console.log(`Large dataset fixture ${mode}`);
  console.log(`version=${summary.fixtureVersion} size=${summary.size}`);
  console.log(`anchors=${JSON.stringify(summary.anchors)}`);
  for (const [name, count] of Object.entries(summary.counts)) {
    console.log(`${name}=${count}`);
  }
}

main().catch((error) => {
  console.error(error instanceof Error ? error.message : error);
  process.exitCode = 1;
});
