import { randomBytes, randomInt, timingSafeEqual } from "crypto";
import logger from "./logger.js";
import pool from "./db.js";

export interface Verification {
  email: string;
  token: string;
  apiKey: string;
  createdAt: string;
  verifiedAt: string | null;
}

export interface PendingVerification {
  email: string;
  code: string;
  createdAt: string;
  expiresAt: string;
  attempts: number;
}

const TOKEN_EXPIRY_MS = 24 * 60 * 60 * 1000;
const CODE_EXPIRY_MS = 15 * 60 * 1000;
const MAX_ATTEMPTS = 3;

export async function createVerification(email: string, apiKey: string): Promise<Verification> {
  // Check for existing unexpired, unverified
  const existing = await pool.query(
    "SELECT * FROM verifications WHERE email = $1 AND verified_at IS NULL AND created_at > NOW() - INTERVAL '24 hours' LIMIT 1",
    [email]
  );
  if (existing.rows.length > 0) {
    const r = existing.rows[0];
    return { email: r.email, token: r.token, apiKey: r.api_key, createdAt: r.created_at.toISOString(), verifiedAt: null };
  }

  // Remove old unverified
  await pool.query("DELETE FROM verifications WHERE email = $1 AND verified_at IS NULL", [email]);

  const token = randomBytes(32).toString("hex");
  const now = new Date().toISOString();
  await pool.query(
    "INSERT INTO verifications (email, token, api_key, created_at) VALUES ($1, $2, $3, $4)",
    [email, token, apiKey, now]
  );
  return { email, token, apiKey, createdAt: now, verifiedAt: null };
}

export function verifyToken(token: string): { status: "ok"; verification: Verification } | { status: "invalid" | "expired" | "already_verified"; verification?: Verification } {
  // Synchronous wrapper — we'll make it async-compatible
  // Actually need to keep sync for the GET /verify route. Use sync query workaround or refactor.
  // For simplicity, we'll cache verifications in memory too.
  return verifyTokenSync(token);
}

// In-memory cache for verifications (loaded on startup, updated on changes)
let verificationsCache: Verification[] = [];

export async function loadVerifications(): Promise<void> {
  const result = await pool.query("SELECT * FROM verifications");
  verificationsCache = result.rows.map((r) => ({
    email: r.email,
    token: r.token,
    apiKey: r.api_key,
    createdAt: r.created_at instanceof Date ? r.created_at.toISOString() : r.created_at,
    verifiedAt: r.verified_at ? (r.verified_at instanceof Date ? r.verified_at.toISOString() : r.verified_at) : null,
  }));

  // Cleanup expired entries every 15 minutes
  setInterval(() => {
    const cutoff = Date.now() - 24 * 60 * 60 * 1000;
    const before = verificationsCache.length;
    verificationsCache = verificationsCache.filter(
      (v) => v.verifiedAt || new Date(v.createdAt).getTime() > cutoff
    );
    const removed = before - verificationsCache.length;
    if (removed > 0) logger.info({ removed }, "Cleaned expired verification cache entries");
  }, 15 * 60 * 1000);
}

function verifyTokenSync(token: string): { status: "ok"; verification: Verification } | { status: "invalid" | "expired" | "already_verified"; verification?: Verification } {
  const v = verificationsCache.find((v) => v.token === token);
  if (!v) return { status: "invalid" };
  if (v.verifiedAt) return { status: "already_verified", verification: v };
  const age = Date.now() - new Date(v.createdAt).getTime();
  if (age > TOKEN_EXPIRY_MS) return { status: "expired" };
  v.verifiedAt = new Date().toISOString();
  // Update DB async
  pool.query("UPDATE verifications SET verified_at = $1 WHERE token = $2", [v.verifiedAt, token]).catch((err) => logger.error({ err }, "Failed to update verification"));
  return { status: "ok", verification: v };
}

export async function createPendingVerification(email: string): Promise<PendingVerification> {
  await pool.query("DELETE FROM pending_verifications WHERE email = $1", [email]);

  const now = new Date();
  const pending: PendingVerification = {
    email,
    code: String(randomInt(100000, 999999)),
    createdAt: now.toISOString(),
    expiresAt: new Date(now.getTime() + CODE_EXPIRY_MS).toISOString(),
    attempts: 0,
  };

  await pool.query(
    "INSERT INTO pending_verifications (email, code, created_at, expires_at, attempts) VALUES ($1, $2, $3, $4, $5)",
    [pending.email, pending.code, pending.createdAt, pending.expiresAt, pending.attempts]
  );
  return pending;
}

export async function verifyCode(email: string, code: string): Promise<{ status: "ok" | "invalid" | "expired" | "max_attempts" }> {
  const cleanEmail = email.trim().toLowerCase();
  const result = await pool.query("SELECT * FROM pending_verifications WHERE email = $1", [cleanEmail]);
  const pending = result.rows[0];

  if (!pending) return { status: "invalid" };

  if (new Date() > new Date(pending.expires_at)) {
    await pool.query("DELETE FROM pending_verifications WHERE email = $1", [cleanEmail]);
    return { status: "expired" };
  }

  if (pending.attempts >= MAX_ATTEMPTS) {
    await pool.query("DELETE FROM pending_verifications WHERE email = $1", [cleanEmail]);
    return { status: "max_attempts" };
  }

  await pool.query("UPDATE pending_verifications SET attempts = attempts + 1 WHERE email = $1", [cleanEmail]);

  const a = Buffer.from(pending.code, "utf8"); const b = Buffer.from(code, "utf8"); const codeMatch = a.length === b.length && timingSafeEqual(a, b);
  if (!codeMatch) {
    return { status: "invalid" };
  }

  await pool.query("DELETE FROM pending_verifications WHERE email = $1", [cleanEmail]);
  return { status: "ok" };
}

export async function isEmailVerified(email: string): Promise<boolean> {
  const result = await pool.query(
    "SELECT 1 FROM verifications WHERE email = $1 AND verified_at IS NOT NULL LIMIT 1",
    [email]
  );
  return result.rows.length > 0;
}

export async function getVerifiedApiKey(email: string): Promise<string | null> {
  const result = await pool.query(
    "SELECT api_key FROM verifications WHERE email = $1 AND verified_at IS NOT NULL LIMIT 1",
    [email]
  );
  return result.rows[0]?.api_key ?? null;
}
