import { execFileSync, spawnSync } from "node:child_process";
import { homedir } from "node:os";
import { resolve } from "node:path";
import { writeConfig, writePassword, removeConfig, checkAddressBinding, writeBindingConfig, } from "../lib/credentials.js";
import { inferProvider, getProviderNotes } from "../lib/providers.js";
import * as imap from "../lib/imap.js";
import * as smtp from "../lib/smtp.js";
/**
 * Ensures email cron jobs (email-fetch, email-auto-respond) are registered.
 * Called after successful email setup so polling starts immediately —
 * no reinstall or service restart required.
 *
 * If the installer already registered the crons (via the MAXY CRONS block),
 * this is a no-op. Handles the case where email is configured on a device
 * that predates the installer cron registration fix.
 */
function ensureEmailCrons(accountId) {
    try {
        const listResult = spawnSync("crontab", ["-l"], {
            encoding: "utf-8",
            stdio: ["pipe", "pipe", "pipe"],
        });
        const existing = listResult.status === 0 ? listResult.stdout : "";
        // If email-fetch is already in the crontab, nothing to do
        if (existing.includes("email-fetch")) {
            return { registered: true };
        }
        // Derive paths from the MCP server's environment
        const platformRoot = process.env.PLATFORM_ROOT ?? resolve(import.meta.dirname, "../../../..");
        const nodeBin = (() => {
            try {
                return execFileSync("which", ["node"], { encoding: "utf-8" }).trim();
            }
            catch {
                return "/usr/bin/node";
            }
        })();
        const logDir = resolve(homedir(), ".maxy/logs");
        const cronEntries = [
            `* * * * * mkdir -p ${logDir} && PLATFORM_ROOT=${platformRoot} ACCOUNT_ID=${accountId} ${nodeBin} ${platformRoot}/plugins/email/mcp/dist/scripts/email-fetch.js >> ${logDir}/email-fetch.log 2>&1`,
            `* * * * * mkdir -p ${logDir} && PLATFORM_ROOT=${platformRoot} ${nodeBin} ${platformRoot}/plugins/email/mcp/dist/scripts/email-auto-respond.js >> ${logDir}/email-auto-respond.log 2>&1`,
        ];
        const newCrontab = existing.trimEnd()
            ? `${existing.trimEnd()}\n${cronEntries.join("\n")}\n`
            : `${cronEntries.join("\n")}\n`;
        const writeResult = spawnSync("crontab", ["-"], {
            input: newCrontab,
            encoding: "utf-8",
            stdio: ["pipe", "pipe", "pipe"],
        });
        if (writeResult.status !== 0) {
            return { registered: false, error: (writeResult.stderr || "").trim() };
        }
        return { registered: true };
    }
    catch (err) {
        return {
            registered: false,
            error: err instanceof Error ? err.message : String(err),
        };
    }
}
export async function emailSetup(params) {
    const { email, password, accountId } = params;
    // Infer provider defaults if hosts not explicitly provided
    const provider = inferProvider(email);
    const providerNotes = getProviderNotes(email) ?? undefined;
    const agentAddress = params.agentAddress?.trim() || email;
    const config = {
        email,
        agentAddress,
        imapHost: params.imapHost ?? provider?.imapHost ?? "",
        imapPort: params.imapPort ?? provider?.imapPort ?? 993,
        imapSecurity: params.imapSecurity ?? provider?.imapSecurity ?? "tls",
        smtpHost: params.smtpHost ?? provider?.smtpHost ?? "",
        smtpPort: params.smtpPort ?? provider?.smtpPort ?? 587,
        smtpSecurity: params.smtpSecurity ?? provider?.smtpSecurity ?? "starttls",
    };
    // Reject if hosts are still empty (unknown provider, user didn't provide)
    if (!config.imapHost) {
        return {
            success: false,
            message: `Cannot infer IMAP host for ${email}. Provide imapHost and smtpHost explicitly.`,
            providerNotes,
        };
    }
    if (!config.smtpHost) {
        return {
            success: false,
            message: `Cannot infer SMTP host for ${email}. Provide smtpHost explicitly.`,
            providerNotes,
        };
    }
    // Validate 1:1 agent-email binding: each address maps to exactly one agent
    const agentSlug = params.agentSlug ?? "admin";
    const conflicting = await checkAddressBinding(agentAddress, agentSlug);
    if (conflicting) {
        return {
            success: false,
            message: `Email address ${agentAddress} is already bound to agent "${conflicting}". Each email address can only be used by one agent.`,
        };
    }
    // Warn about likely misconfigured port/security combinations
    if (config.imapPort === 993 && config.imapSecurity === "starttls") {
        return {
            success: false,
            message: "Port 993 uses implicit TLS, not STARTTLS. Use imapSecurity: 'tls' with port 993, or port 143 with 'starttls'.",
        };
    }
    if (config.smtpPort === 465 && config.smtpSecurity === "starttls") {
        return {
            success: false,
            message: "Port 465 uses implicit TLS, not STARTTLS. Use smtpSecurity: 'tls' with port 465, or port 587 with 'starttls'.",
        };
    }
    // Test IMAP connectivity
    const imapError = await imap.testConnection(config, password);
    if (imapError) {
        return { success: false, message: imapError, providerNotes };
    }
    // Test SMTP connectivity
    const smtpError = await smtp.testConnection(config, password);
    if (smtpError) {
        return { success: false, message: smtpError, providerNotes };
    }
    // Both tests passed — persist credentials
    // Write graph node first (easier to clean up), then password file
    try {
        await writeConfig(accountId, config);
    }
    catch (err) {
        return {
            success: false,
            message: `IMAP and SMTP connections verified, but failed to store config: ${err instanceof Error ? err.message : String(err)}`,
        };
    }
    try {
        writePassword(password);
    }
    catch (err) {
        // Rollback graph node — best effort, don't mask the original error
        try {
            await removeConfig(accountId);
        }
        catch { /* rollback failed — next setup will MERGE over it */ }
        return {
            success: false,
            message: `IMAP and SMTP connections verified, but failed to store password file: ${err instanceof Error ? err.message : String(err)}`,
        };
    }
    // Set agent binding and poll config
    let bindingOk = true;
    try {
        await writeBindingConfig(accountId, agentSlug, params.pollIntervalMinutes ?? 5);
    }
    catch {
        bindingOk = false;
    }
    // Ensure email cron jobs are registered so polling starts immediately
    const cronResult = ensureEmailCrons(accountId);
    // Get inbox count as a confirmation signal
    let inboxCount;
    try {
        inboxCount = await imap.getInboxCount(config, password);
    }
    catch {
        // Non-fatal — setup still succeeded
    }
    const aliasNote = agentAddress !== email ? ` Agent address: ${agentAddress} (auth: ${email}).` : "";
    const pollNote = bindingOk
        ? ` Email polling enabled (every ${params.pollIntervalMinutes ?? 5} minutes, bound to ${agentSlug}).`
        : ` Email configured but polling binding could not be set — run email-setup again.`;
    const cronNote = cronResult.registered
        ? ""
        : ` Warning: email cron registration failed (${cronResult.error}). Crons will be registered on next platform update.`;
    return {
        success: true,
        message: `Email configured successfully for ${email}.${aliasNote} IMAP and SMTP connections verified.${inboxCount !== undefined ? ` Inbox has ${inboxCount} messages.` : ""}${pollNote}${cronNote}`,
        inboxCount,
        providerNotes,
    };
}
//# sourceMappingURL=email-setup.js.map