const path = require("path");
const fs = require("fs");
const { syncPluginRegistry, setPluginEnabled } = require("./plugins");
const {
  createSnapshot,
  finalizeSnapshot,
  discardSnapshot,
  restoreSnapshot,
  applyCoreFilesFromDirectory,
  applyPluginFiles
} = require("./update-manager");
const { getUpdateStatus } = require("./update-index");
const {
  ensureManagedRepo,
  writeUpdateState
} = require("./update-repository");
const {
  createRecoveryMarker,
  updateRecoveryMarker,
  markRecoveryMarkerFailed,
  markRecoveryMarkerComplete
} = require("./recovery-mode");

const repoRoot = path.join(__dirname, "..", "..");
const activeOperations = new Set();

function withOperation(key, fn) {
  if (activeOperations.has(key)) {
    throw new Error("An update is already running for this target.");
  }
  activeOperations.add(key);
  return Promise.resolve()
    .then(fn)
    .finally(() => activeOperations.delete(key));
}

function emitProgress(publish, event, payload) {
  if (typeof publish === "function") {
    publish(event, payload, { role: "admin" });
  }
}

function verifyCoreFiles() {
  for (const file of ["package.json", "src/main.js", "src/web/server.js"]) {
    if (!fs.existsSync(path.join(repoRoot, file))) {
      throw new Error(`Core verification failed: ${file} is missing.`);
    }
  }
}

function verifyPluginFiles(pluginId) {
  const manifest = path.join(repoRoot, "plugins", pluginId, "plugin.json");
  if (!fs.existsSync(manifest)) {
    throw new Error(`Plugin verification failed: ${pluginId}/plugin.json is missing.`);
  }
  JSON.parse(fs.readFileSync(manifest, "utf8"));
}

function applyPluginFromRepositorySnapshot(remote, branch, pluginId) {
  const managed = ensureManagedRepo(remote, branch);
  const pluginRoot = path.join(managed.path, "plugins", pluginId);
  if (!fs.existsSync(path.join(pluginRoot, "plugin.json"))) {
    throw new Error(`Plugin ${pluginId} was not found in ${branch}.`);
  }
  applyPluginFiles(pluginRoot, pluginId, { preserveData: true });
  return managed;
}

async function applyCoreUpdate({ source = "stable", remote = null, publish } = {}) {
  return withOperation("core", async () => {
    const status = getUpdateStatus({ source, remote });
    const target = status.core;
    if (target.blocked) throw new Error(target.blocked_reason || "Core update is blocked.");
    if (!target.update_available) throw new Error("No safe core update target is available.");
    const marker = createRecoveryMarker({
      target_kind: "core",
      target_id: "core",
      from_version: target.current_version,
      to_version: target.safe_target_version,
      source_branch: target.source_branch,
      update_method: "git",
      rollback_safe: target.rollback_safe,
      major_crossing: target.major_crossing
    });
    let snapshot = null;
    let snapshotRecord = null;
    try {
      emitProgress(publish, "update:queued", { target: "core" });
      emitProgress(publish, "update:checking", { target: "core" });
      emitProgress(publish, "update:metadata", target);
      updateRecoveryMarker({ status: "applying" });
      snapshot = await createSnapshot({
        type: "bot",
        metadata: {
          target_kind: "core",
          target_id: "core",
          from_version: target.current_version,
          to_version: target.safe_target_version,
          source_branch: target.source_branch,
          update_method: "git",
          rollback_safe: target.rollback_safe,
          recovery_marker_id: marker.id,
          major_crossing: target.major_crossing,
          migration_notes: target.migration_notes,
          danger_notes: target.dangers
        }
      });
      snapshotRecord = finalizeSnapshot(snapshot);
      emitProgress(publish, "update:snapshot", { target: "core", snapshot_id: snapshotRecord.id });
      emitProgress(publish, "update:recovery_marker", { target: "core", marker_id: marker.id });
      emitProgress(publish, "update:download", { target: "core", branch: target.source_branch });
      const managed = ensureManagedRepo(status.remote, target.source_branch);
      emitProgress(publish, "update:apply", { target: "core" });
      applyCoreFilesFromDirectory(managed.path);
      updateRecoveryMarker({ status: "verifying" });
      emitProgress(publish, "update:verify", { target: "core" });
      verifyCoreFiles();
      const record = snapshotRecord;
      markRecoveryMarkerComplete({ snapshot_id: record.id });
      writeUpdateState({
        remote: managed.repository,
        branch: managed.branch,
        last_update_at: new Date().toISOString(),
        last_update_status: "complete",
        last_snapshot_id: record.id,
        last_target_kind: "core",
        last_target_version: target.safe_target_version
      });
      emitProgress(publish, "update:restart_required", { target: "core" });
      emitProgress(publish, "update:complete", { target: "core", snapshot_id: record.id });
      return { status: "complete", restart_required: true, snapshot: record, target };
    } catch (error) {
      if (snapshotRecord) {
        try {
          restoreSnapshot(snapshotRecord.id, {
            expectedType: "bot",
            currentVersion: target.safe_target_version,
            allowUnsafeMajorRollback: true
          });
        } catch (restoreError) {
          markRecoveryMarkerFailed(restoreError);
        }
      } else if (snapshot) {
        try {
          discardSnapshot(snapshot);
        } catch {
          // Ignore cleanup failures.
        }
      }
      markRecoveryMarkerFailed(error);
      writeUpdateState({
        last_update_at: new Date().toISOString(),
        last_update_status: "failed",
        last_target_kind: "core",
        last_error: error.message
      });
      emitProgress(publish, "update:failed", { target: "core", error: error.message });
      throw error;
    }
  });
}

async function applyPluginUpdateFromRepo(pluginId, { source = "stable", remote = null, publish } = {}) {
  return withOperation(`plugin:${pluginId}`, async () => {
    const status = getUpdateStatus({ source, remote });
    const target = status.plugins.find((plugin) => plugin.id === pluginId);
    if (!target) throw new Error("Plugin is not installed.");
    if (target.blocked) throw new Error(target.blocked_reason || "Plugin update is blocked.");
    if (!target.update_available) throw new Error("No safe plugin update target is available.");
    const marker = createRecoveryMarker({
      target_kind: "plugin",
      target_id: pluginId,
      from_version: target.current_version,
      to_version: target.safe_target_version,
      source_branch: target.source_branch,
      update_method: target.installed === false ? "git_install" : "git",
      rollback_safe: target.rollback_safe,
      major_crossing: target.major_crossing
    });
    let snapshot = null;
    let snapshotRecord = null;
    try {
      emitProgress(publish, "update:queued", { target: "plugin", plugin_id: pluginId });
      emitProgress(publish, "update:metadata", target);
      updateRecoveryMarker({ status: "applying" });
      snapshot = await createSnapshot({
        type: "plugin",
        pluginId,
        metadata: {
          target_kind: "plugin",
          target_id: pluginId,
          from_version: target.current_version,
          to_version: target.safe_target_version,
          source_branch: target.source_branch,
          update_method: target.installed === false ? "git_install" : "git",
          rollback_safe: target.rollback_safe,
          recovery_marker_id: marker.id,
          major_crossing: target.major_crossing,
          migration_notes: target.migration_notes,
          danger_notes: target.dangers
        }
      });
      snapshotRecord = finalizeSnapshot(snapshot);
      emitProgress(publish, "update:snapshot", { target: "plugin", plugin_id: pluginId, snapshot_id: snapshotRecord.id });
      emitProgress(publish, "update:download", { target: "plugin", plugin_id: pluginId, branch: target.source_branch });
      emitProgress(publish, "update:apply", { target: "plugin", plugin_id: pluginId });
      const managed = applyPluginFromRepositorySnapshot(status.remote, target.source_branch, pluginId);
      updateRecoveryMarker({ status: "verifying" });
      emitProgress(publish, "update:verify", { target: "plugin", plugin_id: pluginId });
      verifyPluginFiles(pluginId);
      syncPluginRegistry();
      const record = snapshotRecord;
      markRecoveryMarkerComplete({ snapshot_id: record.id });
      writeUpdateState({
        remote: managed.repository,
        branch: managed.branch,
        last_update_at: new Date().toISOString(),
        last_update_status: "complete",
        last_snapshot_id: record.id,
        last_target_kind: "plugin",
        last_target_id: pluginId,
        last_target_version: target.safe_target_version
      });
      emitProgress(publish, "update:complete", { target: "plugin", plugin_id: pluginId, snapshot_id: record.id });
      return { status: "complete", restart_required: false, snapshot: record, target };
    } catch (error) {
      if (snapshotRecord) {
        try {
          restoreSnapshot(snapshotRecord.id, {
            expectedType: "plugin",
            expectedPluginId: pluginId,
            currentVersion: target.safe_target_version,
            allowUnsafeMajorRollback: true
          });
        } catch (restoreError) {
          markRecoveryMarkerFailed(restoreError);
        }
      } else if (snapshot) {
        try {
          discardSnapshot(snapshot);
        } catch {
          // Ignore cleanup failures.
        }
      }
      markRecoveryMarkerFailed(error);
      writeUpdateState({
        last_update_at: new Date().toISOString(),
        last_update_status: "failed",
        last_target_kind: "plugin",
        last_target_id: pluginId,
        last_error: error.message
      });
      emitProgress(publish, "update:failed", { target: "plugin", plugin_id: pluginId, error: error.message });
      throw error;
    }
  });
}

async function revertCoreSnapshot(snapshotId, { currentVersion, publish } = {}) {
  return withOperation("core:revert", async () => {
    emitProgress(publish, "update:revert", { target: "core", snapshot_id: snapshotId });
    const entry = restoreSnapshot(snapshotId, {
      expectedType: "bot",
      currentVersion
    });
    return { status: "reverted", restart_required: true, snapshot: entry };
  });
}

async function revertPluginSnapshot(pluginId, snapshotId, { currentVersion, publish } = {}) {
  return withOperation(`plugin:${pluginId}:revert`, async () => {
    emitProgress(publish, "update:revert", { target: "plugin", plugin_id: pluginId, snapshot_id: snapshotId });
    const entry = restoreSnapshot(snapshotId, {
      expectedType: "plugin",
      expectedPluginId: pluginId,
      currentVersion
    });
    syncPluginRegistry();
    return { status: "reverted", restart_required: true, snapshot: entry };
  });
}

function disablePluginForRecovery(pluginId, publish) {
  setPluginEnabled(pluginId, false);
  emitProgress(publish, "recovery:plugin_disabled", { plugin_id: pluginId });
  return { status: "disabled", plugin_id: pluginId };
}

module.exports = {
  applyCoreUpdate,
  applyPluginUpdateFromRepo,
  revertCoreSnapshot,
  revertPluginSnapshot,
  disablePluginForRecovery
};
