import {
  generateRegistrationOptions,
  verifyRegistrationResponse,
  generateAuthenticationOptions,
  verifyAuthenticationResponse,
} from "@simplewebauthn/server";
import { query } from "../config/db.js";
import { env } from "../config/env.js";

function parseTransports(val) {
  try { return val ? JSON.parse(val) : undefined; } catch { return undefined; }
}

export async function beginRegistration(user) {
  const result = await query(
    `SELECT credential_id FROM webauthn_credential WHERE user_id = $1 AND revoked_at IS NULL`,
    [user.id]
  );

  const options = await generateRegistrationOptions({
    rpName: "BR-Office",
    rpID: env.rpId,
    userName: user.email,
    userDisplayName: user.full_name || user.email,
    userID: String(user.id),
    attestationType: "none",
    excludeCredentials: result.rows.map((row) => ({
      id: row.credential_id,
      type: "public-key",
    })),
    authenticatorSelection: {
      residentKey: "preferred",
      userVerification: "preferred",
    },
  });

  await query(
    `INSERT INTO webauthn_challenge(user_id, flow_type, challenge, expires_at)
     VALUES ($1,'register',$2,NOW() + INTERVAL '10 minutes')`,
    [user.id, options.challenge]
  );

  return options;
}

export async function finishRegistration(user, credential) {
  const result = await query(
    `SELECT challenge FROM webauthn_challenge
      WHERE user_id = $1 AND flow_type = 'register' AND used_at IS NULL AND expires_at > NOW()
      ORDER BY created_at DESC LIMIT 1`,
    [user.id]
  );
  const row = result.rows[0];
  if (!row) throw Object.assign(new Error("WebAuthn-Challenge fehlt"), { status: 400, publicMessage: "WebAuthn-Challenge fehlt" });

  const verification = await verifyRegistrationResponse({
    response: credential,
    expectedChallenge: row.challenge,
    expectedOrigin: env.rpOrigin,
    expectedRPID: env.rpId,
  });

  if (!verification.verified || !verification.registrationInfo) {
    throw Object.assign(new Error("Passkey-Registrierung fehlgeschlagen"), { status: 400, publicMessage: "Passkey-Registrierung fehlgeschlagen" });
  }

  const { credentialPublicKey, credentialID, counter, credentialDeviceType, credentialBackedUp } = verification.registrationInfo;

  await query(
    `INSERT INTO webauthn_credential(user_id, credential_id, public_key, counter, transports, credential_device_type, credential_backed_up, nickname)
     VALUES ($1,$2,$3,$4,$5,$6,$7,$8)`,
    [
      user.id,
      Buffer.from(credentialID).toString("base64url"),
      Buffer.from(credentialPublicKey).toString("base64url"),
      counter,
      JSON.stringify(credential.response?.transports || []),
      credentialDeviceType,
      credentialBackedUp,
      credential.id,
    ]
  );
  await query(`UPDATE webauthn_challenge SET used_at = NOW() WHERE user_id = $1 AND flow_type = 'register' AND used_at IS NULL`, [user.id]);
  await query(`UPDATE app_user SET last_mfa_at = NOW() WHERE id = $1`, [user.id]);
  return { ok: true };
}

export async function beginAuthentication(user) {
  const result = await query(
    `SELECT credential_id, transports FROM webauthn_credential WHERE user_id = $1 AND revoked_at IS NULL`,
    [user.id]
  );
  const options = await generateAuthenticationOptions({
    rpID: env.rpId,
    allowCredentials: result.rows.map((row) => ({
      id: row.credential_id,
      type: "public-key",
      transports: parseTransports(row.transports),
    })),
    userVerification: "preferred",
  });
  await query(
    `INSERT INTO webauthn_challenge(user_id, flow_type, challenge, expires_at)
     VALUES ($1,'auth',$2,NOW() + INTERVAL '10 minutes')`,
    [user.id, options.challenge]
  );
  return options;
}

export async function finishAuthentication(user, credential) {
  const challengeResult = await query(
    `SELECT challenge FROM webauthn_challenge
      WHERE user_id = $1 AND flow_type = 'auth' AND used_at IS NULL AND expires_at > NOW()
      ORDER BY created_at DESC LIMIT 1`,
    [user.id]
  );
  const challenge = challengeResult.rows[0]?.challenge;
  if (!challenge) throw Object.assign(new Error("WebAuthn-Challenge fehlt"), { status: 400, publicMessage: "WebAuthn-Challenge fehlt" });

  const credResult = await query(
    `SELECT id, credential_id, public_key, counter FROM webauthn_credential
      WHERE user_id = $1 AND credential_id = $2 AND revoked_at IS NULL`,
    [user.id, credential.id]
  );
  const dbCred = credResult.rows[0];
  if (!dbCred) throw Object.assign(new Error("Passkey nicht gefunden"), { status: 404, publicMessage: "Passkey nicht gefunden" });

  const verification = await verifyAuthenticationResponse({
    response: credential,
    expectedChallenge: challenge,
    expectedOrigin: env.rpOrigin,
    expectedRPID: env.rpId,
    authenticator: {
      credentialID: dbCred.credential_id,
      credentialPublicKey: Buffer.from(dbCred.public_key, "base64url"),
      counter: dbCred.counter,
    },
  });

  if (!verification.verified) {
    throw Object.assign(new Error("Passkey-Prüfung fehlgeschlagen"), { status: 401, publicMessage: "Passkey-Prüfung fehlgeschlagen" });
  }

  await query(`UPDATE webauthn_credential SET counter = $2, last_used_at = NOW() WHERE id = $1`, [dbCred.id, verification.authenticationInfo.newCounter]);
  await query(`UPDATE webauthn_challenge SET used_at = NOW() WHERE user_id = $1 AND flow_type = 'auth' AND used_at IS NULL`, [user.id]);
  await query(`UPDATE app_user SET last_mfa_at = NOW() WHERE id = $1`, [user.id]);
  return { ok: true };
}
