import express from "express";
import {
  loginSchema,
  registerSchema,
  loginSecondFactorSchema,
  totpVerifySchema,
  webauthnFinishSchema,
} from "./schemas.js";
import { validate } from "../../middleware/validate.js";
import {
  login,
  refresh,
  register,
  finalizeSecondFactor,
  userByLoginToken,
} from "./service.js";
import { authenticate } from "../../middleware/auth.js";
import { requirePermission } from "../../middleware/rbac.js";
import { env } from "../../config/env.js";
import {
  beginRegistration,
  finishRegistration,
  beginAuthentication,
  finishAuthentication,
} from "../../services/webauthn.js";
import { startTotpEnrollment, verifyTotpEnrollment } from "../../services/totp.js";
import { writeAudit } from "../../utils/audit.js";

const router = express.Router();

function getRefreshCookieOptions() {
  const isProduction = String(env.nodeEnv || "").toLowerCase() === "production";

  return {
    httpOnly: true,
    sameSite: "lax",
    secure: isProduction && Boolean(env.cookieSecure),
    path: "/api/auth",
    maxAge: 7 * 24 * 60 * 60 * 1000,
  };
}

function setRefreshCookie(res, token) {
  res.cookie("refresh_token", token, getRefreshCookieOptions());
}

function clearRefreshCookie(res) {
  res.clearCookie("refresh_token", getRefreshCookieOptions());
}

router.post("/login", validate(loginSchema), async (req, res, next) => {
  try {
    const result = await login({ ...req.body, ipAddress: req.ip });

    if (result.mfaRequired) {
      return res.status(202).json({
        mfaRequired: true,
        loginToken: result.loginToken,
        availableSecondFactors: result.availableSecondFactors || null,
        userHint: result.userHint || null,
      });
    }

    setRefreshCookie(res, result.refreshToken);

    res.json({
      accessToken: result.accessToken,
      user: result.user,
    });
  } catch (err) {
    next(err);
  }
});

router.post("/login/totp", validate(loginSecondFactorSchema), async (req, res, next) => {
  try {
    const result = await finalizeSecondFactor({
      loginToken: req.body.login_token,
      totpCode: req.body.totp_code,
      ipAddress: req.ip,
    });

    setRefreshCookie(res, result.refreshToken);

    res.json({
      accessToken: result.accessToken,
      user: result.user,
    });
  } catch (err) {
    next(err);
  }
});

router.post(
  "/login/webauthn/options",
  validate(loginSecondFactorSchema.pick({ login_token: true })),
  async (req, res, next) => {
    try {
      const user = await userByLoginToken(req.body.login_token);
      if (!user) {
        return res.status(401).json({ error: "Login-Challenge abgelaufen" });
      }

      const options = await beginAuthentication(user);
      res.json(options);
    } catch (err) {
      next(err);
    }
  }
);

router.post(
  "/login/webauthn/verify",
  validate(webauthnFinishSchema.extend({ login_token: loginSecondFactorSchema.shape.login_token })),
  async (req, res, next) => {
    try {
      const user = await userByLoginToken(req.body.login_token);
      if (!user) {
        return res.status(401).json({ error: "Login-Challenge abgelaufen" });
      }

      await finishAuthentication(user, req.body.credential);

      const result = await finalizeSecondFactor({
        loginToken: req.body.login_token,
        ipAddress: req.ip,
      });

      setRefreshCookie(res, result.refreshToken);

      res.json({
        accessToken: result.accessToken,
        user: result.user,
      });
    } catch (err) {
      next(err);
    }
  }
);

router.post("/refresh", async (req, res, next) => {
  try {
    const token = req.cookies?.refresh_token;

    if (!token) {
      return res.status(401).json({ error: "Kein Refresh-Token" });
    }

    const result = await refresh(token, req.ip);

    setRefreshCookie(res, result.refreshToken);

    res.json({
      accessToken: result.accessToken,
      user: result.user,
    });
  } catch (err) {
    clearRefreshCookie(res);
    next(err);
  }
});

router.post(
  "/register",
  authenticate,
  requirePermission("system.users", ["admin"]),
  validate(registerSchema),
  async (req, res, next) => {
    try {
      const user = await register(req.body, req.user.sub, req.ip);
      res.status(201).json(user);
    } catch (err) {
      next(err);
    }
  }
);

router.post("/mfa/totp/setup", authenticate, async (req, res, next) => {
  try {
    const setup = await startTotpEnrollment({
      id: req.user.sub,
      email: req.user.email || "user@example.local",
    });
    res.json(setup);
  } catch (err) {
    next(err);
  }
});

router.post("/mfa/totp/verify", authenticate, validate(totpVerifySchema), async (req, res, next) => {
  try {
    const result = await verifyTotpEnrollment(
      { id: req.user.sub, email: req.user.email || "user@example.local" },
      req.body.token
    );

    await writeAudit({
      actorId: req.user.sub,
      action: "MFA_TOTP_ENROLLED",
      entityType: "user",
      entityId: req.user.sub,
      ipAddress: req.ip,
    });

    res.json(result);
  } catch (err) {
    next(err);
  }
});

router.post("/mfa/webauthn/register/options", authenticate, async (req, res, next) => {
  try {
    const user = {
      id: req.user.sub,
      email: req.user.email || `user-${req.user.sub}@local`,
      full_name: req.user.email || `User ${req.user.sub}`,
    };

    const options = await beginRegistration(user);
    res.json(options);
  } catch (err) {
    next(err);
  }
});

router.post(
  "/mfa/webauthn/register/verify",
  authenticate,
  validate(webauthnFinishSchema),
  async (req, res, next) => {
    try {
      const user = { id: req.user.sub };
      const result = await finishRegistration(user, req.body.credential);

      await writeAudit({
        actorId: req.user.sub,
        action: "WEBAUTHN_REGISTERED",
        entityType: "user",
        entityId: req.user.sub,
        ipAddress: req.ip,
      });

      res.json(result);
    } catch (err) {
      next(err);
    }
  }
);

router.post("/logout", async (req, res) => {
  clearRefreshCookie(res);
  res.json({ ok: true });
});

export default router;
