/**
 * uninstall.ts — top-level `mosaic uninstall` command
 *
 * Flags:
 *   --framework   Remove ~/.config/mosaic/ (honor --keep-data for SOUL.md etc.)
 *   --cli         npm uninstall -g @mosaicstack/mosaic
 *   --gateway     Delegate to gateway/uninstall runUninstall
 *   --all         All three + runtime asset reversal
 *   --keep-data   Preserve memory/, SOUL.md, USER.md, TOOLS.md, gateway DB/storage
 *   --yes / -y    Skip confirmation (also: MOSAIC_ASSUME_YES=1)
 *   --dry-run     List what would be removed; mutate nothing
 *
 * Default (no category flag): interactive prompt per category.
 */

import { existsSync, readFileSync, writeFileSync, rmSync, readdirSync } from 'node:fs';
import { createInterface } from 'node:readline';
import { execSync } from 'node:child_process';
import { homedir } from 'node:os';
import { join, dirname } from 'node:path';
import type { Command } from 'commander';
import { DEFAULT_MOSAIC_HOME } from '../constants.js';
import {
  readManifest,
  heuristicRuntimeAssetDests,
  DEFAULT_SCOPE_LINE,
} from '../runtime/install-manifest.js';

// ─── types ───────────────────────────────────────────────────────────────────

interface UninstallOptions {
  framework: boolean;
  cli: boolean;
  gateway: boolean;
  all: boolean;
  keepData: boolean;
  yes: boolean;
  dryRun: boolean;
  mosaicHome: string;
}

// ─── protected data paths (relative to MOSAIC_HOME) ──────────────────────────

/** Paths inside MOSAIC_HOME that --keep-data protects. */
const KEEP_DATA_PATHS = ['SOUL.md', 'USER.md', 'TOOLS.md', 'memory', 'sources'];

// ─── public entry point ───────────────────────────────────────────────────────

export async function runTopLevelUninstall(opts: UninstallOptions): Promise<void> {
  const assume = opts.yes || process.env['MOSAIC_ASSUME_YES'] === '1';

  const doFramework = opts.all || opts.framework;
  const doCli = opts.all || opts.cli;
  const doGateway = opts.all || opts.gateway;
  const interactive = !doFramework && !doCli && !doGateway;

  if (opts.dryRun) {
    console.log('[dry-run] No changes will be made.\n');
  }

  const rl = createInterface({ input: process.stdin, output: process.stdout });
  const ask = (q: string): Promise<boolean> =>
    new Promise((resolve) => {
      if (assume) {
        console.log(`${q} [auto-yes]`);
        resolve(true);
        return;
      }
      rl.question(`${q} [y/N] `, (ans) => resolve(ans.trim().toLowerCase() === 'y'));
    });

  try {
    const shouldFramework = interactive
      ? await ask('Uninstall Mosaic framework (~/.config/mosaic)?')
      : doFramework;
    const shouldCli = interactive
      ? await ask('Uninstall @mosaicstack/mosaic CLI (npm global)?')
      : doCli;
    const shouldGateway = interactive ? await ask('Uninstall Mosaic Gateway?') : doGateway;

    if (!shouldFramework && !shouldCli && !shouldGateway) {
      console.log('Nothing to uninstall. Exiting.');
      return;
    }

    // 1. Gateway
    if (shouldGateway) {
      await uninstallGateway(opts.dryRun);
    }

    // 2. Runtime assets (reverse linked files) — always run when framework removal
    if (shouldFramework) {
      reverseRuntimeAssets(opts.mosaicHome, opts.dryRun);
    }

    // 3. npmrc scope line
    if (shouldCli || shouldFramework) {
      reverseNpmrc(opts.mosaicHome, opts.dryRun);
    }

    // 4. Framework directory
    if (shouldFramework) {
      removeFramework(opts.mosaicHome, opts.keepData, opts.dryRun);
    }

    // 5. CLI npm package
    if (shouldCli) {
      removeCli(opts.dryRun);
    }

    if (!opts.dryRun) {
      console.log('\nUninstall complete.');
    } else {
      console.log('\n[dry-run] No changes made.');
    }
  } finally {
    rl.close();
  }
}

// ─── step: gateway ────────────────────────────────────────────────────────────

async function uninstallGateway(dryRun: boolean): Promise<void> {
  console.log('\n[gateway] Delegating to gateway uninstaller…');
  if (dryRun) {
    console.log('[dry-run] Would call gateway/uninstall runUninstall()');
    return;
  }
  try {
    const { runUninstall } = await import('./gateway/uninstall.js');
    await runUninstall();
  } catch (err) {
    console.warn(
      `  Warning: gateway uninstall failed — ${err instanceof Error ? err.message : String(err)}`,
    );
  }
}

// ─── step: reverse runtime assets ────────────────────────────────────────────

/**
 * Reverse all runtime asset copies made by mosaic-link-runtime-assets:
 *   - If a .mosaic-bak-* backup exists → restore it
 *   - Else if the managed copy exists → remove it
 */
export function reverseRuntimeAssets(mosaicHome: string, dryRun: boolean): void {
  const home = homedir();
  const manifest = readManifest(mosaicHome);

  let copies: Array<{ dest: string; backup?: string }>;

  if (manifest) {
    copies = manifest.mutations.runtimeAssetCopies;
  } else {
    // Heuristic mode
    console.warn('  Warning: no install manifest found — using heuristic mode for runtime assets.');
    copies = heuristicRuntimeAssetDests(home).map((dest) => ({ dest }));
  }

  for (const entry of copies) {
    const dest = entry.dest;
    const backupFromManifest = entry.backup;

    // Resolve backup: manifest may have one, or scan for pattern
    const backup = backupFromManifest ?? findLatestBackup(dest);

    if (backup && existsSync(backup)) {
      if (dryRun) {
        console.log(`[dry-run] Would restore backup: ${backup} → ${dest}`);
      } else {
        try {
          const content = readFileSync(backup);
          writeFileSync(dest, content);
          rmSync(backup, { force: true });
          console.log(`  Restored: ${dest}`);
        } catch (err) {
          console.warn(
            `  Warning: could not restore ${dest}: ${err instanceof Error ? err.message : String(err)}`,
          );
        }
      }
    } else if (existsSync(dest)) {
      if (dryRun) {
        console.log(`[dry-run] Would remove managed copy: ${dest}`);
      } else {
        try {
          rmSync(dest, { force: true });
          console.log(`  Removed: ${dest}`);
        } catch (err) {
          console.warn(
            `  Warning: could not remove ${dest}: ${err instanceof Error ? err.message : String(err)}`,
          );
        }
      }
    }
  }
}

/**
 * Scan the directory of `filePath` for the most recent `.mosaic-bak-*` backup.
 */
function findLatestBackup(filePath: string): string | undefined {
  const dir = dirname(filePath);
  const base = filePath.split('/').at(-1) ?? '';
  if (!existsSync(dir)) return undefined;

  let entries: string[];
  try {
    entries = readdirSync(dir);
  } catch {
    return undefined;
  }

  const backups = entries
    .filter((e) => e.startsWith(`${base}.mosaic-bak-`))
    .sort()
    .reverse(); // most recent first (timestamp suffix)

  return backups.length > 0 ? join(dir, backups[0]!) : undefined;
}

// ─── step: reverse npmrc ──────────────────────────────────────────────────────

/**
 * Remove the @mosaicstack:registry line added by tools/install.sh.
 * Only removes the exact line; never touches anything else.
 */
export function reverseNpmrc(mosaicHome: string, dryRun: boolean): void {
  const npmrcPath = join(homedir(), '.npmrc');
  if (!existsSync(npmrcPath)) return;

  const manifest = readManifest(mosaicHome);
  const linesToRemove: string[] =
    manifest?.mutations.npmrcLines && manifest.mutations.npmrcLines.length > 0
      ? manifest.mutations.npmrcLines
      : [DEFAULT_SCOPE_LINE];

  let content: string;
  try {
    content = readFileSync(npmrcPath, 'utf8');
  } catch {
    return;
  }

  const lines = content.split('\n');
  const filtered = lines.filter((l) => !linesToRemove.includes(l.trimEnd()));

  if (filtered.length === lines.length) {
    // Nothing to remove
    return;
  }

  if (dryRun) {
    for (const line of linesToRemove) {
      if (lines.some((l) => l.trimEnd() === line)) {
        console.log(`[dry-run] Would remove from ~/.npmrc: ${line}`);
      }
    }
    return;
  }

  try {
    writeFileSync(npmrcPath, filtered.join('\n'), 'utf8');
    console.log('  Removed @mosaicstack registry from ~/.npmrc');
  } catch (err) {
    console.warn(
      `  Warning: could not update ~/.npmrc: ${err instanceof Error ? err.message : String(err)}`,
    );
  }
}

// ─── step: remove framework directory ────────────────────────────────────────

export function removeFramework(mosaicHome: string, keepData: boolean, dryRun: boolean): void {
  if (!existsSync(mosaicHome)) {
    console.log(`  Framework directory not found: ${mosaicHome}`);
    return;
  }

  if (!keepData) {
    if (dryRun) {
      console.log(`[dry-run] Would remove: ${mosaicHome} (entire directory)`);
    } else {
      rmSync(mosaicHome, { recursive: true, force: true });
      console.log(`  Removed: ${mosaicHome}`);
    }
    return;
  }

  // --keep-data: remove everything except protected paths
  const entries = readdirSync(mosaicHome);
  for (const entry of entries) {
    if (KEEP_DATA_PATHS.some((p) => entry === p)) {
      continue; // protected
    }
    const full = join(mosaicHome, entry);
    if (dryRun) {
      console.log(`[dry-run] Would remove: ${full}`);
    } else {
      try {
        rmSync(full, { recursive: true, force: true });
        console.log(`  Removed: ${full}`);
      } catch (err) {
        console.warn(
          `  Warning: could not remove ${full}: ${err instanceof Error ? err.message : String(err)}`,
        );
      }
    }
  }
  if (!dryRun) {
    console.log(`  Framework removed (preserved: ${KEEP_DATA_PATHS.join(', ')})`);
  }
}

// ─── step: remove CLI npm package ────────────────────────────────────────────

export function removeCli(dryRun: boolean): void {
  if (dryRun) {
    console.log('[dry-run] Would run: npm uninstall -g @mosaicstack/mosaic');
    return;
  }

  console.log('  Uninstalling @mosaicstack/mosaic (npm global)…');
  try {
    execSync('npm uninstall -g @mosaicstack/mosaic', { stdio: 'inherit' });
    console.log('  CLI uninstalled.');
  } catch (err) {
    console.warn(
      `  Warning: npm uninstall failed — ${err instanceof Error ? err.message : String(err)}`,
    );
    console.warn('  You may need to run: npm uninstall -g @mosaicstack/mosaic');
  }
}

// ─── commander registration ───────────────────────────────────────────────────

export function registerUninstallCommand(program: Command): void {
  program
    .command('uninstall')
    .description('Uninstall Mosaic (framework, CLI, and/or gateway)')
    .option('--framework', 'Remove ~/.config/mosaic/ framework directory')
    .option('--cli', 'Uninstall @mosaicstack/mosaic npm global package')
    .option('--gateway', 'Uninstall the Mosaic Gateway (delegates to gateway uninstaller)')
    .option('--all', 'Uninstall everything (framework + CLI + gateway + runtime asset reversal)')
    .option(
      '--keep-data',
      'Preserve user data: memory/, SOUL.md, USER.md, TOOLS.md, gateway DB/storage',
    )
    .option('--yes, -y', 'Skip confirmation prompts (also: MOSAIC_ASSUME_YES=1)')
    .option('--dry-run', 'List what would be removed without making any changes')
    .option(
      '--mosaic-home <path>',
      'Override MOSAIC_HOME directory',
      process.env['MOSAIC_HOME'] ?? DEFAULT_MOSAIC_HOME,
    )
    .action(
      async (opts: {
        framework?: boolean;
        cli?: boolean;
        gateway?: boolean;
        all?: boolean;
        keepData?: boolean;
        yes?: boolean;
        dryRun?: boolean;
        mosaicHome: string;
      }) => {
        await runTopLevelUninstall({
          framework: opts.framework ?? false,
          cli: opts.cli ?? false,
          gateway: opts.gateway ?? false,
          all: opts.all ?? false,
          keepData: opts.keepData ?? false,
          yes: opts.yes ?? false,
          dryRun: opts.dryRun ?? false,
          mosaicHome: opts.mosaicHome,
        });
      },
    );
}
