import { createHash, createHmac, randomInt, timingSafeEqual } from "crypto";
import type { BallotVerificationMethod } from "@/types";

const CHALLENGE_TTL_MS = 10 * 60 * 1000;

function secret(): string {
  return process.env.NEXTAUTH_SECRET ?? "surveystronghold-dev-only";
}

export interface VerificationChallengePayload {
  electionId: string;
  voterRollId: string;
  email: string;
  phone: string | null;
  verificationMethod: BallotVerificationMethod;
  codeHash: string;
  exp: number;
}

export function generateOtpCode(): string {
  return String(randomInt(100000, 999999));
}

export function hashOtpCode(code: string): string {
  return createHash("sha256")
    .update(`${secret()}:otp:${code}`)
    .digest("hex");
}

export function signVerificationChallenge(
  input: Omit<VerificationChallengePayload, "codeHash" | "exp"> & {
    code: string;
    ttlMs?: number;
  }
): string {
  const exp = Date.now() + (input.ttlMs ?? CHALLENGE_TTL_MS);
  const codeHash = hashOtpCode(input.code);
  const phone = input.phone ?? "";
  const payload = [
    input.electionId,
    input.voterRollId,
    input.email,
    phone,
    input.verificationMethod,
    codeHash,
    String(exp),
  ].join("|");
  const sig = createHmac("sha256", secret()).update(payload).digest("base64url");
  return `${Buffer.from(payload).toString("base64url")}.${sig}`;
}

export function verifyVerificationChallenge(
  token: string
): VerificationChallengePayload | null {
  const parts = token.split(".");
  if (parts.length !== 2) return null;
  const [encoded, sig] = parts;
  if (!encoded || !sig) return null;

  let payload: string;
  try {
    payload = Buffer.from(encoded, "base64url").toString("utf8");
  } catch {
    return null;
  }

  const expected = createHmac("sha256", secret())
    .update(payload)
    .digest("base64url");
  const sigBuf = Buffer.from(sig);
  const expectedBuf = Buffer.from(expected);
  if (sigBuf.length !== expectedBuf.length) return null;
  if (!timingSafeEqual(sigBuf, expectedBuf)) return null;

  const [
    electionId,
    voterRollId,
    email,
    phone,
    verificationMethod,
    codeHash,
    expRaw,
  ] = payload.split("|");
  if (
    !electionId ||
    !voterRollId ||
    !email ||
    !verificationMethod ||
    !codeHash ||
    !expRaw
  ) {
    return null;
  }

  const exp = Number(expRaw);
  if (!Number.isFinite(exp) || Date.now() > exp) return null;

  if (
    verificationMethod !== "email" &&
    verificationMethod !== "sms_2fa" &&
    verificationMethod !== "email_sms"
  ) {
    return null;
  }

  return {
    electionId,
    voterRollId,
    email,
    phone: phone || null,
    verificationMethod,
    codeHash,
    exp,
  };
}

export function otpMatches(code: string, codeHash: string): boolean {
  const actual = hashOtpCode(code.trim());
  const a = Buffer.from(actual);
  const b = Buffer.from(codeHash);
  if (a.length !== b.length) return false;
  return timingSafeEqual(a, b);
}
