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,
  normalizeDeploymentBranch,
  readUpdateState,
  writeUpdateState
} = require("./update-repository");
const { getSetting, setSetting } = require("./settings");
const { compareSemver, parseSemver } = require("./versioning");
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(rootPath = repoRoot, expectedVersion = null) {
  for (const file of ["package.json", "src/main.js", "src/web/server.js"]) {
    if (!fs.existsSync(path.join(rootPath, file))) {
      throw new Error(`Core verification failed: ${file} is missing.`);
    }
  }
  const actualVersion = JSON.parse(fs.readFileSync(path.join(rootPath, "package.json"), "utf8")).version;
  if (expectedVersion && actualVersion !== expectedVersion) {
    throw new Error(`Core verification failed: requested ${expectedVersion}, but repository ref contains ${actualVersion || "no version"}.`);
  }
  return actualVersion;
}

function coreBranchMetadata(rootPath) {
  const version = verifyCoreFiles(rootPath);
  let manifest = {};
  for (const name of ["update-manifest.json", "lumi.manifest.json"]) {
    try {
      manifest = JSON.parse(fs.readFileSync(path.join(rootPath, name), "utf8"));
      break;
    } catch {
      // Branch deployments can proceed without optional release metadata.
    }
  }
  return {
    version,
    rollback_safe: manifest.rollback_safe !== false,
    migration_notes: String(manifest.migration_notes || ""),
    danger_notes: Array.isArray(manifest.dangers) ? manifest.dangers : []
  };
}

function bundledPluginIds(rootPath) {
  const pluginsPath = path.join(rootPath, "plugins");
  try {
    return fs.readdirSync(pluginsPath, { withFileTypes: true })
      .filter((entry) => entry.isDirectory() && fs.existsSync(path.join(pluginsPath, entry.name, "plugin.json")))
      .map((entry) => entry.name)
      .sort();
  } catch {
    return [];
  }
}

function verifyPluginFiles(pluginId, rootPath = repoRoot, expectedVersion = null) {
  const manifest = path.join(rootPath, "plugins", pluginId, "plugin.json");
  if (!fs.existsSync(manifest)) {
    throw new Error(`Plugin verification failed: ${pluginId}/plugin.json is missing.`);
  }
  const metadata = JSON.parse(fs.readFileSync(manifest, "utf8"));
  if (expectedVersion && metadata.version !== expectedVersion) {
    throw new Error(`Plugin verification failed: requested ${expectedVersion}, but repository ref contains ${metadata.version || "no version"}.`);
  }
  return metadata;
}

function targetForRequestedVersion(baseTarget, requestedVersion, label) {
  if (!requestedVersion) return baseTarget;
  const version = parseSemver(requestedVersion)?.raw;
  if (!version) throw new Error("Choose a valid version in x.y.z format.");
  const selected = (baseTarget.available_versions || []).find((item) => item.version === version);
  if (!selected) throw new Error(`${label} ${version} is not listed as an installable repository release.`);
  const comparison = compareSemver(version, baseTarget.current_version);
  const direction = comparison < 0 ? "downgrade" : comparison === 0 ? "repair reinstall" : "update";
  return {
    ...baseTarget,
    blocked: false,
    blocked_reason: null,
    update_available: true,
    safe_target_version: version,
    source_branch: selected.ref,
    raw_target: selected,
    rollback_safe: selected.rollback_safe !== false,
    migration_notes: selected.migration_notes || baseTarget.migration_notes || "",
    major_crossing: parseSemver(version)?.major !== parseSemver(baseTarget.current_version)?.major,
    requires_manual_confirmation: true,
    requested_version: version,
    version_description: `${baseTarget.current_version} -> ${version} (${direction})`,
    warnings: [
      ...(baseTarget.warnings || []),
      comparison < 0
        ? "You selected an older code version. Lumi data is preserved, but an older release may not understand newer database changes."
        : comparison === 0
          ? "This reinstalls the selected release to repair its managed files."
          : `You selected ${version} instead of the recommended target.`
    ]
  };
}

async function applyCoreUpdate({ source = "stable", remote = null, version = null, publish } = {}) {
  return withOperation("core", async () => {
    const status = getUpdateStatus({ source, remote });
    const target = targetForRequestedVersion(status.core, version, "Core version");
    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);
      verifyCoreFiles(managed.path, target.safe_target_version);
      emitProgress(publish, "update:apply", { target: "core" });
      applyCoreFilesFromDirectory(managed.path);
      updateRecoveryMarker({ status: "verifying" });
      emitProgress(publish, "update:verify", { target: "core" });
      verifyCoreFiles(repoRoot, target.safe_target_version);
      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_update_stage: "complete",
        last_error: null,
        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 deployCoreBranch({ branch, remote = null, publish } = {}) {
  return withOperation("core", async () => {
    const targetBranch = normalizeDeploymentBranch(branch);
    const configuredRemote = remote || getSetting("git_remote", "origin");
    const priorState = readUpdateState();
    const fromBranch = String(priorState.deployed_branch || getSetting("git_branch", "main") || "main");
    const fromCommit = priorState.deployed_commit || null;
    const currentVersion = verifyCoreFiles(repoRoot);
    let stage = "fetching and verifying the selected branch";
    let managed = null;
    let target = null;
    let marker = null;
    let snapshot = null;
    let snapshotRecord = null;
    try {
      emitProgress(publish, "update:queued", { target: "core", deployment_branch: targetBranch });
      emitProgress(publish, "update:checking", { target: "core", deployment_branch: targetBranch });
      managed = ensureManagedRepo(configuredRemote, targetBranch);
      target = coreBranchMetadata(managed.path);
      const targetPluginIds = bundledPluginIds(managed.path);
      for (const pluginId of targetPluginIds) verifyPluginFiles(pluginId, managed.path);
      const majorCrossing = parseSemver(currentVersion)?.major !== parseSemver(target.version)?.major;
      const deployment = {
        from_branch: fromBranch,
        from_commit: fromCommit,
        from_version: currentVersion,
        branch: managed.branch,
        commit: managed.commit,
        version: target.version,
        rollback_safe: target.rollback_safe,
        major_crossing: majorCrossing
      };
      emitProgress(publish, "update:metadata", { target: "core", deployment });

      stage = "preparing recovery";
      marker = createRecoveryMarker({
        target_kind: "core",
        target_id: "core",
        from_version: currentVersion,
        to_version: target.version,
        source_branch: managed.branch,
        update_method: "branch_deploy",
        rollback_safe: target.rollback_safe,
        major_crossing: majorCrossing
      });
      updateRecoveryMarker({ status: "applying" });

      stage = "creating the rollback snapshot";
      snapshot = await createSnapshot({
        type: "bot",
        metadata: {
          target_kind: "core",
          target_id: "core",
          from_version: currentVersion,
          to_version: target.version,
          from_source_branch: fromBranch,
          from_commit: fromCommit,
          source_branch: managed.branch,
          target_commit: managed.commit,
          update_method: "branch_deploy",
          rollback_safe: target.rollback_safe,
          recovery_marker_id: marker.id,
          major_crossing: majorCrossing,
          migration_notes: target.migration_notes,
          danger_notes: target.danger_notes,
          include_plugin_code: true,
          target_plugin_ids: targetPluginIds
        }
      });
      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: managed.branch, commit: managed.commit });

      stage = "replacing core files";
      emitProgress(publish, "update:apply", { target: "core", branch: managed.branch });
      applyCoreFilesFromDirectory(managed.path);
      for (const pluginId of targetPluginIds) {
        applyPluginFiles(path.join(managed.path, "plugins", pluginId), pluginId, { preserveData: true });
      }
      updateRecoveryMarker({ status: "verifying" });

      stage = "verifying the deployed branch";
      emitProgress(publish, "update:verify", { target: "core", branch: managed.branch });
      verifyCoreFiles(repoRoot, target.version);
      for (const pluginId of targetPluginIds) verifyPluginFiles(pluginId, repoRoot);
      syncPluginRegistry();
      setSetting("git_branch", managed.branch);
      markRecoveryMarkerComplete({ snapshot_id: snapshotRecord.id });
      writeUpdateState({
        remote: managed.repository,
        branch: managed.branch,
        commit: managed.commit,
        deployed_branch: managed.branch,
        deployed_commit: managed.commit,
        deployed_version: target.version,
        previous_deployed_branch: fromBranch,
        previous_deployed_commit: fromCommit,
        last_update_at: new Date().toISOString(),
        last_update_status: "complete",
        last_update_stage: "complete",
        last_error: null,
        last_snapshot_id: snapshotRecord.id,
        last_target_kind: "core",
        last_target_version: target.version,
        last_deployment_method: "branch_deploy"
      });
      emitProgress(publish, "update:restart_required", { target: "core", deployment });
      emitProgress(publish, "update:complete", { target: "core", snapshot_id: snapshotRecord.id, deployment });
      return { status: "complete", restart_required: true, snapshot: snapshotRecord, deployment };
    } catch (error) {
      const originalMessage = error?.message || String(error || "Unknown deployment error.");
      error.message = `Branch deployment failed while ${stage}: ${originalMessage}`;
      if (snapshotRecord) {
        try {
          restoreSnapshot(snapshotRecord.id, {
            expectedType: "bot",
            currentVersion: target?.version,
            allowUnsafeMajorRollback: true
          });
          setSetting("git_branch", fromBranch);
        } catch (restoreError) {
          error.message += ` Automatic restore also failed: ${restoreError.message}`;
        }
      } else if (snapshot) {
        try { discardSnapshot(snapshot); } catch {}
      }
      if (marker) markRecoveryMarkerFailed(error);
      writeUpdateState({
        last_update_at: new Date().toISOString(),
        last_update_status: "failed",
        last_update_stage: stage,
        last_target_kind: "core",
        last_target_version: target?.version || null,
        last_error: error.message
      });
      emitProgress(publish, "update:failed", { target: "core", deployment_branch: targetBranch, error: error.message });
      throw error;
    }
  });
}

async function applyPluginUpdateFromRepo(pluginId, { source = "stable", remote = null, version = null, publish } = {}) {
  return withOperation(`plugin:${pluginId}`, async () => {
    let status = null;
    let target = null;
    let marker = null;
    let managed = null;
    let snapshot = null;
    let snapshotRecord = null;
    let stage = "checking repository metadata";
    try {
      status = getUpdateStatus({ source, remote });
      const statusTarget = status.plugins.find((plugin) => plugin.id === pluginId);
      if (!statusTarget) throw new Error("Plugin was not found in the local or repository catalog.");
      target = targetForRequestedVersion(statusTarget, version, "Plugin version");
      if (target.blocked) throw new Error(target.blocked_reason || "Plugin update is blocked.");
      if (!target.update_available) throw new Error("No plugin update target is available.");
      emitProgress(publish, "update:queued", { target: "plugin", plugin_id: pluginId });
      emitProgress(publish, "update:metadata", target);

      stage = "preparing the selected repository version";
      managed = ensureManagedRepo(status.remote, target.source_branch);
      verifyPluginFiles(pluginId, managed.path, target.safe_target_version);

      stage = "preparing recovery";
      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
      });
      updateRecoveryMarker({ status: "applying" });

      stage = "creating the rollback snapshot";
      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 });
      stage = "replacing plugin files";
      applyPluginFiles(path.join(managed.path, "plugins", pluginId), pluginId, { preserveData: true });
      updateRecoveryMarker({ status: "verifying" });
      emitProgress(publish, "update:verify", { target: "plugin", plugin_id: pluginId });
      stage = "verifying the installed plugin";
      verifyPluginFiles(pluginId, repoRoot, target.safe_target_version);
      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_update_stage: "complete",
        last_error: null,
        last_snapshot_id: record.id,
        last_target_kind: "plugin",
        last_target_id: pluginId,
        last_target_version: target.safe_target_version
      });
      emitProgress(publish, "update:restart_required", { target: "plugin", plugin_id: pluginId });
      emitProgress(publish, "update:complete", { target: "plugin", plugin_id: pluginId, snapshot_id: record.id });
      return { status: "complete", restart_required: true, snapshot: record, target };
    } catch (error) {
      const originalMessage = error?.message || String(error || "Unknown update error.");
      error.message = `Plugin update failed while ${stage}: ${originalMessage}`;
      const recoveryDiagnostics = [];
      if (snapshotRecord) {
        try {
          restoreSnapshot(snapshotRecord.id, {
            expectedType: "plugin",
            expectedPluginId: pluginId,
            currentVersion: target.safe_target_version,
            allowUnsafeMajorRollback: true
          });
        } catch (restoreError) {
          recoveryDiagnostics.push(`automatic restore failed: ${restoreError.message}`);
        }
      } else if (snapshot) {
        try {
          discardSnapshot(snapshot);
        } catch (discardError) {
          recoveryDiagnostics.push(`incomplete snapshot cleanup failed: ${discardError.message}`);
        }
      }
      if (marker) {
        try {
          markRecoveryMarkerFailed(error);
        } catch (markerError) {
          recoveryDiagnostics.push(`recovery marker update failed: ${markerError.message}`);
        }
      }
      if (recoveryDiagnostics.length) error.message += ` Recovery diagnostics: ${recoveryDiagnostics.join("; ")}`;
      try {
        writeUpdateState({
          last_update_at: new Date().toISOString(),
          last_update_status: "failed",
          last_target_kind: "plugin",
          last_target_id: pluginId,
          last_target_version: target?.safe_target_version || null,
          last_update_stage: stage,
          last_error: error.message
        });
      } catch (stateError) {
        error.message += ` Update-state diagnostics could not be saved: ${stateError.message}`;
      }
      emitProgress(publish, "update:failed", { target: "plugin", plugin_id: pluginId, error: error.message });
      throw error;
    }
  });
}

async function revertCoreSnapshot(snapshotId, { currentVersion, publish } = {}) {
  return withOperation("core", async () => {
    emitProgress(publish, "update:revert", { target: "core", snapshot_id: snapshotId });
    const entry = restoreSnapshot(snapshotId, {
      expectedType: "bot",
      currentVersion
    });
    if (entry.update_method === "branch_deploy" && entry.from_source_branch) {
      setSetting("git_branch", entry.from_source_branch);
      writeUpdateState({
        deployed_branch: entry.from_source_branch,
        deployed_commit: entry.from_commit || null,
        deployed_version: entry.from_version || null,
        previous_deployed_branch: entry.source_branch || null,
        previous_deployed_commit: entry.target_commit || null,
        last_update_at: new Date().toISOString(),
        last_update_status: "complete",
        last_update_stage: "reverted",
        last_error: null,
        last_snapshot_id: entry.id,
        last_target_kind: "core",
        last_target_version: entry.from_version || null,
        last_deployment_method: "snapshot_revert"
      });
    }
    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,
  deployCoreBranch,
  applyPluginUpdateFromRepo,
  targetForRequestedVersion,
  revertCoreSnapshot,
  revertPluginSnapshot,
  disablePluginForRecovery
};
