const express = require("express");
const crypto = require("crypto");
const session = require("express-session");
const BetterSqlite3Store = require("better-sqlite3-session-store")(session);

const { db } = require("./src/services/db");
const { getSetting, setSetting } = require("./src/services/settings");
const {
  buildDiscordAuthUrl,
  exchangeDiscordCode,
  fetchDiscordUser,
  fetchDiscordGuildMember
} = require("./src/services/auth");
const { getRoleFlags, hasAccess } = require("./src/services/rbac");
const { listSnapshots, restoreSnapshot } = require("./src/services/update-manager");
const { requestRestart } = require("./src/services/updater");
const {
  safeModeStatus,
  clearRecoveryMarker
} = require("./src/services/recovery-mode");
const { setPluginEnabled } = require("./src/services/plugins");

function ensureSessionSecret() {
  let secret = getSetting("session_secret");
  if (!secret) {
    secret = crypto.randomBytes(32).toString("hex");
    setSetting("session_secret", secret);
  }
  return secret;
}

function isConfigured() {
  return Boolean(
    getSetting("discord_client_id") &&
      getSetting("discord_client_secret") &&
      getSetting("discord_guild_id")
  );
}

function renderPage(title, content) {
  return `<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <title>${title}</title>
  <style>
    body { font-family: Arial, sans-serif; background: #f5f3ef; color: #1c1f23; margin: 0; }
    header { padding: 20px 28px; background: #1c1f23; color: #fff; }
    main { padding: 24px 28px; max-width: 900px; margin: 0 auto; }
    .card { background: #fff; border-radius: 12px; padding: 18px 20px; margin-bottom: 16px; box-shadow: 0 10px 25px rgba(20, 24, 30, 0.08); }
    .button { background: #0f6a78; color: #fff; border: none; padding: 8px 14px; border-radius: 8px; cursor: pointer; text-decoration: none; display: inline-block; }
    .button.subtle { background: #eef5f6; color: #173238; }
    .button.danger { background: #c24b3b; }
    .muted { color: #5a616a; }
    .warning { border-left: 4px solid #d28a24; }
    .danger-card { border-left: 4px solid #c24b3b; }
    form { display: inline-block; margin: 4px 6px 4px 0; }
    code { background: #f1ede7; padding: 2px 5px; border-radius: 5px; }
    table { width: 100%; border-collapse: collapse; }
    th, td { text-align: left; padding: 8px; border-bottom: 1px solid #e1ddd7; }
  </style>
</head>
<body>
  <header>
    <strong>Safe Mode</strong>
  </header>
  <main>
    ${content}
  </main>
</body>
</html>`;
}

function buildSnapshotTable(snapshots) {
  if (!snapshots.length) {
    return "<p class=\"muted\">No snapshots available.</p>";
  }
  const rows = snapshots
    .map((snap) => {
      const label = snap.type === "plugin" ? `Plugin: ${snap.pluginId}` : "Bot core";
      const when = new Date(snap.createdAt).toLocaleString();
      return `
        <tr>
          <td>${label}</td>
          <td>${when}</td>
          <td>
            <form method="post" action="/rollback/${snap.id}">
              <button class="button danger" type="submit">Rollback</button>
            </form>
          </td>
        </tr>
      `;
    })
    .join("");
  return `
    <table>
      <thead>
        <tr>
          <th>Snapshot</th>
          <th>Created</th>
          <th>Action</th>
        </tr>
      </thead>
      <tbody>
        ${rows}
      </tbody>
    </table>
  `;
}

function escapeHtml(value) {
  return String(value ?? "")
    .replace(/&/g, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;");
}

function buildRecoveryPanel() {
  const status = safeModeStatus();
  const marker = status.marker;
  if (!marker) {
    return `<section class="card"><h2>Recovery state</h2><p class="muted">No recovery marker is present.</p></section>`;
  }
  const pluginActions = marker.target_kind === "plugin" && marker.target_id
    ? `<form method="post" action="/plugins/${encodeURIComponent(marker.target_id)}/disable"><button class="button danger" type="submit">Disable plugin</button></form>`
    : "";
  return `
    <section class="card ${marker.status === "failed" || marker.status === "stale" ? "danger-card" : "warning"}">
      <h2>Last update marker</h2>
      <p class="muted">Status: <strong>${escapeHtml(marker.status)}</strong></p>
      <table>
        <tbody>
          <tr><th>Target</th><td>${escapeHtml(marker.target_kind || "unknown")} ${escapeHtml(marker.target_id || "")}</td></tr>
          <tr><th>Version</th><td>${escapeHtml(marker.from_version || "?")} -> ${escapeHtml(marker.to_version || "?")}</td></tr>
          <tr><th>Method</th><td>${escapeHtml(marker.update_method || "?")}</td></tr>
          <tr><th>Source branch</th><td>${escapeHtml(marker.source_branch || "?")}</td></tr>
          <tr><th>Snapshot</th><td>${escapeHtml(marker.snapshot_id || marker.recovery_marker_id || "pending")}</td></tr>
          <tr><th>Updated</th><td>${escapeHtml(marker.updated_at || marker.created_at || "?")}</td></tr>
          <tr><th>Error</th><td>${escapeHtml(marker.error || "")}</td></tr>
        </tbody>
      </table>
      <div>
        ${pluginActions}
        <form method="post" action="/recovery/clear-marker"><button class="button danger" type="submit">Clear stale marker</button></form>
        <form method="post" action="/recovery/retry-normal-startup"><button class="button subtle" type="submit">Retry normal startup</button></form>
      </div>
    </section>
  `;
}

const app = express();
const sessionStore = new BetterSqlite3Store({ client: db });
app.use(
  session({
    secret: ensureSessionSecret(),
    resave: false,
    saveUninitialized: false,
    store: sessionStore
  })
);
app.use(express.urlencoded({ extended: false }));

app.get("/", (req, res) => {
  if (!isConfigured()) {
    return res.send(
      renderPage(
        "Safe Mode",
        `<section class="card"><h2>Discord not configured</h2><p class="muted">Discord settings are required to enter safe mode.</p></section>`
      )
    );
  }
  if (!req.session.user) {
    return res.send(
      renderPage(
        "Safe Mode",
        `<section class="card"><h2>Login required</h2><p class="muted">Authenticate with Discord to access rollback tools.</p><a class="button" href="/auth/discord">Login with Discord</a></section>`
      )
    );
  }
  if (!hasAccess(req.session.user, "admin")) {
    return res.send(
      renderPage(
        "Safe Mode",
        `<section class="card"><h2>Access denied</h2><p class="muted">You do not have administrator access.</p></section>`
      )
    );
  }
  const snapshots = listSnapshots();
  const table = buildSnapshotTable(snapshots);
  res.send(
    renderPage(
      "Safe Mode",
      `${buildRecoveryPanel()}<section class="card"><h2>Rollback snapshots</h2><p class="muted">Use these snapshots to roll back failed updates. Major-version rollback is blocked unless the snapshot is marked rollback safe. The server will restart after rollback.</p>${table}</section>`
    )
  );
});

app.get("/auth/discord", (req, res) => {
  if (!isConfigured()) {
    return res.redirect("/");
  }
  const state = crypto.randomBytes(16).toString("hex");
  req.session.discordState = state;
  const baseUrl = `${req.protocol}://${req.get("host")}`;
  const redirectUri = `${baseUrl}/auth/discord/callback`;
  const url = buildDiscordAuthUrl(state, redirectUri);
  res.redirect(url);
});

app.get("/auth/discord/callback", async (req, res) => {
  const { code, state } = req.query;
  if (!code || state !== req.session.discordState) {
    return res.send(renderPage("Safe Mode", "<section class=\"card\">Invalid login state.</section>"));
  }
  try {
    const baseUrl = `${req.protocol}://${req.get("host")}`;
    const redirectUri = `${baseUrl}/auth/discord/callback`;
    const token = await exchangeDiscordCode(code, redirectUri);
    const user = await fetchDiscordUser(token.access_token);
    const guildId = getSetting("discord_guild_id");
    const member = guildId
      ? await fetchDiscordGuildMember(token.access_token, guildId)
      : null;
    const roles = member?.roles || [];
    const flags = getRoleFlags(roles);
    req.session.user = {
      id: user.id,
      username: user.global_name || user.username,
      roles,
      ...flags
    };
    res.redirect("/");
  } catch (error) {
    console.error(error);
    res.send(renderPage("Safe Mode", "<section class=\"card\">Login failed.</section>"));
  }
});

app.post("/rollback/:id", (req, res) => {
  if (!req.session.user || !hasAccess(req.session.user, "admin")) {
    return res.status(403).send(renderPage("Safe Mode", "<section class=\"card\">Access denied.</section>"));
  }
  try {
    restoreSnapshot(req.params.id);
    res.send(
      renderPage(
        "Safe Mode",
        "<section class=\"card\"><h2>Rollback complete</h2><p class=\"muted\">Restarting the bot now...</p></section>"
      )
    );
    requestRestart();
  } catch (error) {
    res.send(
      renderPage(
        "Safe Mode",
        `<section class=\"card\"><h2>Rollback failed</h2><p class=\"muted\">${error.message}</p></section>`
      )
    );
  }
});

app.post("/plugins/:id/disable", (req, res) => {
  if (!req.session.user || !hasAccess(req.session.user, "admin")) {
    return res.status(403).send(renderPage("Safe Mode", "<section class=\"card\">Access denied.</section>"));
  }
  setPluginEnabled(req.params.id, false);
  res.redirect("/");
});

app.post("/recovery/clear-marker", (req, res) => {
  if (!req.session.user || !hasAccess(req.session.user, "admin")) {
    return res.status(403).send(renderPage("Safe Mode", "<section class=\"card\">Access denied.</section>"));
  }
  clearRecoveryMarker();
  res.redirect("/");
});

app.post("/recovery/retry-normal-startup", (req, res) => {
  if (!req.session.user || !hasAccess(req.session.user, "admin")) {
    return res.status(403).send(renderPage("Safe Mode", "<section class=\"card\">Access denied.</section>"));
  }
  res.send(renderPage("Safe Mode", "<section class=\"card\"><h2>Retrying normal startup</h2><p class=\"muted\">Safe mode is exiting. The wrapper will restart Lumi normally.</p></section>"));
  requestRestart();
});

const port = Number(process.env.SAFE_MODE_PORT || 3001);
app.listen(port, () => {
  console.log(`Safe mode listening on http://localhost:${port}`);
});
