/**
 * OPS_STATE=PROD(구 REAL) 일 때 이메일(@xavis.co.kr) 매직 링크 인증
 */
const crypto = require("crypto");
const { isOpsProdMode } = require("./lib/ops-state");
const path = require("path");
const fsSync = require("fs");
const { v4: uuidv4 } = require("uuid");

let nodemailer = null;
try {
  nodemailer = require("nodemailer");
} catch {
  /* optional */
}

const OPS_AUTH_COOKIE = "ops_user_session";
const ALLOWED_EMAIL_SUFFIX = "@xavis.co.kr";
/** 메일 안 인증 링크 유효 시간(토큰 만료) */
const MAGIC_LINK_MAX_AGE_MS = 15 * 60 * 1000;

/** OPS_SESSION_TZ 기준 달력 날짜 키 (YYYY-MM-DD) */
function calendarDateKeyInTz(tsMs, tz) {
  const parts = new Intl.DateTimeFormat("en-CA", {
    timeZone: tz,
    year: "numeric",
    month: "2-digit",
    day: "2-digit",
  }).formatToParts(new Date(tsMs));
  const y = parts.find((p) => p.type === "year").value;
  const mo = parts.find((p) => p.type === "month").value;
  const da = parts.find((p) => p.type === "day").value;
  return `${y}-${mo}-${da}`;
}

/**
 * 로그인 세션 만료: OPS_SESSION_TZ(기본 Asia/Seoul)에서 해당 일자의 마지막 순간(23:59:59.999에 해당하는 epoch ms)
 */
function getOpsSessionExpiresAtMs(nowMs = Date.now()) {
  const tz = (process.env.OPS_SESSION_TZ || "Asia/Seoul").trim() || "Asia/Seoul";
  const cur = calendarDateKeyInTz(nowMs, tz);
  let lo = nowMs;
  let hi = nowMs + 48 * 60 * 60 * 1000;
  let guard = 0;
  while (calendarDateKeyInTz(hi, tz) === cur && guard < 400) {
    hi += 24 * 60 * 60 * 1000;
    guard++;
  }
  if (calendarDateKeyInTz(hi, tz) === cur) {
    hi = nowMs + 400 * 24 * 60 * 60 * 1000;
  }
  while (hi - lo > 1) {
    const mid = Math.floor((lo + hi) / 2);
    if (calendarDateKeyInTz(mid, tz) === cur) lo = mid;
    else hi = mid;
  }
  return hi - 1;
}

function isOpsProd() {
  return isOpsProdMode();
}

function getAuthSecret() {
  return (process.env.AUTH_SECRET || process.env.ADMIN_TOKEN || "xavis-admin").trim();
}

function getBaseUrl() {
  const b = (process.env.BASE_URL || "").trim().replace(/\/$/, "");
  if (b) return b;
  const port = process.env.PORT || 8030;
  return `http://localhost:${port}`;
}

function sanitizeReturnTo(v) {
  const s = (v || "").toString().trim();
  if (!s.startsWith("/") || s.startsWith("//")) return "/learning";
  return s;
}

function appendVerifiedParam(returnPath) {
  const s = sanitizeReturnTo(returnPath);
  return s.includes("?") ? `${s}&verified=1` : `${s}?verified=1`;
}

function isAllowedXavisEmail(email) {
  const s = String(email || "")
    .trim()
    .toLowerCase();
  if (!s.includes("@")) return false;
  return s.endsWith(ALLOWED_EMAIL_SUFFIX);
}

function signSessionCookie(email, expMs) {
  const exp = expMs;
  const payload = `${email}|${exp}`;
  const sig = crypto.createHmac("sha256", getAuthSecret()).update(payload).digest("hex");
  return Buffer.from(JSON.stringify({ email, exp, sig })).toString("base64url");
}

function parseSessionCookie(val) {
  if (!val || typeof val !== "string") return null;
  try {
    const j = JSON.parse(Buffer.from(val, "base64url").toString("utf8"));
    if (!j.email || typeof j.exp !== "number") return null;
    if (Date.now() > j.exp) return null;
    const payload = `${j.email}|${j.exp}`;
    const sig = crypto.createHmac("sha256", getAuthSecret()).update(payload).digest("hex");
    if (sig !== j.sig) return null;
    return String(j.email).toLowerCase();
  } catch {
    return null;
  }
}

function getOpsSessionEmail(req) {
  return parseSessionCookie(req.cookies?.[OPS_AUTH_COOKIE]);
}

function loadMagicLinks(MAGIC_LINK_PATH) {
  try {
    const raw = fsSync.readFileSync(MAGIC_LINK_PATH, "utf8");
    const arr = JSON.parse(raw);
    return Array.isArray(arr) ? arr : [];
  } catch {
    return [];
  }
}

function saveMagicLinks(MAGIC_LINK_PATH, list) {
  fsSync.mkdirSync(path.dirname(MAGIC_LINK_PATH), { recursive: true });
  fsSync.writeFileSync(MAGIC_LINK_PATH, JSON.stringify(list, null, 2), "utf8");
}

function pruneExpired(list) {
  const now = Date.now();
  return list.filter((x) => x.expiresAt > now);
}

/** 클라이언트에 내부 주소·스택이 노출되지 않도록 SMTP 오류 메시지 정리 */
function publicSmtpErrorMessage(err) {
  const raw = String(err?.message || err || "");
  const code = err?.code || err?.cause?.code;
  if (code === "ECONNREFUSED" || raw.includes("ECONNREFUSED")) {
    return "메일 서버에 연결할 수 없습니다. PROD 환경의 SMTP_HOST·포트가 앱 서버에서 접근 가능한지(방화벽·사내 전용 게이트웨이 여부)를 확인하세요.";
  }
  if (code === "ETIMEDOUT" || raw.includes("ETIMEDOUT") || /timeout/i.test(raw)) {
    return "메일 서버 연결 시간이 초과되었습니다.";
  }
  if (code === "ENOTFOUND" || raw.includes("ENOTFOUND")) {
    return "메일 서버 주소(SMTP_HOST)를 찾을 수 없습니다.";
  }
  if (code === "ECONNRESET" || raw.includes("ECONNRESET")) {
    return "메일 서버와의 연결이 끊겼습니다. TLS/포트(SMTP_SECURE, SMTP_PORT) 설정을 확인하세요.";
  }
  return "인증 메일을 보내지 못했습니다. 잠시 후 다시 시도하거나 관리자에게 문의하세요.";
}

function createSmtpTransport() {
  const host = (process.env.SMTP_HOST || "").trim();
  const port = Number(process.env.SMTP_PORT || 587);
  const secure = process.env.SMTP_SECURE === "1";
  const user = (process.env.SMTP_USER || "").trim();
  const pass = (process.env.SMTP_PASS || "").trim();
  const requireTlsEnv = process.env.SMTP_REQUIRE_TLS;
  const requireTLS =
    requireTlsEnv === "0"
      ? false
      : requireTlsEnv === "1" || (!secure && port === 587);
  return nodemailer.createTransport({
    host,
    port,
    secure,
    auth: user ? { user, pass } : undefined,
    requireTLS,
    connectionTimeout: 15000,
    greetingTimeout: 15000,
    socketTimeout: 20000,
    tls: { minVersion: "TLSv1.2" },
  });
}

function isOpsPublicPath(req) {
  const p = req.path || "";
  if (p === "/login") return true;
  if (p === "/logout") return true;
  /** 쿼리형·경로형 인증 링크 모두 허용 (메일 클라이언트는 ?token= 링크를 깨뜨리는 경우가 있음) */
  if (p === "/auth/verify" || p.startsWith("/auth/verify/")) return true;
  if (p === "/api/auth/request-link" && req.method === "POST") return true;
  return false;
}

/** HTML 이메일용 이스케이프 (속성·본문 공통) */
function escapeHtmlEmail(s) {
  return String(s)
    .replace(/&/g, "&amp;")
    .replace(/"/g, "&quot;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;");
}

/**
 * 인증 메일 본문 HTML — 인라인 스타일·테이블 기반(주요 클라이언트 호환)
 */
function buildMagicLinkEmailHtml(linkUrl, linkMinutes) {
  const href = String(linkUrl).replace(/&/g, "&amp;").replace(/"/g, "&quot;");
  const linkText = escapeHtmlEmail(linkUrl);
  return `<!DOCTYPE html>
<html lang="ko">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>인증 안내</title>
</head>
<body style="margin:0;padding:0;background-color:#f1f5f9;-webkit-text-size-adjust:100%;">
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0" style="background-color:#f1f5f9;padding:24px 12px;">
  <tr>
    <td align="center">
      <table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0" style="max-width:560px;background-color:#ffffff;border-radius:12px;overflow:hidden;border:1px solid #e2e8f0;">
        <tr>
          <td style="padding:28px 22px 8px 22px;font-family:'Apple SD Gothic Neo','Malgun Gothic',Helvetica,Arial,sans-serif;">
            <p style="margin:0 0 4px 0;font-size:13px;font-weight:700;letter-spacing:0.02em;color:#dc2626;">XAVIS</p>
            <p style="margin:0 0 20px 0;font-size:17px;font-weight:700;color:#0f172a;line-height:1.35;">AI Platform</p>
            <p style="margin:0 0 14px 0;font-size:15px;line-height:1.65;color:#334155;">계정 인증을 위해 아래의 인증 링크를 안내드립니다.</p>
            <p style="margin:0 0 24px 0;font-size:15px;line-height:1.65;color:#334155;">아래 버튼을 누르시면 인증이 완료되며, 이후 서비스를 정상적으로 이용하실 수 있습니다.</p>
            <table role="presentation" cellspacing="0" cellpadding="0" border="0" align="center" style="margin:0 auto 20px auto;">
              <tr>
                <td align="center" style="border-radius:8px;background-color:#2563eb;">
                  <a href="${href}" target="_blank" rel="noopener noreferrer" style="display:inline-block;padding:14px 32px;font-size:15px;font-weight:600;color:#ffffff;text-decoration:none;font-family:'Apple SD Gothic Neo','Malgun Gothic',Helvetica,Arial,sans-serif;">인증 완료하기</a>
                </td>
              </tr>
            </table>
            <p style="margin:0 0 8px 0;font-size:12px;line-height:1.5;color:#64748b;">버튼이 동작하지 않으면 아래 주소를 브라우저에 복사해 붙여 넣어 주세요.</p>
            <p style="margin:0 0 22px 0;font-size:12px;line-height:1.5;color:#2563eb;word-break:break-all;"><a href="${href}" style="color:#2563eb;text-decoration:underline;">${linkText}</a></p>
            <div style="border-top:1px solid #e2e8f0;padding-top:18px;margin-top:4px;">
              <p style="margin:0 0 8px 0;font-size:13px;line-height:1.6;color:#64748b;">※ 본 인증 링크는 보안을 위해 발송 시점으로부터 <strong style="color:#475569;">${linkMinutes}분</strong> 동안만 유효합니다.</p>
              <p style="margin:0 0 18px 0;font-size:13px;line-height:1.6;color:#64748b;">※ 인증 시간이 만료된 경우, 다시 인증 메일을 요청해 주시기 바랍니다.</p>
              <p style="margin:0 0 6px 0;font-size:13px;line-height:1.6;color:#64748b;">본 메일은 발신 전용이며, 문의 사항이 있으실 경우 <strong style="color:#475569;">AI혁신팀</strong>으로 문의 부탁합니다.</p>
              <p style="margin:0;font-size:14px;font-weight:600;color:#0f172a;">감사합니다.</p>
            </div>
          </td>
        </tr>
      </table>
      <p style="margin:16px 0 0 0;font-size:11px;line-height:1.4;color:#94a3b8;font-family:'Apple SD Gothic Neo','Malgun Gothic',Helvetica,Arial,sans-serif;">© XAVIS · 본 메일은 시스템에 의해 자동 발송되었습니다.</p>
    </td>
  </tr>
</table>
</body>
</html>`;
}

async function sendMagicLinkEmail(to, linkUrl) {
  const host = (process.env.SMTP_HOST || "").trim();
  if (!nodemailer || !host) {
    console.warn("[OPS] SMTP 미설정 — 아래 링크를 수동으로 전달하거나 SMTP_HOST 등을 설정하세요.");
    console.warn("[OPS] Magic link for", to, "→", linkUrl);
    return;
  }
  const from = (process.env.SMTP_FROM || (process.env.SMTP_USER || "").trim() || "noreply@xavis.co.kr").trim();
  const transporter = createSmtpTransport();
  const linkMinutes = Math.max(1, Math.floor(MAGIC_LINK_MAX_AGE_MS / 60000));
  const textBody = [
    "계정 인증을 위해 아래의 인증 링크를 안내드립니다.",
    "아래 링크를 클릭하시면 인증이 완료되며, 이후 서비스를 정상적으로 이용하실 수 있습니다.",
    "",
    "인증 링크:",
    linkUrl,
    "",
    `※ 본 인증 링크는 보안을 위해 발송 시점으로부터 ${linkMinutes}분 동안만 유효합니다.`,
    "※ 인증 시간이 만료된 경우, 다시 인증 메일을 요청해 주시기 바랍니다.",
    "",
    "본 메일은 발신 전용이며, 문의 사항이 있으실 경우 AI혁신팀으로 문의 부탁합니다.",
    "",
    "감사합니다.",
  ].join("\n");
  const htmlBody = buildMagicLinkEmailHtml(linkUrl, linkMinutes);
  await transporter.sendMail({
    from,
    to,
    subject: "[인증] XAVIS AI Platform 인증 링크",
    text: textBody,
    html: htmlBody,
  });
}

module.exports = function createOpsAuth(DATA_DIR, hooks = {}) {
  const MAGIC_LINK_PATH = path.join(DATA_DIR, "ops-magic-links.json");
  const BASE_URL = getBaseUrl();

  function opsAccessMiddleware(req, res, next) {
    if (!isOpsProd()) {
      res.locals.opsAuthRequired = false;
      res.locals.opsUserEmail = null;
      return next();
    }
    res.locals.opsAuthRequired = true;
    if (isOpsPublicPath(req)) {
      return next();
    }
    const email = getOpsSessionEmail(req);
    if (email) {
      res.locals.opsUserEmail = email;
      return next();
    }
    if (req.path.startsWith("/api/")) {
      return res.status(401).json({ error: "로그인이 필요합니다." });
    }
    const returnTo = req.originalUrl || "/learning";
    return res.redirect("/login?returnTo=" + encodeURIComponent(returnTo));
  }

  function registerRoutes(app) {
    app.get("/login", (req, res) => {
      if (!isOpsProd()) {
        return res.redirect("/learning");
      }
      if (getOpsSessionEmail(req)) {
        return res.redirect(sanitizeReturnTo(req.query.returnTo));
      }
      return res.render("login", {
        returnTo: sanitizeReturnTo(req.query.returnTo),
        layoutMinimal: true,
      });
    });

    app.get("/logout", async (req, res) => {
      const sessionEmail = getOpsSessionEmail(req);
      if (typeof hooks.onLogout === "function" && sessionEmail) {
        try {
          await hooks.onLogout({ email: sessionEmail, req });
        } catch (hookErr) {
          console.error("[OPS] onLogout hook:", hookErr?.message || hookErr);
        }
      }
      res.clearCookie(OPS_AUTH_COOKIE, { path: "/" });
      if (isOpsProd()) {
        return res.redirect("/login");
      }
      return res.redirect("/learning");
    });

    app.post("/api/auth/request-link", async (req, res) => {
      try {
        if (!isOpsProd()) {
          return res.status(400).json({ error: "PROD(운영) 모드가 아닙니다." });
        }
        const email = String(req.body?.email || "")
          .trim()
          .toLowerCase();
        if (!email) {
          return res.status(400).json({ error: "이메일을 입력해 주세요." });
        }
        if (!isAllowedXavisEmail(email)) {
          return res.status(403).json({ error: "허용된 임직원이 아닙니다." });
        }
        let list = pruneExpired(loadMagicLinks(MAGIC_LINK_PATH));
        const token = uuidv4();
        const expiresAt = Date.now() + MAGIC_LINK_MAX_AGE_MS;
        const ret = sanitizeReturnTo(req.body?.returnTo);
        list.push({
          token,
          email,
          expiresAt,
          used: false,
          createdAt: new Date().toISOString(),
          returnTo: ret,
        });
        saveMagicLinks(MAGIC_LINK_PATH, list);
        /** 경로에 토큰만 두기 (쿼리 `?token=`는 일부 웹메일/Outlook에서 href가 잘림) */
        const linkUrl = `${BASE_URL}/auth/verify/${encodeURIComponent(token)}`;
        await sendMagicLinkEmail(email, linkUrl);
        if (typeof hooks.onMagicLinkRequested === "function") {
          try {
            await hooks.onMagicLinkRequested({ email, returnTo: ret, req });
          } catch (hookErr) {
            console.error("[OPS] onMagicLinkRequested hook:", hookErr?.message || hookErr);
          }
        }
        return res.json({
          ok: true,
          message: "회사 메일로 인증 링크를 보냈습니다. 메일함을 확인해 주세요.",
        });
      } catch (err) {
        console.error("[OPS] request-link:", err?.message || err);
        return res.status(500).json({ error: publicSmtpErrorMessage(err) });
      }
    });

    async function handleMagicLinkVerify(req, res) {
      if (!isOpsProd()) {
        return res.redirect("/learning");
      }
      const token = String(req.params.token || req.query.token || "").trim();
      if (!token) {
        return res.status(400).send("유효하지 않은 링크입니다.");
      }
      let list = pruneExpired(loadMagicLinks(MAGIC_LINK_PATH));
      const idx = list.findIndex((x) => x.token === token && !x.used);
      if (idx === -1) {
        return res.status(400).send("유효하지 않거나 만료된 링크입니다.");
      }
      const row = list[idx];
      if (Date.now() > row.expiresAt) {
        return res.status(400).send("만료된 링크입니다.");
      }
      row.used = true;
      saveMagicLinks(MAGIC_LINK_PATH, list);
      if (typeof hooks.onLoginSuccess === "function") {
        try {
          await hooks.onLoginSuccess({
            email: row.email,
            returnTo: row.returnTo,
            req,
          });
        } catch (hookErr) {
          console.error("[OPS] onLoginSuccess hook:", hookErr?.message || hookErr);
        }
      }
      const sessionExp = getOpsSessionExpiresAtMs();
      const cookieVal = signSessionCookie(row.email, sessionExp);
      const secure = process.env.NODE_ENV === "production";
      res.cookie(OPS_AUTH_COOKIE, cookieVal, {
        httpOnly: true,
        maxAge: Math.max(0, sessionExp - Date.now()),
        sameSite: "lax",
        path: "/",
        secure,
      });
      const dest = appendVerifiedParam(sanitizeReturnTo(row.returnTo || "/learning"));
      return res.redirect(dest);
    }

    /** 구 메일 호환: ?token= */
    app.get("/auth/verify", handleMagicLinkVerify);
    /** 권장: 쿼리 없이 경로만 (메일 HTML 파서 깨짐 방지) */
    app.get("/auth/verify/:token", handleMagicLinkVerify);
  }

  return {
    middleware: opsAccessMiddleware,
    registerRoutes,
    isOpsProd,
    /** @deprecated REAL→PROD 이전 호환용 */
    isOpsReal: isOpsProd,
  };
};
