/**
 * Standalone folder populator.
 *
 * Bypasses mailx's broken sync loop and directly fetches the latest N
 * messages from every folder of every IMAP account, writing rows into the
 * mailx.db. Designed to run while rmfmail.exe is killed (overnight job).
 *
 * Why: mailx's sync has been silently failing on bobma — bobma/Sent has
 * never had real server-fetched rows in the local DB. Rather than fight
 * the sync loop's connection-state corruption, this tool uses a single
 * fresh IMAP connection (which we proved completes SELECT Sent in 26ms)
 * and pumps messages into the DB directly via upsertMessage.
 *
 * Run:
 *   1. Kill rmfmail.exe
 *   2. cd Y:/dev/email/mailx/app
 *   3. npx tsx Y:/dev/email/mailx/dev/populate-folders.ts [--n 500] [--account bobma] [--folder Sent]
 *
 * Defaults: N=500 latest messages per folder, all IMAP accounts, all folders.
 *
 * Notes:
 *   - Bodies are NOT fetched here — only envelope rows. Body fetch is
 *     mailx's prefetch loop's job; this tool just unblocks the metadata.
 *   - One connection, sequential folders. No parallelism that could
 *     trigger the same connection-state issues mailx hits.
 *   - Progress logged per folder. Failures logged + skipped, never bail.
 *   - Idempotent: re-running re-upserts. UNIQUE(account, folder, uid)
 *     constraint means existing rows are updated, not duplicated.
 */

import * as fs from "node:fs";
import * as path from "node:path";
import * as os from "node:os";
import { NativeImapClient } from "@bobfrankston/iflow-direct";
import { NodeTcpTransport } from "@bobfrankston/node-tcp-transport";
import { MailxDB } from "@bobfrankston/mailx-store";

// ── Args ──
const args = process.argv.slice(2);
const argVal = (name: string, def?: string): string | undefined => {
    const i = args.indexOf(`--${name}`);
    if (i < 0 || i === args.length - 1) return def;
    return args[i + 1];
};
const FETCH_N = Number(argVal("n", "500"));
const ONLY_ACCOUNT = argVal("account");
const ONLY_FOLDER = argVal("folder");
const CONNECT_TIMEOUT_MS = Number(argVal("timeout", "60000"));

// ── Config ──
const home = process.env.USERPROFILE || os.homedir();
const dbDir = path.join(home, ".rmfmail");
const accountsPath = path.join(dbDir, "accounts.jsonc");
if (!fs.existsSync(accountsPath)) {
    console.error(`accounts.jsonc not found at ${accountsPath}`);
    process.exit(1);
}

// JSONC: strip // and /* */ before parsing
function parseJsonc(raw: string): any {
    const stripped = raw
        .replace(/\/\*[\s\S]*?\*\//g, "")
        .replace(/^\s*\/\/.*$/gm, "");
    return JSON.parse(stripped);
}

const accountsCfg = parseJsonc(fs.readFileSync(accountsPath, "utf-8"));
const accounts = accountsCfg.accounts || [];

// ── Helpers ──
function ts(): string { return new Date().toISOString().replace("T", " ").slice(0, 19); }
function log(msg: string): void { console.log(`[${ts()}] ${msg}`); }

// Skip Gmail / API-backed accounts — they don't speak IMAP through this
// tool's path, and Gmail's sync route is separate (REST). Same for any
// account without an `imap` block.
function shouldUseImap(acct: any): boolean {
    if (!acct?.imap?.host) return false;
    const host = String(acct.imap.host).toLowerCase();
    if (host.includes("gmail.com") || host.includes("googlemail.com")) return false;
    return true;
}

// Skip noselect / [Gmail] virtual folders / huge label folders (All Mail).
// Folders we DO want: every selectable real folder, including Sent / Drafts /
// custom user folders. The skip list is conservative.
function shouldSkipFolder(folder: { id: number; name: string; specialUse: string | null; flags: string[] }): string | null {
    const flags = folder.flags || [];
    if (flags.some(f => /noselect/i.test(f))) return "noselect";
    if (folder.name === "[Gmail]") return "Gmail virtual";
    return null;
}

// ── Main ──
async function populateAccount(db: MailxDB, account: any): Promise<{ folders: number; messages: number; failed: number }> {
    const folders = db.getFolders(account.id);
    if (folders.length === 0) {
        log(`  [${account.id}] no folders in DB — run mailx sync at least once first to populate the folder list`);
        return { folders: 0, messages: 0, failed: 0 };
    }

    const port = account.imap.port || 993;
    const useTls = account.imap.tls !== false;
    log(`  [${account.id}] connecting to ${account.imap.host}:${port} ${useTls ? "(TLS)" : "(plain)"}...`);

    const client = new NativeImapClient(
        {
            server: account.imap.host,
            port,
            username: account.imap.user || account.imap.username,
            password: account.imap.password,
            verbose: false,
            inactivityTimeout: CONNECT_TIMEOUT_MS,
            greetingTimeout: 30_000,
        },
        () => new NodeTcpTransport(),
    );

    let counters = { folders: 0, messages: 0, failed: 0 };
    try {
        await client.connect();
        log(`  [${account.id}] connected.`);

        const targets = ONLY_FOLDER
            ? folders.filter(f => f.name === ONLY_FOLDER || f.path === ONLY_FOLDER)
            : folders;
        log(`  [${account.id}] ${targets.length} folder(s) to process`);

        for (const folder of targets) {
            const skip = shouldSkipFolder(folder as any);
            if (skip) {
                log(`    skip ${folder.name} (${skip})`);
                continue;
            }
            const t0 = Date.now();
            try {
                const info = await client.select(folder.path || folder.name);
                const startUid = db.getHighestUid(account.id, folder.id);
                if (info.exists === 0) {
                    log(`    ${folder.name}: empty server-side`);
                    counters.folders++;
                    continue;
                }
                // Fetch the latest N by sequence — fast, doesn't depend on
                // having a known highestUid. Newer messages overwrite older
                // upserts (UNIQUE constraint) so re-runs are idempotent.
                const msgs = await client.fetchLatestN(FETCH_N, { source: false });
                let stored = 0;
                for (const m of msgs) {
                    if (typeof m.uid !== "number" || m.uid <= 0) continue;
                    db.upsertMessage({
                        accountId: account.id,
                        folderId: folder.id,
                        uid: m.uid,
                        messageId: m.messageId || "",
                        inReplyTo: m.inReplyTo || "",
                        references: [],
                        date: m.date ? m.date.getTime() : 0,
                        subject: m.subject || "",
                        from: (m.from && m.from[0]) ? { name: m.from[0].name || "", address: m.from[0].address || "" } : { name: "", address: "" },
                        to: (m.to || []).map(a => ({ name: a.name || "", address: a.address || "" })),
                        cc: (m.cc || []).map(a => ({ name: a.name || "", address: a.address || "" })),
                        flags: Array.from(m.flags || []),
                        size: m.size || 0,
                        hasAttachments: false,        // populated by body-prefetch later
                        preview: "",                  // populated by body-prefetch later
                        bodyPath: "",                 // body not fetched here
                    });
                    stored++;
                }
                db.recalcFolderCounts(folder.id);
                counters.folders++;
                counters.messages += stored;
                const took = Date.now() - t0;
                log(`    ${folder.name}: server=${info.exists}, fetched=${msgs.length}, stored=${stored}, prevHighUid=${startUid} (${took}ms)`);
            } catch (e: any) {
                counters.failed++;
                log(`    ${folder.name}: ERROR ${e?.message || e}`);
                // Connection might be in a bad state — try to recover by
                // closing the mailbox. Continue to next folder either way.
                try { await client.closeMailbox(); } catch { /* */ }
            }
        }
    } finally {
        try { await client.logout(); } catch { /* */ }
    }
    return counters;
}

async function main(): Promise<void> {
    log(`mailx populate-folders starting`);
    log(`  config: dbDir=${dbDir} fetchN=${FETCH_N}${ONLY_ACCOUNT ? " account=" + ONLY_ACCOUNT : ""}${ONLY_FOLDER ? " folder=" + ONLY_FOLDER : ""}`);

    const db = new MailxDB(dbDir);
    let totalFolders = 0, totalMessages = 0, totalFailed = 0;

    for (const acct of accounts) {
        if (!acct.enabled && acct.enabled !== undefined) continue;
        if (ONLY_ACCOUNT && acct.id !== ONLY_ACCOUNT) continue;
        if (!shouldUseImap(acct)) {
            log(`[${acct.id}] skipped (not IMAP / Gmail-API account)`);
            continue;
        }
        log(`[${acct.id}] starting...`);
        try {
            const c = await populateAccount(db, acct);
            log(`[${acct.id}] done: ${c.folders} folders, ${c.messages} messages, ${c.failed} failed`);
            totalFolders += c.folders; totalMessages += c.messages; totalFailed += c.failed;
        } catch (e: any) {
            log(`[${acct.id}] FATAL ${e?.message || e}`);
        }
    }

    log(`---`);
    log(`Total: ${totalFolders} folders processed, ${totalMessages} messages stored, ${totalFailed} failures`);
    log(`Done. Restart rmfmail to see populated folders.`);
}

main().catch(e => {
    console.error("fatal:", e);
    process.exit(1);
});
