import { Router, type Request, type Response } from "express";
import { randomBytes } from "crypto";
import { verifyEvent, validateEvent } from "nostr-tools";
import { trustService } from "../lib/trust.js";
import { makeLogger } from "../lib/logger.js";

const logger = makeLogger("identity");
const router = Router();

// ── In-memory nonce store (TTL = 5 minutes) ───────────────────────────────────
// Nonces are single-use: consumed on first successful verify.

const NONCE_TTL_MS = 5 * 60 * 1000;

interface ChallengeEntry {
  nonce: string;
  expiresAt: number;
}

const challenges = new Map<string, ChallengeEntry>();

// Cleanup stale nonces periodically
setInterval(() => {
  const now = Date.now();
  for (const [key, entry] of challenges) {
    if (now > entry.expiresAt) challenges.delete(key);
  }
}, 60_000);

// ── POST /identity/challenge ──────────────────────────────────────────────────
// Returns a time-limited nonce the client must sign with their Nostr key.

router.post("/identity/challenge", (_req: Request, res: Response) => {
  const nonce = randomBytes(32).toString("hex");
  const expiresAt = Date.now() + NONCE_TTL_MS;
  challenges.set(nonce, { nonce, expiresAt });

  res.json({
    nonce,
    expiresAt: new Date(expiresAt).toISOString(),
  });
});

// ── POST /identity/verify ─────────────────────────────────────────────────────
// Accepts a NIP-01 signed event whose `content` field contains the nonce.
// Verifies the signature, consumes the nonce, upserts the identity row,
// and returns a signed `nostr_token` valid for 24 h.
//
// Body: { event: NostrEvent }
//   event.pubkey  — 64-char hex pubkey
//   event.content — the nonce returned by /identity/challenge
//   event.kind    — any (27235 recommended per NIP-98, but not enforced)

router.post("/identity/verify", async (req: Request, res: Response) => {
  // Accept both { event } and { pubkey, event } shapes (pubkey is optional but asserted if present)
  const { event, pubkey: explicitPubkey } = req.body as { event?: unknown; pubkey?: unknown };

  if (!event || typeof event !== "object") {
    res.status(400).json({ error: "Body must include 'event' (Nostr signed event)" });
    return;
  }

  // If caller provided a top-level pubkey, validate it matches event.pubkey
  if (explicitPubkey !== undefined) {
    if (typeof explicitPubkey !== "string" || !/^[0-9a-f]{64}$/.test(explicitPubkey)) {
      res.status(400).json({ error: "top-level 'pubkey' must be a 64-char hex string" });
      return;
    }
  }

  // ── Validate event structure ──────────────────────────────────────────────
  const ev = event as Record<string, unknown>;
  const pubkey = ev["pubkey"];
  const content = ev["content"];

  if (typeof pubkey !== "string" || !/^[0-9a-f]{64}$/.test(pubkey)) {
    res.status(400).json({ error: "event.pubkey must be a 64-char hex string" });
    return;
  }

  // Assert top-level pubkey matches event.pubkey if both are provided
  if (typeof explicitPubkey === "string" && explicitPubkey !== pubkey) {
    res.status(400).json({ error: "top-level 'pubkey' does not match event.pubkey" });
    return;
  }

  if (typeof content !== "string" || content.trim().length === 0) {
    res.status(400).json({ error: "event.content must be the nonce string" });
    return;
  }

  const nonce = content.trim();

  // ── Check nonce ──────────────────────────────────────────────────────────
  const entry = challenges.get(nonce);
  if (!entry) {
    res.status(401).json({ error: "Nonce not found or already consumed. Request a new challenge." });
    return;
  }
  if (Date.now() > entry.expiresAt) {
    challenges.delete(nonce);
    res.status(401).json({ error: "Nonce expired. Request a new challenge." });
    return;
  }

  // ── Verify Nostr signature ────────────────────────────────────────────────
  if (!validateEvent(ev as Parameters<typeof validateEvent>[0])) {
    res.status(401).json({ error: "Invalid Nostr event structure" });
    return;
  }

  let valid = false;
  try {
    valid = verifyEvent(ev as Parameters<typeof verifyEvent>[0]);
  } catch {
    valid = false;
  }

  if (!valid) {
    res.status(401).json({ error: "Nostr signature verification failed" });
    return;
  }

  // ── Consume nonce (single-use) ────────────────────────────────────────────
  challenges.delete(nonce);

  // ── Upsert identity & issue token ────────────────────────────────────────
  try {
    const identity = await trustService.getOrCreate(pubkey);
    const token = trustService.issueToken(pubkey);
    const tierInfo = await trustService.getIdentityWithDecay(pubkey);

    logger.info("identity verified", {
      pubkey: pubkey.slice(0, 8),
      tier: identity.tier,
      score: identity.trustScore,
    });

    res.json({
      pubkey,
      nostr_token: token,
      trust: {
        tier: tierInfo?.tier ?? identity.tier,
        score: tierInfo?.trustScore ?? identity.trustScore,
        interactionCount: identity.interactionCount,
        memberSince: identity.createdAt.toISOString(),
      },
    });
  } catch (err) {
    const message = err instanceof Error ? err.message : "Failed to upsert identity";
    logger.error("identity verify failed", { error: message });
    res.status(500).json({ error: message });
  }
});

// ── GET /identity/me ──────────────────────────────────────────────────────────
// Look up the trust profile for a verified nostr_token.

router.get("/identity/me", async (req: Request, res: Response) => {
  const raw = req.headers["x-nostr-token"];
  const token = typeof raw === "string" ? raw.trim() : null;

  if (!token) {
    res.status(401).json({ error: "Missing X-Nostr-Token header" });
    return;
  }

  const parsed = trustService.verifyToken(token);
  if (!parsed) {
    res.status(401).json({ error: "Invalid or expired nostr_token" });
    return;
  }

  try {
    const identity = await trustService.getIdentityWithDecay(parsed.pubkey);
    if (!identity) {
      res.status(404).json({ error: "Identity not found" });
      return;
    }

    res.json({
      pubkey: identity.pubkey,
      trust: {
        tier: identity.tier,
        score: identity.trustScore,
        interactionCount: identity.interactionCount,
        satsAbsorbedToday: identity.satsAbsorbedToday,
        memberSince: identity.createdAt.toISOString(),
        lastSeen: identity.lastSeen.toISOString(),
      },
    });
  } catch (err) {
    res.status(500).json({ error: err instanceof Error ? err.message : "Failed to fetch identity" });
  }
});

export default router;
