"use strict";

// ── Update modal ──────────────────────────────────────────────────

async function openUpdateModal() {
  if (!$modal) return;

  // Reattach before checking for new updates. This makes a browser reload,
  // RDP reconnect, or suspended tab recover the authoritative systemd-backed
  // state instead of starting over or claiming the system is merely up to date.
  try {
    var current = await apiFetchWithTimeout(
      "/api/updates/status?offset=0",
      { cache: "no-store" },
      STATUS_POLL_FETCH_TIMEOUT
    );
    if (current.running || current.result === "reboot_required") {
      showExistingUpdate(current);
      return;
    }
  } catch (_) {
    // The normal start path below has its own visible error handling.
  }

  apiFetchWithTimeout(
    "/api/updates/check",
    { cache: "no-store" },
    STATUS_POLL_FETCH_TIMEOUT
  )
    .then(function(data) {
      if (!data.available) {
        stopUpdatePoll();
        _updateLog = "";
        _updateLogOffset = 0;
        _updateVisibleLogChars = 0;
        _updateFinished = true;
        _updateStatusUnavailable = false;
        if ($modalLog) $modalLog.textContent = "";
        if ($modalStatus) $modalStatus.textContent = "✓ System is already up to date";
        if ($modalSpinner) $modalSpinner.classList.remove("spinning");
        if ($btnReboot) $btnReboot.style.display = "none";
        if ($btnSave) $btnSave.style.display = "none";
        if ($btnRetryUpdate) $btnRetryUpdate.style.display = "none";
        if ($btnCloseModal) $btnCloseModal.disabled = false;
        $modal.classList.add("open");
        return;
      }
      _doOpenUpdateModal();
    })
    .catch(function() {
      _doOpenUpdateModal();
    });
}

function prepareUpdateModal() {
  if (!$modal) return;
  stopUpdatePoll();
  _updateLog = "";
  _updateLogOffset = 0;
  _updateVisibleLogChars = 0;
  _updatePollInFlight = false;
  _serverWasDown = false;
  _updateFinished = false;
  _updateStatusUnavailable = false;
  _updatePollFailures = 0;
  if ($modalLog) $modalLog.textContent = "";
  if ($modalStatus) $modalStatus.textContent = "Starting update…";
  if ($modalSpinner) $modalSpinner.classList.add("spinning");
  if ($btnReboot) $btnReboot.style.display = "none";
  if ($btnSave) $btnSave.style.display = "none";
  if ($btnRetryUpdate) $btnRetryUpdate.style.display = "none";
  if ($btnCloseModal) $btnCloseModal.disabled = true;
  $modal.classList.add("open");
}

function _doOpenUpdateModal() {
  prepareUpdateModal();
  startUpdate();
}

function showExistingUpdate(data) {
  prepareUpdateModal();
  if (data.log) appendLog(data.log);
  _updateLogOffset = Number(data.offset) || 0;

  if (data.running) {
    if ($modalStatus) $modalStatus.textContent = "Updating…";
    startUpdatePoll();
    return;
  }

  _updateFinished = true;
  if (data.result === "reboot_required") {
    onUpdateDone("reboot_required");
  } else if (data.result === "success") {
    onUpdateDone(true);
  } else {
    onUpdateDone(false);
  }
}

async function restoreUpdateModalIfNeeded() {
  if (!$modal || $modal.classList.contains("open")) return;
  try {
    var data = await apiFetchWithTimeout(
      "/api/updates/status?offset=0",
      { cache: "no-store" },
      STATUS_POLL_FETCH_TIMEOUT
    );
    if (data.running || data.result === "reboot_required") {
      showExistingUpdate(data);
    }
  } catch (_) {
    // Dashboard startup must remain usable when status cannot be reached.
  }
}

function closeUpdateModal() {
  if (!$modal) return;
  $modal.classList.remove("open");
  stopUpdatePoll();
}

function appendLog(text) {
  if (!text) return;
  _updateLog += text;
  if ($modalLog) {
    // Appending a text node avoids reparsing/replacing the complete log on
    // every two-second poll. Trim only occasionally once the visible log is
    // large; the complete _updateLog remains available for error reports.
    if (_updateVisibleLogChars + text.length > UPDATE_VISIBLE_LOG_MAX_CHARS) {
      var tail = _updateLog.slice(-UPDATE_VISIBLE_LOG_TRIM_CHARS);
      var notice = "[Earlier update output hidden from this view; it remains in the saved report.]\n\n";
      $modalLog.textContent = notice + tail;
      _updateVisibleLogChars = notice.length + tail.length;
    } else {
      $modalLog.appendChild(document.createTextNode(text));
      _updateVisibleLogChars += text.length;
    }
    $modalLog.scrollTop = $modalLog.scrollHeight;
  }
}

function startUpdate() {
  apiFetchWithTimeout(
    "/api/updates/run",
    { method: "POST" },
    STATUS_POLL_FETCH_TIMEOUT * 2
  )
    .then(function(data) {
      if (data.status === "no_updates") {
        if ($modalStatus) $modalStatus.textContent = "✓ System is already up to date";
        if ($modalSpinner) $modalSpinner.classList.remove("spinning");
        if ($btnReboot) $btnReboot.style.display = "none";
        if ($btnSave) $btnSave.style.display = "none";
        if ($btnRetryUpdate) $btnRetryUpdate.style.display = "none";
        if ($btnCloseModal) $btnCloseModal.disabled = false;
        _updateFinished = true;
        return;
      }
      if (data.status === "already_running") appendLog("[Update already in progress, attaching…]\n\n");
      if ($modalStatus) $modalStatus.textContent = "Updating…";
      startUpdatePoll();
    })
    .catch(function(err) {
      appendLog("[Error: failed to start update — " + err + "]\n");
      onUpdateDone(false);
    });
}

function startUpdatePoll() {
  if (_updatePollTimer) clearInterval(_updatePollTimer);
  pollUpdateStatus();
  _updatePollTimer = setInterval(pollUpdateStatus, UPDATE_POLL_INTERVAL);
}

function stopUpdatePoll() {
  if (_updatePollTimer) { clearInterval(_updatePollTimer); _updatePollTimer = null; }
}

async function pollUpdateStatus() {
  // setInterval does not wait for an async callback. The guard prevents a slow
  // request from creating overlapping, out-of-order status polls.
  if (_updateFinished || _updatePollInFlight) return;
  _updatePollInFlight = true;
  try {
    var data = await apiFetchWithTimeout(
      "/api/updates/status?offset=" + _updateLogOffset,
      { cache: "no-store" },
      STATUS_POLL_FETCH_TIMEOUT
    );
    _updatePollFailures = 0;
    if (_serverWasDown) {
      _serverWasDown = false;
      if (!data.running) {
        // The update finished while the server or browser connection was away.
        // Re-fetch from offset 0 so the final result and complete tail agree.
        _updateLog = "";
        _updateLogOffset = 0;
        _updateVisibleLogChars = 0;
        if ($modalLog) $modalLog.textContent = "";
        try {
          var fullData = await apiFetchWithTimeout(
            "/api/updates/status?offset=0",
            { cache: "no-store" },
            STATUS_POLL_FETCH_TIMEOUT
          );
          if (fullData.log) appendLog(fullData.log);
          _updateLogOffset = fullData.offset;
          data = fullData;
        } catch (_) {
          if (data.log) appendLog(data.log);
          _updateLogOffset = data.offset;
        }
        if (data.result === "reboot_required") {
          appendLog("[Reconnected — update completed, reboot required.]\n");
        } else if (data.result === "success") {
          appendLog("[Reconnected — update completed successfully.]\n");
        } else {
          appendLog("[Reconnected — update encountered an error.]\n");
        }
        _updateFinished = true;
        stopUpdatePoll();
        if (data.result === "reboot_required") {
          onUpdateDone("reboot_required");
        } else {
          onUpdateDone(data.result === "success");
        }
        return;
      }
      appendLog("[Update status reconnected]\n");
      if ($modalStatus) $modalStatus.textContent = "Updating…";
    }
    if (data.log) appendLog(data.log);
    _updateLogOffset = data.offset;
    if (data.running) return;
    _updateFinished = true;
    stopUpdatePoll();
    if (data.result === "reboot_required") {
      onUpdateDone("reboot_required");
    } else if (data.result === "success") {
      onUpdateDone(true);
    } else {
      onUpdateDone(false);
    }
  } catch (err) {
    _updatePollFailures += 1;
    if (_updatePollFailures >= STATUS_POLL_MAX_FAILURES) {
      showUpdateStatusUnavailable();
      return;
    }
    if (!_serverWasDown) {
      _serverWasDown = true;
      appendLog("\n[Update status connection interrupted — retrying…]\n");
      if ($modalStatus) $modalStatus.textContent = "Reconnecting to update…";
    }
  } finally {
    _updatePollInFlight = false;
  }
}

function showUpdateStatusUnavailable() {
  _updateFinished = true;
  _updateStatusUnavailable = true;
  stopUpdatePoll();
  if ($modalSpinner) $modalSpinner.classList.remove("spinning");
  if ($modalStatus) $modalStatus.textContent = "Update status unavailable — update may still be running";
  appendLog("\n[The Hub could not confirm update status. The background update was not stopped. Select Retry Status after reconnecting.]\n");
  if ($btnRetryUpdate) $btnRetryUpdate.style.display = "inline-flex";
  if ($btnCloseModal) $btnCloseModal.disabled = false;
}

function retryUpdateStatus() {
  if (!$modal) return;
  _updateFinished = false;
  _updateStatusUnavailable = false;
  _updatePollFailures = 0;
  _serverWasDown = true;
  if ($modalSpinner) $modalSpinner.classList.add("spinning");
  if ($modalStatus) $modalStatus.textContent = "Reconnecting to update…";
  if ($btnRetryUpdate) $btnRetryUpdate.style.display = "none";
  if ($btnCloseModal) $btnCloseModal.disabled = true;
  startUpdatePoll();
}

function resumeUpdateStatusAfterInterruption() {
  if (!$modal || !$modal.classList.contains("open")) return;
  if (_updateStatusUnavailable) {
    retryUpdateStatus();
  } else if (!_updateFinished) {
    pollUpdateStatus();
  }
}

function onUpdateDone(result) {
  _updateStatusUnavailable = false;
  if ($modalSpinner) $modalSpinner.classList.remove("spinning");
  if ($btnRetryUpdate) $btnRetryUpdate.style.display = "none";
  if ($btnCloseModal) $btnCloseModal.disabled = false;
  if (result === true) {
    if ($modalStatus) $modalStatus.textContent = "✓ Update complete";
    if ($btnReboot) $btnReboot.style.display = "inline-flex";
  } else if (result === "reboot_required") {
    if ($modalStatus) $modalStatus.textContent = "✓ Update complete — restart required";
    if ($btnReboot) $btnReboot.style.display = "inline-flex";
  } else {
    if ($modalStatus) $modalStatus.textContent = "✗ Update failed";
    if ($btnSave) $btnSave.style.display = "inline-flex";
    if ($btnReboot) $btnReboot.style.display = "inline-flex";
  }
}

function saveErrorReport() {
  var blob = new Blob([_updateLog], { type: "text/plain" });
  var url = URL.createObjectURL(blob);
  var a = document.createElement("a");
  a.href = url;
  a.download = "sovran-update-error-" + new Date().toISOString().split(".")[0].replace(/:/g, "-") + ".txt";
  document.body.appendChild(a);
  a.click();
  document.body.removeChild(a);
  URL.revokeObjectURL(url);
}


// ── Reboot ────────────────────────────────────────────────────────

var _rebootStartTime = 0;
var _serverWentDown = false;
var _rebootFailed = false;

function _setRebootStatus(msg) {
  if ($rebootSubmessage) $rebootSubmessage.textContent = msg;
}

function doReboot() {
  if ($modal) $modal.classList.remove("open");
  if ($rebuildModal) $rebuildModal.classList.remove("open");
  stopUpdatePoll();
  stopRebuildPoll();
  // Reset overlay to main card
  if ($rebootMainCard) $rebootMainCard.style.display = "";
  if ($rebootErrorCard) $rebootErrorCard.style.display = "none";
  _setRebootStatus("Sending restart request\u2026");
  if ($rebootOverlay) $rebootOverlay.classList.add("visible");
  _rebootStartTime = Date.now();
  _serverWentDown = false;
  _rebootFailed = false;
  var rebootCtrl = new AbortController();
  setTimeout(function() { rebootCtrl.abort(); }, REBOOT_REQUEST_TIMEOUT);
  fetch("/api/reboot", { method: "POST", signal: rebootCtrl.signal })
    .then(function(res) {
      if (!res.ok) {
        // Definitive HTTP error — server rejected the request before going down
        _rebootFailed = true;
        if ($rebootMainCard) $rebootMainCard.style.display = "none";
        if ($rebootErrorCard) $rebootErrorCard.style.display = "";
        // Leave overlay visible so the error card is shown
      }
      // HTTP 2xx: request accepted, proceed with polling
    })
    .catch(function() {
      // Connection dropped or request aborted — the server is likely already going
      // down as part of the restart. Treat as success and continue polling.
    });
  // Wait before the first check — NixOS shutdown after an update can take 20-40s
  setTimeout(waitForServerReboot, REBOOT_INITIAL_DELAY);
}

function waitForServerReboot() {
  if (_rebootFailed) return;
  // Update status on first check (server hasn't gone down yet)
  if (!_serverWentDown) _setRebootStatus("Waiting for the computer to shut down\u2026");
  var controller = new AbortController();
  var timeoutId = setTimeout(function() { controller.abort(); }, REBOOT_FETCH_TIMEOUT);

  fetch("/api/ping", { cache: "no-store", signal: controller.signal, headers: { "Connection": "close" } })
    .then(function(res) {
      clearTimeout(timeoutId);
      if (_serverWentDown) {
        // Server is responding after having been down — reboot is complete.
        // Any response (even 401/500) means the server process is back.
        _setRebootStatus("System is back online. Reconnecting\u2026");
        window.location.reload();
      } else if ((Date.now() - _rebootStartTime) < 90000) {
        // Server still responding but hasn't gone down yet — keep waiting
        setTimeout(waitForServerReboot, REBOOT_CHECK_INTERVAL);
      } else {
        // Been over 90 seconds and server is responding — just reload
        _setRebootStatus("System is back online. Reconnecting\u2026");
        window.location.reload();
      }
    })
    .catch(function() {
      clearTimeout(timeoutId);
      if (!_serverWentDown) {
        _serverWentDown = true;
        _setRebootStatus("The computer is restarting\u2026");
      }
      setTimeout(waitForServerReboot, REBOOT_CHECK_INTERVAL);
    });
}
