import { Router, type Request, type Response } from "express";
import { randomBytes, randomUUID } from "crypto";
import { verifyEvent, validateEvent } from "nostr-tools";
import { db, nostrTrustVouches, nostrIdentities, timmyNostrEvents } from "@workspace/db";
import { eq, count } from "drizzle-orm";
import { trustService } from "../lib/trust.js";
import { timmyIdentityService } from "../lib/timmy-identity.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)
//   event.tags    — optional: include ["lud16", "user@domain.com"] to store
//                   a lightning address for future zap-out payments

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);

    // ── Optional: persist lightning address from lud16 tag ────────────────
    // Allows ZapService to resolve LNURL-pay invoices for this identity.
    const tags = Array.isArray(ev["tags"]) ? ev["tags"] as unknown[][] : [];
    const lud16Tag = tags.find(t => Array.isArray(t) && t[0] === "lud16" && typeof t[1] === "string");
    if (lud16Tag) {
      const lightningAddress = lud16Tag[1] as string;
      const lnAddrRe = /^[^@\s]+@[^@\s]+\.[^@\s]+$/;
      if (lnAddrRe.test(lightningAddress)) {
        await db
          .update(nostrIdentities)
          .set({ lightningAddress, updatedAt: new Date() })
          .where(eq(nostrIdentities.pubkey, pubkey));
        logger.info("lightning address stored", { pubkey: pubkey.slice(0, 8), lightningAddress });
      }
    }

    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/timmy ───────────────────────────────────────────────────────
// Returns Timmy's own Nostr identity (npub) and outbound zap statistics.
// Public endpoint — no authentication required.

router.get("/identity/timmy", async (_req: Request, res: Response) => {
  try {
    const zapRows = await db
      .select({ count: count() })
      .from(timmyNostrEvents)
      .where(eq(timmyNostrEvents.kind, 9734));

    const zapCount = zapRows[0]?.count ?? 0;

    res.json({
      npub: timmyIdentityService.npub,
      pubkeyHex: timmyIdentityService.pubkeyHex,
      zapCount,
      relayUrl: process.env["NOSTR_RELAY_URL"] ?? null,
    });
  } catch (err) {
    res.status(500).json({ error: err instanceof Error ? err.message : "Failed to fetch Timmy identity" });
  }
});

// ── POST /identity/vouch ──────────────────────────────────────────────────────
// Allows an elite-tier identity to co-sign a new pubkey, granting it a trust
// boost. The voucher MUST provide a signed Nostr event that explicitly tags the
// voucheePubkey in a "p" tag, proving the co-signing is intentional.
//
// Requires: X-Nostr-Token with elite tier.
// Body: {
//   voucheePubkey: string,       — 64-char hex pubkey of the identity to vouch for
//   event: NostrSignedEvent      — REQUIRED: voucher's signed kind-1 (or 9735) event
//                                   containing a ["p", voucheePubkey] tag
// }

const VOUCH_TRUST_BOOST = 20;

router.post("/identity/vouch", 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: "X-Nostr-Token header required" });
    return;
  }

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

  const tier = await trustService.getTier(parsed.pubkey);
  if (tier !== "elite") {
    res.status(403).json({ error: "Vouching requires elite trust tier" });
    return;
  }

  const { voucheePubkey, event } = req.body as {
    voucheePubkey?: unknown;
    event?: unknown;
  };

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

  if (voucheePubkey === parsed.pubkey) {
    res.status(400).json({ error: "Cannot vouch for yourself" });
    return;
  }

  // ── event is REQUIRED — voucher must produce a signed Nostr event ─────────
  if (!event || typeof event !== "object") {
    res.status(400).json({
      error: "event is required: include a signed Nostr event with a [\"p\", voucheePubkey] tag",
    });
    return;
  }

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

  let valid = false;
  try { valid = verifyEvent(event as Parameters<typeof verifyEvent>[0]); } catch { valid = false; }
  if (!valid) {
    res.status(401).json({ error: "Nostr signature verification failed" });
    return;
  }

  const ev = event as Record<string, unknown>;

  // ── event.pubkey must match the authenticated voucher ────────────────────
  if (ev["pubkey"] !== parsed.pubkey) {
    res.status(400).json({ error: "event.pubkey does not match X-Nostr-Token identity" });
    return;
  }

  // ── event must contain a ["p", voucheePubkey] tag — binding co-signature ─
  // This proves the voucher intentionally named the vouchee in their signed event.
  const tags = Array.isArray(ev["tags"]) ? ev["tags"] as unknown[][] : [];
  const pTag = tags.find(
    t => Array.isArray(t) && t[0] === "p" && t[1] === voucheePubkey
  );
  if (!pTag) {
    res.status(400).json({
      error: `event must include a ["p", "${voucheePubkey}"] tag to bind the co-signature`,
    });
    return;
  }

  // ── Extract event ID for replay guard ────────────────────────────────────
  const eventId = typeof ev["id"] === "string" ? ev["id"] : randomUUID();

  try {
    // Upsert vouchee identity so it exists before applying boost
    await trustService.getOrCreate(voucheePubkey);

    // Idempotent insert — unique constraints on (voucher, vouchee) pair AND
    // event_id prevent both duplicate vouches and event replay attacks.
    // onConflictDoNothing().returning() returns [] on conflict: no boost applied.
    const inserted = await db
      .insert(nostrTrustVouches)
      .values({
        id: randomUUID(),
        voucherPubkey: parsed.pubkey,
        voucheePubkey,
        eventId,
        vouchEventJson: JSON.stringify(event),
        trustBoost: VOUCH_TRUST_BOOST,
      })
      .onConflictDoNothing()
      .returning({ id: nostrTrustVouches.id });

    if (inserted.length === 0) {
      // Duplicate — either same pair already vouched or event replayed.
      // Return existing state without applying another boost.
      const voucheeIdentity = await trustService.getIdentityWithDecay(voucheePubkey);
      logger.info("vouch duplicate — no additional boost applied", {
        voucher: parsed.pubkey.slice(0, 8),
        vouchee: voucheePubkey.slice(0, 8),
      });
      res.status(409).json({
        ok: false,
        duplicate: true,
        message: "Already vouched for this identity — co-signature recorded but no additional boost applied",
        voucheePubkey,
        currentScore: voucheeIdentity?.trustScore,
        currentTier: voucheeIdentity?.tier,
      });
      return;
    }

    // First-time vouch: apply the one-time trust boost
    const currentRows = await db
      .select({ score: nostrIdentities.trustScore })
      .from(nostrIdentities)
      .where(eq(nostrIdentities.pubkey, voucheePubkey));
    const currentScore = currentRows[0]?.score ?? 0;

    await db
      .update(nostrIdentities)
      .set({ trustScore: currentScore + VOUCH_TRUST_BOOST, updatedAt: new Date() })
      .where(eq(nostrIdentities.pubkey, voucheePubkey));

    logger.info("vouch recorded", {
      voucher: parsed.pubkey.slice(0, 8),
      vouchee: voucheePubkey.slice(0, 8),
      boost: VOUCH_TRUST_BOOST,
    });

    const voucheeIdentity = await trustService.getIdentityWithDecay(voucheePubkey);

    res.json({
      ok: true,
      voucheePubkey,
      trustBoost: VOUCH_TRUST_BOOST,
      newScore: voucheeIdentity?.trustScore,
      newTier: voucheeIdentity?.tier,
    });
  } catch (err) {
    const message = err instanceof Error ? err.message : "Failed to record vouch";
    logger.error("vouch 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;
