// qb:load — thin CLI wrapper. Parses args, reads the xlsx, hands off to
// tools/lib/journal-load.ts for every DB-flow step, then prints the SAME
// output format as before the refactor (docs/superpowers/plans/
// 2026-07-31-local-app.md, Task 2).
// See docs/superpowers/plans/2026-07-30-qb-migration-loader.md, Task 2.
//
// Two connections, two jobs:
//  - admin (DATABASE_URL): resolve the company id and the user id journal
//    batches get attributed to. Read-only lookups; never creates anything
//    (run chart:load first). A single Client — one lookup, then closed.
//  - app (APP_DATABASE_URL, RLS-bound): everything else, via a small
//    dedicated Pool (see tools/lib/journal-load.ts's CONNECTION CONTRACT —
//    loadJournalYears/postYears own their sessions and must not be handed
//    an already-open client/transaction).

import { parseArgs } from "node:util";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { Client, Pool } from "pg";
import "dotenv/config";
import { resolveDatabaseUrls } from "../server/src/db.js";
import { readQbSheet } from "./qb-xlsx.js";
import { parseJournalRows, groupEntriesByYear } from "./journal-logic.js";
import {
  resolveCompanyAndOwner,
  loadJournalYears,
  postYears,
  shouldRevalidate,
  printYearReport,
  type YearResult,
} from "./lib/journal-load.js";

// Re-exported unchanged so tests/tools/load-quickbooks.test.ts keeps
// importing them from here without modification.
export { shouldRevalidate, printYearReport, type YearResult };

// Resolved when needed, not at import — same reasoning as tools/load-chart.ts.
// The hardcoded localhost:5432 pair that used to be here named the Docker
// container; since its removal on 2026-08-08 that address belongs to whatever
// PostgreSQL the developer runs themselves, and this tool posts a company's
// entire QuickBooks history into what it is pointed at.
async function databaseUrls(): Promise<{ admin: string; app: string }> {
  const { admin, app } = await resolveDatabaseUrls();
  return { admin, app };
}

interface Args {
  file: string;
  company: string;
  year: number | null;
  post: boolean;
}

function parseCliArgs(argv: string[]): Args {
  const { values } = parseArgs({
    args: argv,
    options: {
      file: { type: "string" },
      company: { type: "string" },
      year: { type: "string" },
      post: { type: "boolean", default: false },
    },
  });
  if (!values.file || !values.company) {
    throw new Error(
      "usage: qb:load --file <xlsx> --company <name> [--year <yyyy>] [--post]",
    );
  }
  let year: number | null = null;
  if (values.year !== undefined) {
    year = Number(values.year);
    if (!Number.isInteger(year)) {
      throw new Error(`--year must be an integer, got "${values.year}"`);
    }
  }
  return { file: values.file, company: values.company, year, post: values.post ?? false };
}

export async function run(argv: string[]): Promise<number> {
  const args = parseCliArgs(argv);
  const basename = path.basename(args.file);

  // Read and parse the file BEFORE touching either connection — a bad file
  // should never produce a half-written batch.
  const sheetRows = await readQbSheet(args.file);
  const entries = parseJournalRows(sheetRows);
  if (entries.length === 0) {
    console.error(`no entries found in "${args.file}"`);
    return 1;
  }

  const byYear = groupEntriesByYear(entries);
  let years = [...byYear.keys()].sort((a, b) => a - b);
  if (args.year !== null) {
    if (!byYear.has(args.year)) {
      console.error(`no entries found for year ${args.year} in "${args.file}"`);
      return 1;
    }
    years = [args.year];
  }

  const { admin: adminUrl, app: appUrl } = await databaseUrls();
  const adminClient = new Client({ connectionString: adminUrl });
  let companyId: string;
  let ownerId: string;
  await adminClient.connect();
  try {
    ({ companyId, ownerId } = await resolveCompanyAndOwner(adminClient, args.company));
  } finally {
    await adminClient.end();
  }

  // A small dedicated pool for this run only — loadJournalYears/postYears
  // check out their own client per year and set app.company_id themselves
  // (see tools/lib/journal-load.ts). Never the shared request-serving pool
  // a server would use for db.withCompany().
  const appPool = new Pool({ connectionString: appUrl });
  try {
    const results = await loadJournalYears(appPool, {
      companyId, ownerId, filename: basename, entriesByYear: byYear, years,
    });
    for (const result of results) printYearReport(result);

    if (!args.post) {
      return 0;
    }

    const dirty = results.filter((r) => r.errorCount > 0);
    if (dirty.length > 0) {
      console.error(
        `refusing to post: ${dirty.length} targeted year(s) have validation errors: ` +
          dirty.map((r) => r.year).join(", "),
      );
      return 1;
    }

    const postResult = await postYears(appPool, companyId, ownerId, results);
    for (const y of postResult.years) {
      if (y.alreadyPosted) {
        console.log(`year ${y.year}: already posted — skipping (batch ${y.batchId})`);
        continue;
      }
      if (y.error) {
        console.error(`year ${y.year}: post failed — ${y.error}`);
        return 1;
      }
      console.log(`year ${y.year}: posted ${y.posted} entries (batch ${y.batchId})`);
      console.log(`  reconciliation: ${JSON.stringify(y.reconciliation)}`);
    }

    return 0;
  } finally {
    await appPool.end();
  }
}

const isMain = process.argv[1] &&
  path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
if (isMain) {
  run(process.argv.slice(2))
    .then((code) => { process.exitCode = code; })
    .catch((err) => { console.error(err); process.exitCode = 1; });
}
