import { spawnSync, execFileSync } from "node:child_process";
import { existsSync, mkdirSync, readFileSync, rmSync, appendFileSync, cpSync } from "node:fs";
import { resolve, join } from "node:path";
import { homedir } from "node:os";
import { createInterface } from "node:readline";
const HOME = homedir();
const PAYLOAD_DIR = resolve(import.meta.dirname, "../payload");
// Brand manifest — read from payload to derive brand-specific installation paths.
// No fallback: if brand.json is missing, the package is corrupted.
const BRAND_PATH = join(PAYLOAD_DIR, "platform", "config", "brand.json");
if (!existsSync(BRAND_PATH)) {
    console.error("FATAL: brand.json not found in payload. Package may be corrupted.");
    process.exit(1);
}
let BRAND;
try {
    BRAND = JSON.parse(readFileSync(BRAND_PATH, "utf-8"));
}
catch (err) {
    console.error(`FATAL: Failed to parse brand.json: ${err.message}`);
    process.exit(1);
}
const INSTALL_DIR = resolve(HOME, BRAND.installDir);
const CONFIG_DIR = resolve(HOME, BRAND.configDir);
const LOG_FILE = join("/tmp", `${BRAND.productName.toLowerCase().replace(/\s+/g, "-")}-uninstall-${new Date().toISOString().replace(/[:.]/g, "-")}.log`);
const TOTAL = "10";
// ---------------------------------------------------------------------------
// Logging — timestamped to console AND persistent log file in /tmp
// (Log lives in /tmp because the uninstall deletes the config directory)
// ---------------------------------------------------------------------------
function logFile(msg) {
    try {
        appendFileSync(LOG_FILE, `[${new Date().toISOString()}] ${msg}\n`);
    }
    catch { /* fs full */ }
}
const _log = console.log;
const _err = console.error;
console.log = (...args) => { _log(...args); logFile(args.map(String).join(" ")); };
console.error = (...args) => { _err(...args); logFile(`[ERROR] ${args.map(String).join(" ")}`); };
function log(step, message) {
    console.log(`[${step}/${TOTAL}] ${message}`);
}
// ---------------------------------------------------------------------------
// Shell helpers (duplicated from index.ts — intentionally not shared)
// ---------------------------------------------------------------------------
function shell(command, args, options) {
    const cmd = options?.sudo ? "sudo" : command;
    const cmdArgs = options?.sudo ? [command, ...args] : args;
    const start = Date.now();
    logFile(`> ${cmd} ${cmdArgs.join(" ")}`);
    const result = spawnSync(cmd, cmdArgs, {
        stdio: "inherit",
        timeout: options?.timeout ?? 120_000,
    });
    const dur = ((Date.now() - start) / 1000).toFixed(1);
    if (result.status !== 0) {
        const reason = result.signal ? `signal ${result.signal}` : `exit ${result.status}`;
        logFile(`  FAILED (${reason}) after ${dur}s`);
        throw new Error(`${cmd} ${cmdArgs.slice(0, 3).join(" ")} failed (${reason})`);
    }
    logFile(`  OK in ${dur}s`);
}
function isLinux() {
    return process.platform === "linux";
}
function commandExists(cmd) {
    try {
        execFileSync("which", [cmd], { stdio: "pipe" });
        return true;
    }
    catch {
        return false;
    }
}
// ---------------------------------------------------------------------------
// Detection
// ---------------------------------------------------------------------------
export function isMaxyInstalled() {
    return existsSync(INSTALL_DIR);
}
// ---------------------------------------------------------------------------
// Step 1: Stop services
// ---------------------------------------------------------------------------
function stopServices() {
    log("1", "Stopping services...");
    // Stop platform user service
    try {
        spawnSync("systemctl", ["--user", "stop", BRAND.serviceName.replace(".service", "")], { stdio: "pipe", timeout: 15_000 });
        console.log(`  Stopped ${BRAND.serviceName}`);
    }
    catch {
        console.log(`  ${BRAND.serviceName} not running`);
    }
    // Stop Neo4j
    try {
        spawnSync("sudo", ["systemctl", "stop", "neo4j"], { stdio: "pipe", timeout: 15_000 });
        console.log("  Stopped Neo4j");
    }
    catch {
        console.log("  Neo4j not running");
    }
    // Kill cloudflared (read PID from tunnel.state)
    const stateFile = resolve(HOME, ".cloudflared/tunnel.state");
    if (existsSync(stateFile)) {
        try {
            const state = JSON.parse(readFileSync(stateFile, "utf-8"));
            if (state.pid) {
                spawnSync("kill", [String(state.pid)], { stdio: "pipe" });
                console.log(`  Killed cloudflared (PID ${state.pid})`);
            }
        }
        catch {
            console.log("  cloudflared not running");
        }
    }
    // Kill VNC processes (Xtigervnc, websockify, Chromium)
    for (const proc of ["Xtigervnc", "websockify", "chromium"]) {
        try {
            spawnSync("pkill", ["-f", proc], { stdio: "pipe" });
        }
        catch { /* not running */ }
    }
    console.log("  Stopped VNC/browser processes");
    // Kill Ollama
    try {
        spawnSync("sudo", ["systemctl", "stop", "ollama"], { stdio: "pipe", timeout: 10_000 });
    }
    catch { /* not running */ }
    try {
        spawnSync("pkill", ["-f", "ollama"], { stdio: "pipe" });
    }
    catch { /* not running */ }
    console.log("  Stopped Ollama");
}
// ---------------------------------------------------------------------------
// Step 2: Delete Cloudflare tunnel
// ---------------------------------------------------------------------------
function deleteCloudflareTunnel() {
    log("2", "Deleting Cloudflare tunnel...");
    if (!commandExists("cloudflared")) {
        console.log("  cloudflared not installed — skipping tunnel deletion.");
        return;
    }
    const stateFile = resolve(HOME, ".cloudflared/tunnel.state");
    const certFile = resolve(HOME, ".cloudflared/cert.pem");
    // Read tunnel info for actionable error messages
    let tunnelDomain;
    let tunnelConfigPath;
    if (existsSync(stateFile)) {
        try {
            const state = JSON.parse(readFileSync(stateFile, "utf-8"));
            tunnelDomain = state.domain;
            tunnelConfigPath = state.configPath;
        }
        catch { /* corrupt state file */ }
    }
    if (!existsSync(certFile)) {
        console.log("  cert.pem missing — cannot delete tunnel from Cloudflare.");
        if (tunnelDomain) {
            console.log(`  The tunnel for ${tunnelDomain} may still exist on Cloudflare's edge.`);
            console.log("  Delete it manually at https://one.dash.cloudflare.com → Networks → Tunnels");
        }
        return;
    }
    // List tunnels to find the one to delete
    const listResult = spawnSync("cloudflared", ["tunnel", "list", "--output", "json"], {
        stdio: "pipe",
        encoding: "utf-8",
        timeout: 30_000,
    });
    if (listResult.status !== 0) {
        console.log("  Could not list tunnels — network may be unreachable.");
        if (tunnelDomain) {
            console.log(`  The tunnel for ${tunnelDomain} may still exist.`);
            console.log("  Delete it manually at https://one.dash.cloudflare.com → Networks → Tunnels");
        }
        return;
    }
    let tunnels = [];
    try {
        tunnels = JSON.parse(listResult.stdout || "[]");
    }
    catch { /* malformed response */ }
    if (tunnels.length === 0) {
        console.log("  No tunnels found — nothing to delete.");
        return;
    }
    // Delete each tunnel (typically just one)
    for (const tunnel of tunnels) {
        console.log(`  Deleting tunnel: ${tunnel.name} (${tunnel.id})...`);
        const deleteResult = spawnSync("cloudflared", ["tunnel", "delete", "-f", tunnel.id], {
            stdio: "pipe",
            encoding: "utf-8",
            timeout: 30_000,
        });
        if (deleteResult.status === 0) {
            console.log(`  Deleted tunnel ${tunnel.name} and its DNS records.`);
        }
        else {
            console.log(`  Failed to delete tunnel ${tunnel.name}: ${(deleteResult.stderr || "").trim()}`);
            if (tunnelDomain) {
                console.log(`  Delete it manually at https://one.dash.cloudflare.com → Networks → Tunnels`);
            }
        }
    }
}
// ---------------------------------------------------------------------------
// Step 3: Data export
// ---------------------------------------------------------------------------
export function exportData(exportPath) {
    log("3", `Exporting data to ${exportPath}...`);
    mkdirSync(exportPath, { recursive: true });
    // Export Neo4j via neo4j-admin database dump (database is stopped at this point)
    if (commandExists("neo4j-admin")) {
        console.log("  Exporting Neo4j database...");
        try {
            shell("neo4j-admin", ["database", "dump", "neo4j", `--to-path=${exportPath}`], { sudo: true, timeout: 120_000 });
            console.log("  Neo4j database exported.");
        }
        catch (err) {
            console.log(`  Neo4j export failed: ${err instanceof Error ? err.message : String(err)}`);
            console.log("  Continuing without database export.");
        }
    }
    else {
        console.log("  neo4j-admin not found — skipping database export.");
    }
    // Copy account config
    const accountsDir = resolve(CONFIG_DIR, "accounts");
    if (existsSync(accountsDir)) {
        try {
            const dest = join(exportPath, "accounts");
            cpSync(accountsDir, dest, { recursive: true });
            console.log(`  Account config exported to ${dest}`);
        }
        catch (err) {
            console.log(`  Account config export failed: ${err instanceof Error ? err.message : String(err)}`);
        }
    }
    else {
        console.log(`  No account config found at ${accountsDir}`);
    }
    // Copy secrets (password, API key) so a fresh install could reuse them
    for (const file of [".neo4j-password", ".anthropic-api-key", ".admin-pin"]) {
        const src = resolve(CONFIG_DIR, file);
        if (existsSync(src)) {
            try {
                cpSync(src, join(exportPath, file));
                console.log(`  Exported ${file}`);
            }
            catch { /* permission error */ }
        }
    }
    console.log(`  Export complete: ${exportPath}`);
}
// ---------------------------------------------------------------------------
// Step 4: Remove application directories
// ---------------------------------------------------------------------------
function removeAppDirs() {
    log("4", "Removing application directories...");
    const dirs = [
        { path: INSTALL_DIR, label: `~/${BRAND.installDir}` },
        { path: CONFIG_DIR, label: `~/${BRAND.configDir}` },
        { path: resolve(HOME, ".cloudflared"), label: "~/.cloudflared" },
        { path: resolve(HOME, ".claude"), label: "~/.claude" },
        { path: resolve(HOME, ".ollama"), label: "~/.ollama" },
    ];
    for (const { path, label } of dirs) {
        if (existsSync(path)) {
            try {
                rmSync(path, { recursive: true, force: true });
                console.log(`  Removed ${label}`);
            }
            catch (err) {
                console.log(`  Failed to remove ${label}: ${err instanceof Error ? err.message : String(err)}`);
            }
        }
        else {
            console.log(`  ${label} not found — skipping`);
        }
    }
}
// ---------------------------------------------------------------------------
// Step 5: Remove Neo4j data
// ---------------------------------------------------------------------------
function removeNeo4jData() {
    log("5", "Removing Neo4j data...");
    const paths = [
        "/var/lib/neo4j/data",
        "/var/lib/neo4j/logs",
    ];
    for (const p of paths) {
        if (existsSync(p)) {
            try {
                shell("rm", ["-rf", p], { sudo: true });
                console.log(`  Removed ${p}`);
            }
            catch (err) {
                console.log(`  Failed to remove ${p}: ${err instanceof Error ? err.message : String(err)}`);
            }
        }
    }
}
// ---------------------------------------------------------------------------
// Step 6: Purge system packages
// ---------------------------------------------------------------------------
function purgeSystemPackages() {
    log("6", "Removing system packages...");
    if (!isLinux()) {
        console.log("  Not Linux — skipping package removal.");
        return;
    }
    const packages = [
        "neo4j",
        "openjdk-17-jre-headless",
        "openjdk-21-jre-headless",
        "tigervnc-standalone-server",
        "python3-websockify",
        "novnc",
        "cloudflared",
    ];
    // Check which are installed before removing
    const installed = packages.filter((pkg) => {
        const result = spawnSync("dpkg", ["-s", pkg], { stdio: "pipe" });
        return result.status === 0;
    });
    if (installed.length === 0) {
        console.log("  No Maxy-specific packages installed.");
        return;
    }
    console.log(`  Removing: ${installed.join(", ")}`);
    try {
        shell("apt-get", ["remove", "--purge", "-y", ...installed], { sudo: true, timeout: 120_000 });
        console.log("  Packages removed.");
    }
    catch (err) {
        console.log(`  Package removal failed: ${err instanceof Error ? err.message : String(err)}`);
        console.log(`  Try manually: sudo apt-get remove --purge -y ${installed.join(" ")}`);
    }
    // Autoremove unused dependencies
    try {
        shell("apt-get", ["autoremove", "-y"], { sudo: true, timeout: 60_000 });
    }
    catch { /* not critical */ }
}
// ---------------------------------------------------------------------------
// Step 7: Remove system config files
// ---------------------------------------------------------------------------
function removeSystemConfig() {
    log("7", "Removing system configuration...");
    if (!isLinux()) {
        console.log("  Not Linux — skipping.");
        return;
    }
    const files = [
        { path: `/etc/avahi/services/${BRAND.hostname}.service`, label: "Avahi mDNS service" },
        { path: `/etc/NetworkManager/conf.d/${BRAND.hostname}-no-powersave.conf`, label: "WiFi power save config" },
        { path: "/etc/apt/sources.list.d/neo4j.list", label: "Neo4j apt repository" },
        { path: "/usr/share/keyrings/neo4j.gpg", label: "Neo4j GPG key" },
    ];
    for (const { path, label } of files) {
        if (existsSync(path)) {
            try {
                shell("rm", ["-f", path], { sudo: true });
                console.log(`  Removed ${label}`);
            }
            catch (err) {
                console.log(`  Failed to remove ${label}: ${err instanceof Error ? err.message : String(err)}`);
            }
        }
    }
    // Restart avahi to pick up the removed service file
    try {
        spawnSync("sudo", ["systemctl", "restart", "avahi-daemon"], { stdio: "pipe", timeout: 10_000 });
    }
    catch { /* not critical */ }
}
// ---------------------------------------------------------------------------
// Step 8: Remove systemd service
// ---------------------------------------------------------------------------
function removeSystemdService() {
    log("8", "Removing systemd service...");
    // Disable the service
    const svcUnit = BRAND.serviceName.replace(".service", "");
    try {
        spawnSync("systemctl", ["--user", "disable", svcUnit], { stdio: "pipe" });
    }
    catch { /* already disabled or missing */ }
    // Remove service file
    const serviceFile = resolve(HOME, `.config/systemd/user/${BRAND.serviceName}`);
    if (existsSync(serviceFile)) {
        try {
            rmSync(serviceFile);
            console.log(`  Removed ${BRAND.serviceName}`);
        }
        catch (err) {
            console.log(`  Failed to remove service file: ${err instanceof Error ? err.message : String(err)}`);
        }
    }
    // Reload daemon
    try {
        spawnSync("systemctl", ["--user", "daemon-reload"], { stdio: "pipe" });
    }
    catch { /* not critical */ }
    // Disable linger
    try {
        const user = execFileSync("whoami", [], { encoding: "utf-8" }).trim();
        spawnSync("sudo", ["loginctl", "disable-linger", user], { stdio: "pipe" });
        console.log("  Disabled user lingering");
    }
    catch { /* not critical */ }
}
// ---------------------------------------------------------------------------
// Step 9: Remove Ollama
// ---------------------------------------------------------------------------
function removeOllama() {
    log("9", "Removing Ollama...");
    // Stop and disable the systemd service if it exists
    try {
        spawnSync("sudo", ["systemctl", "stop", "ollama"], { stdio: "pipe", timeout: 10_000 });
        spawnSync("sudo", ["systemctl", "disable", "ollama"], { stdio: "pipe" });
    }
    catch { /* not running or not installed as service */ }
    // Remove the systemd service file
    const serviceFile = "/etc/systemd/system/ollama.service";
    if (existsSync(serviceFile)) {
        try {
            shell("rm", ["-f", serviceFile], { sudo: true });
            spawnSync("sudo", ["systemctl", "daemon-reload"], { stdio: "pipe" });
            console.log("  Removed Ollama systemd service");
        }
        catch { /* not critical */ }
    }
    // Remove the binary
    const binary = "/usr/local/bin/ollama";
    if (existsSync(binary)) {
        try {
            shell("rm", ["-f", binary], { sudo: true });
            console.log("  Removed Ollama binary");
        }
        catch (err) {
            console.log(`  Failed to remove Ollama binary: ${err instanceof Error ? err.message : String(err)}`);
        }
    }
    else {
        console.log("  Ollama binary not found — already removed");
    }
    // Models directory (~/.ollama/) is removed in step 4 (removeAppDirs)
}
// ---------------------------------------------------------------------------
// Step 10: Restore hostname
// ---------------------------------------------------------------------------
function restoreHostname() {
    log("10", "Restoring hostname...");
    if (!isLinux()) {
        console.log("  Not Linux — skipping.");
        return;
    }
    try {
        const hostname = execFileSync("hostname", [], { encoding: "utf-8" }).trim();
        if (hostname !== BRAND.hostname) {
            console.log(`  Hostname is '${hostname}' — not '${BRAND.hostname}', nothing to restore.`);
            return;
        }
        // The installer doesn't save the original hostname, so we use a sensible default
        const fallback = "raspberrypi";
        shell("hostnamectl", ["set-hostname", fallback], { sudo: true });
        shell("sed", ["-i", `s/127\\.0\\.1\\.1.*$/127.0.1.1\\t${fallback}/`, "/etc/hosts"], { sudo: true });
        console.log(`  Hostname restored to '${fallback}'`);
    }
    catch (err) {
        console.log(`  Hostname restore failed: ${err instanceof Error ? err.message : String(err)}`);
    }
}
// ---------------------------------------------------------------------------
// Summary display
// ---------------------------------------------------------------------------
function showRemovalSummary() {
    console.log("");
    console.log("The following will be removed:");
    console.log("");
    console.log(`  Services:   ${BRAND.serviceName}, Neo4j, cloudflared, VNC, Ollama`);
    console.log(`  App dirs:   ~/${BRAND.installDir}/, ~/${BRAND.configDir}/, ~/.claude/, ~/.cloudflared/, ~/.ollama/`);
    console.log("  Database:   Neo4j data and transaction logs");
    console.log("  Packages:   neo4j, openjdk (17 or 21), tigervnc, websockify, novnc, cloudflared");
    console.log("  Config:     avahi service, WiFi power save, apt repos, GPG keys");
    console.log(`  Systemd:    ${BRAND.serviceName} unit file, user lingering`);
    console.log("  Ollama:     binary and models");
    console.log(`  Hostname:   restored from '${BRAND.hostname}' to 'raspberrypi'`);
    console.log("  Cloudflare: tunnel and DNS records deleted from Cloudflare's edge");
    console.log("");
    console.log("  Node.js and shared system utilities (curl, git, etc.) are NOT removed.");
    console.log("");
}
// ---------------------------------------------------------------------------
// Confirmation prompt
// ---------------------------------------------------------------------------
async function confirmUninstall() {
    const rl = createInterface({ input: process.stdin, output: process.stdout });
    return new Promise((resolve) => {
        rl.question("Type UNINSTALL to confirm: ", (answer) => {
            rl.close();
            resolve(answer.trim() === "UNINSTALL");
        });
    });
}
// ---------------------------------------------------------------------------
// Main entry point
// ---------------------------------------------------------------------------
export async function runUninstall(options) {
    const PKG_VERSION = JSON.parse(readFileSync(resolve(import.meta.dirname, "../package.json"), "utf-8")).version;
    console.log("================================================================");
    console.log(`  ${BRAND.productName} Uninstall  (create-maxy v${PKG_VERSION})`);
    console.log("================================================================");
    console.log(`  Log: ${LOG_FILE}`);
    console.log("");
    // Check if Maxy is installed
    if (!isMaxyInstalled()) {
        console.error(`${BRAND.productName} is not installed (~/${BRAND.installDir} not found). Nothing to uninstall.`);
        process.exit(1);
    }
    // Show what will be removed
    showRemovalSummary();
    // Confirmation gate
    if (!options.skipConfirm) {
        console.log("  This action is permanent. All data will be lost.");
        console.log("");
        const confirmed = await confirmUninstall();
        if (!confirmed) {
            console.log("");
            console.log("Uninstall cancelled.");
            process.exit(0);
        }
    }
    console.log("");
    // Run each step, collecting failures
    const failures = [];
    const steps = [
        { name: "Stop services", fn: stopServices },
        { name: "Delete Cloudflare tunnel", fn: deleteCloudflareTunnel },
        ...(options.exportPath
            ? [{ name: "Export data", fn: () => exportData(options.exportPath) }]
            : []),
        { name: "Remove application directories", fn: removeAppDirs },
        { name: "Remove Neo4j data", fn: removeNeo4jData },
        { name: "Purge system packages", fn: purgeSystemPackages },
        { name: "Remove system configuration", fn: removeSystemConfig },
        { name: "Remove systemd service", fn: removeSystemdService },
        { name: "Remove Ollama", fn: removeOllama },
        { name: "Restore hostname", fn: restoreHostname },
    ];
    for (const step of steps) {
        try {
            step.fn();
        }
        catch (err) {
            const msg = `${step.name}: ${err instanceof Error ? err.message : String(err)}`;
            failures.push(msg);
            console.error(`  FAILED: ${msg}`);
        }
        console.log("");
    }
    // Final summary
    console.log("================================================================");
    if (failures.length === 0) {
        console.log("");
        console.log(`  ${BRAND.productName} has been completely removed.`);
        console.log("");
        console.log("  To reinstall: npx -y @rubytech/create-maxy");
    }
    else {
        console.log("");
        console.log(`  Uninstall completed with ${failures.length} issue(s):`);
        console.log("");
        for (const f of failures) {
            console.log(`    - ${f}`);
        }
        console.log("");
        console.log("  Re-run the uninstall to retry, or fix manually.");
    }
    console.log("");
    console.log(`  Log: ${LOG_FILE}`);
    console.log("================================================================");
    if (failures.length > 0) {
        process.exit(1);
    }
}
