import argon2 from "argon2";
import crypto from "crypto";
import { query } from "../../config/db.js";
import { signAccessToken, signRefreshToken, verifyRefreshToken } from "../../utils/jwt.js";
import { writeAudit } from "../../utils/audit.js";
import { env } from "../../config/env.js";
import { verifyTotpCode } from "../../services/totp.js";
import { getUserAccess } from "../../services/accessControl.js";

function makeLoginToken() {
  return crypto.randomBytes(24).toString("hex");
}

function needsSecondFactor(user, webauthnCount) {
  const privileged = ["admin", "chair", "secretary"].includes(user.role);
  return Boolean(user.two_factor_enabled || webauthnCount > 0 || (env.forceMfaForPrivileged && privileged));
}

export async function login({ email, password, ipAddress }) {
  const result = await query(
    `SELECT id, full_name, email, password_hash, role, committee_id, is_active, two_factor_enabled
       FROM app_user
      WHERE email = $1`,
    [email]
  );
  const user = result.rows[0];
  if (!user || !user.is_active) throw Object.assign(new Error("Login fehlgeschlagen"), { status: 401, publicMessage: "Login fehlgeschlagen" });

  const ok = await argon2.verify(user.password_hash, password);
  if (!ok) {
    await writeAudit({ actorId: user.id, action: "LOGIN_FAILED", entityType: "user", entityId: user.id, ipAddress });
    throw Object.assign(new Error("Login fehlgeschlagen"), { status: 401, publicMessage: "Login fehlgeschlagen" });
  }

  const credCount = await query(`SELECT COUNT(*)::int AS count FROM webauthn_credential WHERE user_id = $1 AND revoked_at IS NULL`, [user.id]);
  console.log("MFA_DEBUG", {
  email: user.email,
  role: user.role,
  two_factor_enabled: user.two_factor_enabled,
  webauthnCount: credCount.rows[0].count,
  forceMfaForPrivileged: env.forceMfaForPrivileged,
  privileged: ["admin", "chair", "secretary"].includes(user.role),
});
  if (needsSecondFactor(user, credCount.rows[0].count)) {
    const loginToken = makeLoginToken();
    await query(
      `INSERT INTO auth_challenge(user_id, challenge_type, challenge_value, expires_at, metadata)
       VALUES ($1, 'login', $2, NOW() + INTERVAL '10 minutes', $3)`,
      [user.id, loginToken, JSON.stringify({ ipAddress })]
    );
    await writeAudit({ actorId: user.id, action: "LOGIN_PASSWORD_OK_MFA_PENDING", entityType: "user", entityId: user.id, ipAddress });
    return {
      mfaRequired: true,
      loginToken,
      userHint: {
        id: user.id,
        full_name: user.full_name,
        email: user.email,
        role: user.role,
        committee_id: user.committee_id,
      },
    };
  }

  const accessToken = signAccessToken({ ...user, mfa_verified: true, amr: ["pwd"] });
  const refreshToken = signRefreshToken({ ...user, mfa_verified: true });
  await storeRefresh(user.id, refreshToken);
  await writeAudit({ actorId: user.id, action: "LOGIN_SUCCESS", entityType: "user", entityId: user.id, ipAddress });
  const access = await getUserAccess(user.id, user.role);
  return {
    accessToken,
    refreshToken,
    user: { id: user.id, full_name: user.full_name, email: user.email, role: user.role, committee_id: user.committee_id, mfa_verified: true, ...access }
  };
}

async function storeRefresh(userId, refreshToken) {
  await query(
    `INSERT INTO refresh_token(user_id, token_hash, expires_at)
     VALUES ($1, crypt($2, gen_salt('bf')), NOW() + INTERVAL '7 days')`,
    [userId, refreshToken]
  );
}

export async function finalizeSecondFactor({ loginToken, totpCode, ipAddress }) {
  const pending = await query(
    `SELECT ac.id, ac.user_id, u.full_name, u.email, u.role, u.committee_id, u.is_active
       FROM auth_challenge ac
       JOIN app_user u ON u.id = ac.user_id
      WHERE ac.challenge_type = 'login' AND ac.challenge_value = $1 AND ac.used_at IS NULL AND ac.expires_at > NOW()
      ORDER BY ac.created_at DESC LIMIT 1`,
    [loginToken]
  );
  const row = pending.rows[0];
  if (!row || !row.is_active) throw Object.assign(new Error("MFA-Login ungültig"), { status: 401, publicMessage: "MFA-Login ungültig" });

  if (totpCode) {
    const ok = await verifyTotpCode(row, totpCode);
    if (!ok) {
      await writeAudit({ actorId: row.user_id, action: "MFA_TOTP_FAILED", entityType: "user", entityId: row.user_id, ipAddress });
      throw Object.assign(new Error("Ungültiger TOTP-Code"), { status: 401, publicMessage: "Ungültiger TOTP-Code" });
    }
  }

  await query(`UPDATE auth_challenge SET used_at = NOW() WHERE id = $1`, [row.id]);

  const accessToken = signAccessToken({
    id: row.user_id,
    role: row.role,
    committee_id: row.committee_id,
    mfa_verified: true,
    amr: totpCode ? ["pwd", "otp"] : ["pwd", "hwk"],
  });
  const refreshToken = signRefreshToken({ id: row.user_id, mfa_verified: true });
  await storeRefresh(row.user_id, refreshToken);
  await writeAudit({ actorId: row.user_id, action: "LOGIN_SUCCESS_MFA", entityType: "user", entityId: row.user_id, ipAddress });
  const access = await getUserAccess(row.user_id, row.role);

  return {
    accessToken,
    refreshToken,
    user: { id: row.user_id, full_name: row.full_name, email: row.email, role: row.role, committee_id: row.committee_id, mfa_verified: true, ...access }
  };
}

export async function register(payload, actorId, ipAddress) {
  const hash = await argon2.hash(payload.password);
  const result = await query(
    `INSERT INTO app_user(full_name, email, password_hash, role, committee_id, is_active)
     VALUES ($1,$2,$3,$4,$5,TRUE)
     RETURNING id, full_name, email, role, committee_id, is_active`,
    [payload.full_name, payload.email, hash, payload.role, payload.committee_id ?? null]
  );
  const user = result.rows[0];
  await writeAudit({ actorId, action: "USER_CREATED", entityType: "user", entityId: user.id, metadata: { role: user.role }, ipAddress });
  return user;
}

export async function refresh(oldToken, ipAddress) {
  const payload = verifyRefreshToken(oldToken);
  const userResult = await query(
    `SELECT id, full_name, email, role, committee_id, is_active, true AS mfa_verified FROM app_user WHERE id = $1`,
    [payload.sub]
  );
  const user = userResult.rows[0];
  if (!user || !user.is_active) throw Object.assign(new Error("Refresh fehlgeschlagen"), { status: 401, publicMessage: "Refresh fehlgeschlagen" });

  const accessToken = signAccessToken(user);
  const refreshToken = signRefreshToken(user);
  const access = await getUserAccess(user.id, user.role);
  await writeAudit({ actorId: user.id, action: "TOKEN_REFRESH", entityType: "user", entityId: user.id, ipAddress });
  return { accessToken, refreshToken, user: { ...user, ...access } };
}

export async function userByLoginToken(loginToken) {
  const result = await query(
    `SELECT u.id, u.full_name, u.email, u.role, u.committee_id
       FROM auth_challenge ac
       JOIN app_user u ON u.id = ac.user_id
      WHERE ac.challenge_type = 'login' AND ac.challenge_value = $1 AND ac.used_at IS NULL AND ac.expires_at > NOW()
      ORDER BY ac.created_at DESC LIMIT 1`,
    [loginToken]
  );
  return result.rows[0] || null;
}
