'use strict';

const fs = require('fs');
const os = require('os');
const path = require('path');
const crypto = require('crypto');
const { spawn } = require('child_process');
const { classifySqlitePathRisk } = require('../../shared/sqlite-storage-policy');

const CONTROL_DIR = path.join(os.homedir(), '.walle', 'control');
const LOCK_FILE = 'storage-migration.lock';
const JOURNAL_PREFIX = 'storage-migration-';
const ACTIVE_JOURNAL = 'storage-migration-current.json';
const STALE_LOCK_MS = 15 * 60 * 1000;
const HEARTBEAT_STALE_MS = 2 * 60 * 1000;

function normalizeUserPath(value) {
  const raw = String(value || '').trim().replace(/[\r\n\0]/g, '');
  if (!raw) return '';
  const expanded = raw === '~'
    ? os.homedir()
    : raw.replace(/^~(?=\/|$)/, os.homedir() || '~');
  return path.resolve(expanded);
}

function ensureDir(dir) {
  fs.mkdirSync(dir, { recursive: true });
  const stat = fs.statSync(dir);
  if (!stat.isDirectory()) throw new Error(`Not a directory: ${dir}`);
  return dir;
}

function safeReadJson(file, fallback = null) {
  try { return JSON.parse(fs.readFileSync(file, 'utf8')); } catch { return fallback; }
}

function atomicWriteJson(file, data) {
  ensureDir(path.dirname(file));
  const tmp = `${file}.${process.pid}.${Date.now()}.tmp`;
  fs.writeFileSync(tmp, JSON.stringify(data, null, 2) + '\n', { mode: 0o600 });
  fs.renameSync(tmp, file);
}

function migrationPaths(env = process.env) {
  const root = normalizeUserPath(env.CTM_STORAGE_CONTROL_DIR || CONTROL_DIR);
  return {
    controlDir: root,
    lockPath: path.join(root, LOCK_FILE),
    activeJournalPath: path.join(root, ACTIVE_JOURNAL),
  };
}

function isProcessAlive(pid) {
  const n = Number(pid);
  if (!Number.isInteger(n) || n <= 0) return false;
  try {
    process.kill(n, 0);
    return true;
  } catch {
    return false;
  }
}

function readActiveMigration(env = process.env, nowMs = Date.now()) {
  const paths = migrationPaths(env);
  const lock = safeReadJson(paths.lockPath, null);
  const journal = safeReadJson(paths.activeJournalPath, null);
  if (!lock && !journal) return { active: false, paths };
  const state = String(lock?.state || journal?.state || '').toLowerCase();
  const pid = Number(lock?.pid || journal?.supervisor_pid || 0);
  const heartbeatAt = Date.parse(lock?.heartbeat_at || journal?.heartbeat_at || lock?.updated_at || journal?.updated_at || 0);
  const startedAt = Date.parse(lock?.started_at || journal?.started_at || 0);
  const pidAlive = isProcessAlive(pid);
  const heartbeatFresh = Number.isFinite(heartbeatAt) && (nowMs - heartbeatAt) < HEARTBEAT_STALE_MS;
  const recentlyStarted = Number.isFinite(startedAt) && (nowMs - startedAt) < STALE_LOCK_MS;
  const terminal = ['completed', 'reverted', 'failed'].includes(state);
  const allowStart = lock?.allow_start === true || journal?.allow_start === true;
  const active = !terminal && !allowStart && pidAlive && (heartbeatFresh || recentlyStarted);
  return { active, stale: !terminal && !allowStart && !active, terminal, lock, journal, paths, pidAlive, heartbeatFresh, state, allow_start: allowStart };
}

function startupGuard(label, env = process.env) {
  const current = readActiveMigration(env);
  if (!current.active) return { ok: true, active: false, stale: current.stale || false };
  const err = new Error(`${label || 'process'} paused for active storage migration`);
  err.code = 'CTM_STORAGE_MIGRATION_ACTIVE';
  err.migration = current;
  return { ok: false, active: true, error: err };
}

function serviceCurrentFromDb(dbModule) {
  const dbPath = dbModule.getDbPath ? dbModule.getDbPath() : '';
  const backupInfo = dbModule.getBackupDirInfo ? dbModule.getBackupDirInfo() : { backup_dir: dbModule.getBackupDir?.() || '' };
  return {
    data_dir: dbPath ? path.dirname(dbPath) : '',
    db_path: dbPath || '',
    db_file: 'task-manager.db',
    backup_dir: backupInfo.backup_dir || '',
    default_backup_dir: backupInfo.default_backup_dir || '',
    configured_backup_dir: backupInfo.configured_backup_dir || '',
    backup_dir_source: backupInfo.source || 'default',
  };
}

function normalizeServiceInput(raw = {}, current = {}) {
  const dataDir = normalizeUserPath(raw.data_dir || raw.dataDir || current.data_dir);
  const backupDir = normalizeUserPath(raw.backup_dir || raw.backupDir || current.backup_dir);
  return {
    data_dir: dataDir,
    backup_dir: backupDir,
    move_backups: raw.move_backups !== false && raw.moveBackups !== false,
  };
}

function _riskForDir(dir) {
  try {
    const risk = classifySqlitePathRisk(path.join(dir, 'probe.db'), process.env);
    return risk && risk.risky ? { level: risk.level || 'warn', reason: risk.reason || risk.message || 'risky storage' } : null;
  } catch {
    return null;
  }
}

function buildStorageMigrationPlan(input = {}, deps = {}) {
  const env = deps.env || process.env;
  const ctmCurrent = deps.ctmCurrent || serviceCurrentFromDb(deps.dbModule || {});
  const walleCurrent = deps.walleCurrent || {};
  const requested = input.services || {};
  const services = {};
  const warnings = [];
  const now = new Date();
  const id = `storage-${now.toISOString().replace(/[:.]/g, '-')}-${crypto.randomBytes(4).toString('hex')}`;

  for (const [name, dbFile] of [['ctm', 'task-manager.db'], ['walle', 'wall-e-brain.db']]) {
    const current = name === 'ctm' ? ctmCurrent : walleCurrent;
    const next = normalizeServiceInput(requested[name] || {}, current);
    const currentDataDir = normalizeUserPath(current.data_dir || (current.db_path ? path.dirname(current.db_path) : ''));
    const currentBackupDir = normalizeUserPath(current.backup_dir || '');
    const dataDirChanged = !!next.data_dir && !!currentDataDir && path.resolve(next.data_dir) !== path.resolve(currentDataDir);
    const backupDirChanged = !!next.backup_dir && !!currentBackupDir && path.resolve(next.backup_dir) !== path.resolve(currentBackupDir);
    const service = {
      name,
      label: name === 'ctm' ? 'CTM' : 'Wall-E',
      db_file: dbFile,
      current_data_dir: currentDataDir,
      target_data_dir: next.data_dir || currentDataDir,
      current_db_path: current.db_path || path.join(currentDataDir, dbFile),
      target_db_path: path.join(next.data_dir || currentDataDir, dbFile),
      current_backup_dir: currentBackupDir,
      target_backup_dir: next.backup_dir || currentBackupDir,
      data_dir_changed: dataDirChanged,
      backup_dir_changed: backupDirChanged,
      move_backups: !!next.move_backups,
    };
    const risk = service.target_data_dir ? _riskForDir(service.target_data_dir) : null;
    if (risk) {
      warnings.push({
        service: name,
        type: 'sqlite_storage',
        message: `${service.label} live SQLite target may be unsafe: ${risk.reason}`,
      });
    }
    services[name] = service;
  }

  const dataDirChanges = Object.values(services).filter(s => s.data_dir_changed);
  const backupChanges = Object.values(services).filter(s => s.backup_dir_changed);
  return {
    id,
    created_at: now.toISOString(),
    state: 'planned',
    control_dir: normalizeUserPath(env.CTM_STORAGE_CONTROL_DIR || CONTROL_DIR),
    ctm_pid: Number(env.CTM_PID || process.pid),
    services,
    requires_restart: dataDirChanges.length > 0,
    data_dir_change_count: dataDirChanges.length,
    backup_dir_change_count: backupChanges.length,
    warnings,
    steps: [
      ...(backupChanges.length && !dataDirChanges.length ? ['move_backup_snapshots'] : []),
      ...(dataDirChanges.length ? [
        'write_journal',
        'stop_wall_e',
        'stop_ctm',
        'copy_live_data',
        'verify_sqlite_images',
        ...(backupChanges.length ? ['copy_backup_snapshots'] : []),
        'update_env',
        'restart_ctm',
        'restart_wall_e',
      ] : []),
    ],
  };
}

function writeMigrationPlan(plan, env = process.env) {
  const paths = migrationPaths(env);
  ensureDir(paths.controlDir);
  const journalPath = path.join(paths.controlDir, `${JOURNAL_PREFIX}${plan.id}.json`);
  const withPaths = { ...plan, journal_path: journalPath };
  atomicWriteJson(journalPath, withPaths);
  atomicWriteJson(paths.activeJournalPath, withPaths);
  return { ...withPaths, journal_path: journalPath, active_journal_path: paths.activeJournalPath };
}

function spawnStorageMigrationSupervisor(plan, opts = {}) {
  const written = writeMigrationPlan(plan, opts.env || process.env);
  const script = path.resolve(__dirname, '..', 'bin', 'storage-migration-supervisor.js');
  const args = ['--journal', written.journal_path];
  if (opts.dryRun) args.push('--dry-run');
  const child = spawn(process.execPath, [script, ...args], {
    detached: true,
    stdio: 'ignore',
    env: { ...process.env, ...(opts.env || {}) },
  });
  child.unref();
  return { ok: true, id: written.id, pid: child.pid, journal_path: written.journal_path };
}

module.exports = {
  CONTROL_DIR,
  LOCK_FILE,
  STALE_LOCK_MS,
  HEARTBEAT_STALE_MS,
  normalizeUserPath,
  ensureDir,
  migrationPaths,
  readActiveMigration,
  startupGuard,
  buildStorageMigrationPlan,
  writeMigrationPlan,
  spawnStorageMigrationSupervisor,
  atomicWriteJson,
  safeReadJson,
};
