#!/usr/bin/env bun
import { existsSync, mkdirSync, writeFileSync } from "fs";
import { join, resolve } from "path";
import {
  buildAmdCounts,
  buildWritePlan,
  defaultRunDir,
  loadMapping,
  readCheckpointFile,
  readJsonFile,
  renderRunReport,
  verifyWritePlan,
  writeRunArtifacts,
  type AmdCountsCheckpoint,
  type AmdVisitInput,
  type RipplingInspection,
  type VerificationCheckpoint,
  type WritePlan,
} from "./shift-approval.js";

interface Args {
  [key: string]: string | boolean | undefined;
  _: string[];
}

const args = parseArgs(Bun.argv.slice(2));
const phase = args._[0] ?? "help";

try {
  switch (phase) {
    case "collect":
      collect(args);
      break;
    case "summarize":
      summarize(args);
      break;
    case "inspect-rippling":
      inspectRippling(args);
      break;
    case "plan-writes":
      planWrites(args);
      break;
    case "apply":
      apply(args);
      break;
    case "verify":
      verify(args);
      break;
    case "help":
    default:
      usage(phase !== "help" ? `Unknown phase: ${phase}` : undefined);
  }
} catch (error) {
  const message = error instanceof Error ? error.message : String(error);
  console.error(`approve-shifts: ${message}`);
  process.exit(1);
}

function collect(args: Args): void {
  const start = required(args, "start");
  const end = required(args, "end");
  const visitsPath = args["visits-json"];
  if (typeof visitsPath !== "string") {
    throw new Error("collect currently requires --visits-json with a read-only AdvancedMD visit export.");
  }

  const runDir = runDirFor(args, { start, end });
  const configPath = stringArg(args, "config");
  const source = (stringArg(args, "source") ?? "advancedmd-api") as AmdCountsCheckpoint["source"];
  const visits = readJsonFile<AmdVisitInput[]>(resolve(visitsPath));
  const counts = buildAmdCounts(visits, { start, end }, loadMapping(configPath), source);
  const report = renderRunReport({ counts });
  writeRunArtifacts(runDir, { amdCounts: counts, runReport: report });
  if (counts.blockingIssues.length > 0) {
    throw new Error(`AMD counts contain ${counts.blockingIssues.length} blocking issue(s); wrote ${runDir}/amd-counts.json`);
  }
  console.log(`Wrote ${join(runDir, "amd-counts.json")}`);
}

function summarize(args: Args): void {
  const runDir = required(args, "run-dir");
  const counts = readCheckpointFile<AmdCountsCheckpoint>(join(runDir, "amd-counts.json"));
  const inspection = optionalCheckpoint<RipplingInspection>(join(runDir, "rippling-inspection.json"));
  const plan = optionalCheckpoint<WritePlan>(join(runDir, "write-plan.json"));
  const verification = optionalCheckpoint<VerificationCheckpoint>(join(runDir, "verification.json"));
  const report = renderRunReport({ counts, inspection, plan, verification });
  mkdirSync(runDir, { recursive: true });
  writeFileSync(join(runDir, "run-report.md"), report);
  console.log(report);
}

function inspectRippling(args: Args): void {
  const runDir = required(args, "run-dir");
  const inspectionPath = args["inspection-json"];
  if (typeof inspectionPath !== "string") {
    throw new Error("inspect-rippling requires --inspection-json until the browser inspector driver is connected.");
  }
  const inspection = readCheckpointFile<RipplingInspection>(resolve(inspectionPath));
  writeRunArtifacts(runDir, { ripplingInspection: inspection });
  console.log(`Wrote ${join(runDir, "rippling-inspection.json")}`);
}

function planWrites(args: Args): void {
  const runDir = required(args, "run-dir");
  const counts = readCheckpointFile<AmdCountsCheckpoint>(join(runDir, "amd-counts.json"));
  const inspection = readCheckpointFile<RipplingInspection>(join(runDir, "rippling-inspection.json"));
  const plan = buildWritePlan(counts, inspection);
  const report = renderRunReport({ counts, inspection, plan });
  writeRunArtifacts(runDir, { writePlan: plan, runReport: report });
  if (plan.blockingIssues.length > 0 || plan.summary.needsHumanReview > 0) {
    throw new Error(
      `write plan is not applyable: ${plan.blockingIssues.length} blocking issue(s), ${plan.summary.needsHumanReview} human-review row(s)`
    );
  }
  console.log(`Wrote ${join(runDir, "write-plan.json")}`);
}

function apply(args: Args): void {
  const runDir = required(args, "run-dir");
  requireCheckpoint(runDir, "amd-counts.json");
  requireCheckpoint(runDir, "rippling-inspection.json");
  requireCheckpoint(runDir, "write-plan.json");
  const plan = readCheckpointFile<WritePlan>(join(runDir, "write-plan.json"));
  if (plan.blockingIssues.length > 0 || plan.summary.needsHumanReview > 0) {
    throw new Error("refusing to apply a plan with blocking issues or human-review rows");
  }
  if (args["confirm-live"] !== true) {
    throw new Error("refusing live Rippling writes without --confirm-live");
  }
  throw new Error("Rippling browser write driver is not connected yet; checkpoint validation succeeded but no rows were changed.");
}

function verify(args: Args): void {
  const runDir = required(args, "run-dir");
  const verificationPath = args["verification-json"];
  if (typeof verificationPath !== "string") {
    throw new Error("verify requires --verification-json captured after reopening Rippling rows.");
  }
  const plan = readCheckpointFile<WritePlan>(join(runDir, "write-plan.json"));
  const verification = verifyWritePlan(plan, readCheckpointFile<VerificationCheckpoint>(resolve(verificationPath)));
  const counts = readCheckpointFile<AmdCountsCheckpoint>(join(runDir, "amd-counts.json"));
  const inspection = optionalCheckpoint<RipplingInspection>(join(runDir, "rippling-inspection.json"));
  const report = renderRunReport({ counts, inspection, plan, verification });
  writeRunArtifacts(runDir, { verification, runReport: report });
  if ((verification.mismatches?.length ?? 0) > 0) {
    throw new Error(`verification found ${verification.mismatches!.length} mismatch(es)`);
  }
  console.log(`Wrote ${join(runDir, "verification.json")}`);
}

function parseArgs(argv: string[]): Args {
  const parsed: Args = { _: [] };
  for (let i = 0; i < argv.length; i++) {
    const token = argv[i];
    if (!token.startsWith("--")) {
      parsed._.push(token);
      continue;
    }
    const name = token.slice(2);
    const equalsIndex = name.indexOf("=");
    if (equalsIndex !== -1) {
      parsed[name.slice(0, equalsIndex)] = name.slice(equalsIndex + 1);
      continue;
    }
    const next = argv[i + 1];
    if (!next || next.startsWith("--")) {
      parsed[name] = true;
    } else {
      parsed[name] = next;
      i++;
    }
  }
  return parsed;
}

function required(args: Args, name: string): string {
  const value = args[name];
  if (typeof value !== "string" || value.length === 0) throw new Error(`missing --${name}`);
  return value;
}

function stringArg(args: Args, name: string): string | undefined {
  const value = args[name];
  return typeof value === "string" ? value : undefined;
}

function runDirFor(args: Args, period: { start: string; end: string }): string {
  return stringArg(args, "run-dir") ?? defaultRunDir(period);
}

function optionalCheckpoint<T extends { schemaVersion?: number }>(path: string): T | undefined {
  return existsSync(path) ? readCheckpointFile<T>(path) : undefined;
}

function requireCheckpoint(runDir: string, file: string): void {
  if (!existsSync(join(runDir, file))) throw new Error(`missing required checkpoint ${join(runDir, file)}`);
}

function usage(error?: string): never {
  if (error) console.error(error);
  console.error(`Usage:
  approve-shifts collect --start YYYY-MM-DD --end YYYY-MM-DD --visits-json visits.json [--run-dir DIR]
  approve-shifts summarize --run-dir DIR
  approve-shifts inspect-rippling --run-dir DIR --inspection-json rippling-inspection.json
  approve-shifts plan-writes --run-dir DIR
  approve-shifts apply --run-dir DIR --confirm-live  # bare flag only
  approve-shifts verify --run-dir DIR --verification-json verification.json
`);
  process.exit(error ? 1 : 0);
}
