const fs = require("fs");
const path = require("path");
const { spawn, execSync } = require("child_process");
const http = require("http");
const { getCertParams } = require("./cert/generate");
const { addDNSEntry, removeDNSEntry, checkDNSEntry, checkAllDNSStatus, TOOL_HOSTS, removeAllDNSEntriesSync, isSudoAvailable, canRunSudoWithoutPassword, isSudoPasswordRequired } = require("./dns/dnsConfig");
const { installCert, uninstallCert, checkCertInstalled } = require("./cert/install");
const { MITM_DIR, DATA_DIR } = require("./paths");
const { log, err } = require("./logger");
const { isAdmin } = require("./winElevated.js");
const { LSOF_BIN } = require("./config.js");

const PID_FILE = path.join(MITM_DIR, "mitm.pid");
const SERVER_FILE_SRC = path.join(__dirname, "server.js");
const IS_WIN = process.platform === "win32";
const IS_MAC = process.platform === "darwin";

let activeServerProcess = null;
let healthCheckInterval = null;

// ==========================================
// 1. Password & Encryption Management
// ==========================================

function getEncryptionKey() {
  const machineIdSync = require("node-machine-id").machineIdSync;
  return machineIdSync();
}

function encryptPassword(password) {
  if (!password) return null;
  const crypto = require("crypto");
  const iv = crypto.randomBytes(16);
  const cipher = crypto.createCipheriv("aes-256-cbc", Buffer.from(getEncryptionKey().substring(0, 32).padEnd(32, "0")), iv);
  let encrypted = cipher.update(password, "utf8", "hex");
  encrypted += cipher.final("hex");
  return `${iv.toString("hex")}:${encrypted}`;
}

function decryptPassword(encryptedStr) {
  if (!encryptedStr) return null;
  try {
    const crypto = require("crypto");
    const [ivHex, encryptedHex] = encryptedStr.split(":");
    const iv = Buffer.from(ivHex, "hex");
    const decipher = crypto.createDecipheriv("aes-256-cbc", Buffer.from(getEncryptionKey().substring(0, 32).padEnd(32, "0")), iv);
    let decrypted = decipher.update(encryptedHex, "hex", "utf8");
    decrypted += decipher.final("utf8");
    return decrypted;
  } catch (e) {
    return null;
  }
}

function getStoredPassword() {
  const dbPath = path.join(DATA_DIR, "db.json");
  try {
    if (!fs.existsSync(dbPath)) return null;
    const db = JSON.parse(fs.readFileSync(dbPath, "utf8"));
    return decryptPassword(db.mitmSudoPassword);
  } catch (e) {
    return null;
  }
}

function clearEncryptedPassword() {
  const dbPath = path.join(DATA_DIR, "db.json");
  try {
    if (!fs.existsSync(dbPath)) return;
    const db = JSON.parse(fs.readFileSync(dbPath, "utf8"));
    if (db.mitmSudoPassword) {
      delete db.mitmSudoPassword;
      fs.writeFileSync(dbPath, JSON.stringify(db, null, 2), "utf8");
      log("🔑 Cleared invalid sudo password from storage");
    }
  } catch (e) {
    err(`Failed to clear sudo password: ${e.message}`);
  }
}

// ==========================================
// 2. DNS State Persistence (Runtime)
// ==========================================

const DNS_STATE_FILE = path.join(MITM_DIR, "dns-state.json");

function saveDnsToolState(tool, enabled) {
  try {
    let state = {};
    if (fs.existsSync(DNS_STATE_FILE)) {
      state = JSON.parse(fs.readFileSync(DNS_STATE_FILE, "utf8"));
    }
    state[tool] = enabled;
    fs.writeFileSync(DNS_STATE_FILE, JSON.stringify(state, null, 2), "utf8");
  } catch (e) {
    err(`Failed to save DNS state for ${tool}: ${e.message}`);
  }
}

function loadDnsToolState() {
  try {
    if (fs.existsSync(DNS_STATE_FILE)) {
      return JSON.parse(fs.readFileSync(DNS_STATE_FILE, "utf8"));
    }
  } catch (e) {
    err(`Failed to load DNS state: ${e.message}`);
  }
  return {};
}

// Check if we have the privileges needed to mutate DNS/Cert (Admin on Win, or sudo available on Unix)
function hasDnsPrivilege() {
  if (IS_WIN) return isAdmin();
  if (!isSudoAvailable()) return false;
  return canRunSudoWithoutPassword() || !!getStoredPassword();
}

/**
 * Called on startup to restore previously active DNS intercepts
 */
async function restoreToolDNS(sudoPassword) {
  if (!hasDnsPrivilege()) {
    log("🌐 DNS Restore: Skipped (no privilege / no password available)");
    return;
  }
  
  const state = loadDnsToolState();
  const pwd = sudoPassword || getStoredPassword();
  
  for (const [tool, enabled] of Object.entries(state)) {
    if (enabled && TOOL_HOSTS[tool]) {
      try {
        await addDNSEntry(tool, pwd);
        log(`🌐 DNS Restore: Re-enabled ${tool}`);
      } catch (e) {
        err(`🌐 DNS Restore: Failed to enable ${tool} - ${e.message}`);
      }
    }
  }
}

// ==========================================
// 3. Process & Port Management
// ==========================================

/**
 * Determine what process is currently listening on port 443.
 * Returns process name or 'unknown' if not found.
 */
function getPort443Owner() {
  try {
    if (IS_WIN) {
      const netstat = execSync("netstat -ano | findstr :443", { encoding: "utf8", windowsHide: true });
      const line = netstat.split("\n").find(l => l.includes("LISTENING"));
      if (!line) return null;
      const pid = line.trim().split(/\s+/).pop();
      if (!pid) return null;
      const tasklist = execSync(`tasklist /fi "pid eq ${pid}" /fo csv /nh`, { encoding: "utf8", windowsHide: true });
      return tasklist.split(",")[0].replace(/"/g, "") || "unknown";
    } else {
      if (LSOF_BIN) {
        const out = execSync(`${LSOF_BIN} -nP -iTCP:443 -sTCP:LISTEN -t`, { encoding: "utf8", windowsHide: true }).trim();
        if (out) {
          const pid = out.split("\n")[0];
          return execSync(`ps -p ${pid} -o comm=`, { encoding: "utf8", windowsHide: true }).trim() || "unknown";
        }
      }
      return null;
    }
  } catch {
    return null; // Usually means nothing is listening or we lack permission
  }
}

function getMitmStatus() {
  const isRunning = fs.existsSync(PID_FILE) && isProcessRunning(parseInt(fs.readFileSync(PID_FILE, "utf8"), 10));
  
  // Health check recovery logic (Arouter specific feature)
  if (!isRunning && activeServerProcess) {
    try {
      const owner = getPort443Owner();
      if (owner && owner.includes("node")) {
        // Double check health endpoint
        return new Promise((resolve) => {
          http.get("http://localhost:20128/health", (res) => {
            if (res.statusCode === 200) {
              log("Recovered MITM status via health check");
              resolve({ running: true, port: 443, hasCert: true, dns: checkAllDNSStatus() });
            } else {
              resolve({ running: false, port: 443, hasCert: false, dns: checkAllDNSStatus() });
            }
          }).on("error", () => {
            resolve({ running: false, port: 443, hasCert: false, dns: checkAllDNSStatus() });
          });
        });
      }
    } catch (e) {
      // Ignore
    }
  }

  const dns = checkAllDNSStatus();
  let hasCert = false;
  try {
    const certParams = getCertParams();
    hasCert = !!(certParams && certParams.cert);
  } catch (e) {}

  return Promise.resolve({ running: isRunning, port: 443, hasCert, dns });
}

function isProcessRunning(pid) {
  if (!pid) return false;
  try {
    process.kill(pid, 0);
    return true;
  } catch (e) {
    return false;
  }
}

async function killProcess(pid) {
  if (!pid) return;
  try {
    if (IS_WIN) {
      execSync(`taskkill /PID ${pid} /F /T`, { stdio: "ignore", windowsHide: true });
    } else {
      process.kill(pid, "SIGTERM");
      // Wait a bit for graceful shutdown
      await new Promise(resolve => setTimeout(resolve, 1000));
      if (isProcessRunning(pid)) {
        process.kill(pid, "SIGKILL");
      }
    }
  } catch (e) {
    err(`[Manager] Failed to kill PID ${pid}: ${e.message}`);
  }
}

// ==========================================
// 4. Server Lifecycle
// ==========================================

function shellQuoteSingle(str) {
  if (!str) return "''";
  return `'${str.replace(/'/g, "'\\''")}'`;
}

/**
 * Copy server.js to a runtime directory to avoid EBUSY locks during app updates
 */
function ensureRuntimeServer() {
  const runtimeDir = path.join(DATA_DIR, "runtime", "mitm");
  fs.mkdirSync(runtimeDir, { recursive: true });
  const runtimeServerFile = path.join(runtimeDir, "server.js");
  
  // Copy if doesn't exist or is different (compare mtime or size, but simple overwrite is safer)
  try {
    const srcStat = fs.statSync(SERVER_FILE_SRC);
    let destStat = null;
    try { destStat = fs.statSync(runtimeServerFile); } catch (e) {}
    
    if (!destStat || srcStat.mtimeMs > destStat.mtimeMs) {
      fs.copyFileSync(SERVER_FILE_SRC, runtimeServerFile);
      log("✅ Copied server.js to runtime directory");
    }
  } catch (e) {
    err(`Failed to copy runtime server: ${e.message}`);
    return SERVER_FILE_SRC; // Fallback to source
  }
  
  return runtimeServerFile;
}

/**
 * Figure out the correct backend router URL for the MITM server to forward to.
 * Priority: Configured in db.json > Env var > Default localhost.
 */
function resolveMitmRouterBaseUrl() {
  let routerBase = process.env.MITM_ROUTER_BASE || "http://localhost:20128";
  
  const dbPath = path.join(DATA_DIR, "db.json");
  try {
    if (fs.existsSync(dbPath)) {
      const db = JSON.parse(fs.readFileSync(dbPath, "utf8"));
      if (db.mitmRouterBaseUrl) {
        routerBase = db.mitmRouterBaseUrl;
      }
    }
  } catch (e) {
    // Ignore db read errors
  }
  return routerBase.replace(/\/+$/, ""); // Strip trailing slashes
}

async function startServer(apiKey, sudoPassword, forceKillPort443 = false) {
  const status = await getMitmStatus();

  // If server is running but we lost tracking of the process, adopt it
  if (status.running && !activeServerProcess && fs.existsSync(PID_FILE)) {
    log("Server already running, adopting process...");
    return { success: true };
  }

  // Pre-flight check: is port 443 available?
  const owner = getPort443Owner();
  if (owner && owner !== "unknown") {
    if (owner.includes("node") || owner.includes(process.title)) {
      log(`Port 443 owned by another node process, killing...`);
      // Note: server.js also attempts to kill lingering listeners on start.
    } else if (!forceKillPort443) {
      throw new Error(`Port 443 is currently in use by: ${owner}. MITM server cannot start.`);
    }
  }

  // 1. Generate & Install Cert
  const certParams = getCertParams();
  const pwd = sudoPassword || getStoredPassword();
  
  try {
    await installCert(pwd, certParams.certPath);
  } catch (e) {
    if (e.message.includes("password") || e.message.includes("canceled")) {
      clearEncryptedPassword();
    }
    throw e;
  }

  // 2. Export system-wide CA cert for Node/Electron (macOS/Windows)
  if (IS_MAC) {
    try {
      execSync(`launchctl setenv NODE_EXTRA_CA_CERTS ${shellQuoteSingle(certParams.certPath)}`);
    } catch (e) { err(`Failed to set NODE_EXTRA_CA_CERTS via launchctl: ${e.message}`); }
  } else if (IS_WIN) {
    try {
      execSync(`setx NODE_EXTRA_CA_CERTS "${certParams.certPath}"`, { windowsHide: true });
    } catch (e) { err(`Failed to set NODE_EXTRA_CA_CERTS via setx: ${e.message}`); }
  }

  // 3. Prepare config for server
  const routerBaseUrl = resolveMitmRouterBaseUrl();
  const env = {
    ...process.env,
    ROUTER_API_KEY: apiKey || process.env.ROUTER_API_KEY || "",
    MITM_ROUTER_BASE: routerBaseUrl,
  };

  const serverFile = ensureRuntimeServer();

  // 4. Start Server
  log(`Starting MITM server... (Target: ${routerBaseUrl})`);
  const child = spawn("node", [serverFile], {
    detached: true,
    stdio: "ignore",
    env,
    windowsHide: true,
  });

  child.unref();
  fs.writeFileSync(PID_FILE, child.pid.toString());
  activeServerProcess = child;

  log(`✅ MITM Server started (PID: ${child.pid})`);

  // Wait for health endpoint to be ready (up to 5s)
  return new Promise((resolve, reject) => {
    let attempts = 0;
    const check = setInterval(() => {
      attempts++;
      http.get("http://localhost:20128/health", (res) => {
        if (res.statusCode === 200) {
          clearInterval(check);
          resolve({ success: true, pid: child.pid });
        }
      }).on("error", () => {
        if (attempts > 10) {
          clearInterval(check);
          reject(new Error("Server failed to start in time"));
        }
      });
    }, 500);
  });
}

async function stopServer() {
  // 1. Cleanup DNS & Cert env (synchronously before process dies)
  try {
    if (isAdmin() || canRunSudoWithoutPassword() || !!getStoredPassword()) {
      removeAllDNSEntriesSync(); // Force synchronous cleanup
    }
    if (IS_MAC) {
      try { execSync("launchctl unsetenv NODE_EXTRA_CA_CERTS"); } catch (e) {}
    }
  } catch (e) {
    err(`Cleanup error during stop: ${e.message}`);
  }

  // 2. Kill the process
  if (activeServerProcess) {
    await killProcess(activeServerProcess.pid);
    activeServerProcess = null;
  }
  
  if (fs.existsSync(PID_FILE)) {
    const pid = parseInt(fs.readFileSync(PID_FILE, "utf8"), 10);
    await killProcess(pid);
    fs.unlinkSync(PID_FILE);
  }

  if (healthCheckInterval) {
    clearInterval(healthCheckInterval);
    healthCheckInterval = null;
  }

  log("🛑 MITM Server stopped");
  return { success: true };
}

// ==========================================
// 5. Tool Management (DNS toggle)
// ==========================================

async function toggleTool(tool, enable, sudoPassword) {
  const pwd = sudoPassword || getStoredPassword();
  try {
    if (enable) {
      await addDNSEntry(tool, pwd);
    } else {
      await removeDNSEntry(tool, pwd);
    }
    saveDnsToolState(tool, enable);
  } catch (e) {
    if (e.message.includes("password") || e.message.includes("canceled")) {
      clearEncryptedPassword();
    }
    throw e;
  }
}

// ==========================================
// 6. SSE Logging (Arouter Specific)
// ==========================================

/**
 * Connect to MITM server's SSE endpoint and stream logs via our logger
 */
function connectMitmLogStream() {
  const req = http.request("http://localhost:20128/_mitm_logs", (res) => {
    if (res.statusCode !== 200) return;
    
    res.on('data', (chunk) => {
      const lines = chunk.toString().split('\n');
      lines.forEach(line => {
        if (line.startsWith('data: ')) {
          try {
            const parsed = JSON.parse(line.slice(6));
            if (parsed.type === 'log') {
              log(parsed.message);
            } else if (parsed.type === 'error') {
              err(parsed.message);
            }
          } catch (e) {}
        }
      });
    });
  });
  
  req.on('error', () => {
    // Retry silently
    setTimeout(connectMitmLogStream, 2000);
  });
  
  req.end();
}

// Auto-connect to log stream on module load (if server is running)
connectMitmLogStream();

// Legacy aliases for backward compatibility with frontend/Next.js
const startMitm = startServer;
const stopMitm = stopServer;
const initDbHooks = () => {};
const loadEncryptedPassword = async () => getStoredPassword();

module.exports = {
  getMitmStatus,
  startServer,
  stopServer,
  toggleTool,
  getCertParams,
  encryptPassword,
  uninstallCert: async (pwd) => uninstallCert(pwd || getStoredPassword(), getCertParams().certPath),
  clearEncryptedPassword,
  isSudoPasswordRequired,
  restoreToolDNS,
  hasDnsPrivilege,
  removeAllDNSEntriesSync,
  // Legacy exports
  startMitm,
  stopMitm,
  initDbHooks,
  loadEncryptedPassword,
};
