/**
 * `--clear`: take the invented book back out again.
 *
 * Everything the demo seed writes is either prefixed `DEMO — ` or hangs off a
 * row that is, so every delete here is scoped to one of those two things. A
 * teardown that runs against a database with real records in it is exactly the
 * situation this command is for — someone demoing on top of their own data —
 * and it must leave that data untouched.
 */
import { eq, inArray, like, or, sql } from 'drizzle-orm';
import { unstampDemoActivity } from './activities';
import {
  accounts,
  activities,
  allocations,
  calendarEntries,
  capacityCommitments,
  capacityRequests,
  complianceArtifacts,
  contacts,
  contractObligations,
  contracts,
  demandDeals,
  engagementArtifacts,
  engagements,
  exportAuthorizations,
  learnResources,
  motionTemplates,
  qualificationScores,
  slaTerms,
  teamMemberships,
  users,
  supplyDeals,
} from '../../schema/index';
import type { DemoContext } from './index';

export async function clear(context: DemoContext): Promise<void> {
  const { db, prefix } = context;

  console.log('Removing demo data…');
  // Ordered so foreign keys never block a delete.
  const demoAccounts = await db
    .select({ id: accounts.id })
    .from(accounts)
    .where(like(accounts.name, `${prefix}%`));
  const ids = demoAccounts.map((a) => a.id);

  /*
   * The children of the demo paper and the demo deals, named explicitly.
   *
   * These three deletes — obligations, SLA terms, capacity requests — used to
   * run with NO WHERE CLAUSE. `pnpm db:demo -- --clear` is a documented
   * command, so anyone who ran the demo book on top of their own data lost
   * every renewal deadline, every negotiated SLA term and every customer
   * capacity request in the database along with it: the three tables whose
   * rows are hardest to reconstruct, because they record what was negotiated
   * rather than what can be re-imported. Scoping them by parent id keeps the
   * blast radius inside the demo book and keeps the delete order valid, since
   * each of the three is a child of a row deleted a few lines further down.
   */
  const demoContracts = await db
    .select({ id: contracts.id })
    .from(contracts)
    .where(like(contracts.title, `${prefix}%`));
  const contractIds = demoContracts.map((c) => c.id);

  const demoDemandDeals = await db
    .select({ id: demandDeals.id })
    .from(demandDeals)
    .where(like(demandDeals.name, `${prefix}%`));
  const demandDealIds = demoDemandDeals.map((d) => d.id);

  await db.delete(calendarEntries).where(like(calendarEntries.title, `${prefix}%`));
  await db.delete(learnResources).where(like(learnResources.title, `${prefix}%`));
  await db.delete(allocations).where(like(allocations.notes, `${prefix}%`));
  // Guarded rather than relying on `inArray` with an empty list, which is a
  // condition different drivers have historically disagreed about.
  if (contractIds.length > 0) {
    await db.delete(contractObligations).where(inArray(contractObligations.contractId, contractIds));
    await db.delete(slaTerms).where(inArray(slaTerms.contractId, contractIds));
  }
  await db.delete(contracts).where(like(contracts.title, `${prefix}%`));
  if (demandDealIds.length > 0) {
    await db.delete(capacityRequests).where(inArray(capacityRequests.demandDealId, demandDealIds));
  }
  /*
   * The Motion rows, before the deals they hang off.
   *
   * `engagements.demand_deal_id` cascades, so the delete below would take the
   * engagements, their artefacts and their scores with it — but not the
   * promoted version 2, which is a `motion_templates` row and would be left
   * behind as an orphaned version of a shipped lineage with its
   * `origin_artifact_id` quietly set to null. So the promoted template goes
   * first, and the three engagement tables are removed explicitly rather than
   * by cascade, in child-to-parent order, so the teardown reads as what it
   * does. The starter library itself is NOT touched: it is authored product
   * content from the base seed, carries no prefix, and survives `--clear` the
   * same way the PIG-hosted learn rows do.
   *
   * Found by parentage as well as by prefix, because the cascade off the demo
   * deals takes a wider set than the prefix does: an engagement opened on a
   * demo deal through the app carries whatever summary its author typed, or
   * none. Every one of those the prefix scan missed would be deleted anyway a
   * few lines below, silently, with its artefacts' borrowed `usage_count`
   * never given back — and a starter template left above zero can never be
   * edited again.
   */
  const demoEngagements = await db
    .select({ id: engagements.id })
    .from(engagements)
    .where(
      demandDealIds.length > 0
        ? or(like(engagements.summary, `${prefix}%`), inArray(engagements.demandDealId, demandDealIds))
        : like(engagements.summary, `${prefix}%`),
    );
  const engagementIds = demoEngagements.map((engagement) => engagement.id);
  if (engagementIds.length > 0) {
    /*
     * Give back the `usage_count` the demo book borrowed, before the artefacts
     * that justify it are deleted.
     *
     * Above zero, `usage_count` makes a template permanently un-editable — the
     * API answers 409 and tells you to cut a new version. So a teardown that
     * left the counter raised would hand back a starter library three of whose
     * templates can never be edited again, for engagements that no longer
     * exist, and nothing in the UI would explain why. Decremented by exactly
     * what the demo added rather than reset to zero, because a real
     * instantiation on the same template must survive `--clear`.
     */
    const borrowed = await db
      .select({ templateId: engagementArtifacts.templateId })
      .from(engagementArtifacts)
      .where(inArray(engagementArtifacts.engagementId, engagementIds));
    const usageByTemplate = new Map<string, number>();
    for (const row of borrowed) {
      if (row.templateId) {
        usageByTemplate.set(row.templateId, (usageByTemplate.get(row.templateId) ?? 0) + 1);
      }
    }
    for (const [templateId, count] of usageByTemplate) {
      await db
        .update(motionTemplates)
        .set({ usageCount: sql`greatest(${motionTemplates.usageCount} - ${count}, 0)` })
        .where(eq(motionTemplates.id, templateId));
    }

    await db
      .delete(qualificationScores)
      .where(inArray(qualificationScores.engagementId, engagementIds));
    await db
      .delete(engagementArtifacts)
      .where(inArray(engagementArtifacts.engagementId, engagementIds));
    await db.delete(engagements).where(inArray(engagements.id, engagementIds));
  }
  // After the artefacts, so `origin_artifact_id` is already gone rather than
  // being set null on the way past. Scoped to the prefix, which the seeded
  // library deliberately does not carry.
  await db.delete(motionTemplates).where(like(motionTemplates.title, `${prefix}%`));

  await db.delete(demandDeals).where(like(demandDeals.name, `${prefix}%`));
  await db.delete(supplyDeals).where(like(supplyDeals.name, `${prefix}%`));
  await db.delete(capacityCommitments).where(like(capacityCommitments.name, `${prefix}%`));
  // Before the rows go: the supplier accounts are real and survive `--clear`,
  // but their lastActivityAt is computed from demo correspondence. Recompute it
  // from whatever non-demo activity remains first — once the activities are
  // deleted there is no way to tell which accounts the demo book had touched.
  await unstampDemoActivity(context);

  await db.delete(activities).where(like(activities.subject, `${prefix}%`));
  for (const id of ids) {
    // Compliance rows cascade on the account anyway; deleted explicitly so the
    // order of removal stays readable rather than relying on the constraint.
    await db.delete(exportAuthorizations).where(eq(exportAuthorizations.accountId, id));
    await db.delete(complianceArtifacts).where(eq(complianceArtifacts.accountId, id));
    await db.delete(contacts).where(eq(contacts.accountId, id));
  }
  await db.delete(accounts).where(like(accounts.name, `${prefix}%`));

  // The invented sellers the demand book creates to own its records. Their
  // foreign keys are `on delete set null`, so leaving them behind corrupts
  // nothing — but a teardown that leaves four fictional colleagues in the team
  // list has not finished the job.
  const demoSellers = await db.select({ id: users.id }).from(users).where(like(users.name, `${prefix}%`));
  if (demoSellers.length > 0) {
    const sellerIds = demoSellers.map((seller) => seller.id);
    await db.delete(teamMemberships).where(inArray(teamMemberships.userId, sellerIds));
    await db.delete(users).where(inArray(users.id, sellerIds));
  }

  console.log(`Removed ${ids.length} demo account(s) and everything hanging from them.`);
}
