/**
 * engagement.ts — Proactive engagement engine for Timmy.
 *
 * When enabled (ENGAGEMENT_INTERVAL_DAYS > 0), runs on a configurable
 * schedule and sends a Nostr DM (NIP-04 encrypted, kind 4) to trusted
 * partners who have been absent for ENGAGEMENT_ABSENT_DAYS days.
 *
 * Events are signed with Timmy's key and logged to timmy_nostr_events.
 * Broadcasting to a Nostr relay requires NOSTR_RELAY_URL to be set.
 *
 * Configuration (env vars):
 *   ENGAGEMENT_INTERVAL_DAYS — how often to run the check (default 0 = disabled)
 *   ENGAGEMENT_ABSENT_DAYS   — absence threshold to trigger a DM (default 7)
 *   NOSTR_RELAY_URL          — relay URL; if set, events are POSTed there
 */

import { randomUUID } from "crypto";
import { db, nostrIdentities, timmyNostrEvents } from "@workspace/db";
import { and, gte, lt } from "drizzle-orm";
import { timmyIdentityService } from "./timmy-identity.js";
import { agentService } from "./agent.js";
import { makeLogger } from "./logger.js";

const logger = makeLogger("engagement");

function envInt(name: string, fallback: number): number {
  const v = parseInt(process.env[name] ?? "", 10);
  return Number.isFinite(v) && v >= 0 ? v : fallback;
}

const ENGAGEMENT_INTERVAL_DAYS = envInt("ENGAGEMENT_INTERVAL_DAYS", 0);
const ENGAGEMENT_ABSENT_DAYS   = envInt("ENGAGEMENT_ABSENT_DAYS", 7);
const RELAY_URL                = process.env["NOSTR_RELAY_URL"] ?? "";
const MIN_TIER_SCORES          = { trusted: 50, elite: 200 } as const;

async function sendDm(recipientPubkey: string, plaintext: string): Promise<string> {
  const ciphertext = await timmyIdentityService.encryptDm(recipientPubkey, plaintext);

  const event = timmyIdentityService.sign({
    kind: 4,
    created_at: Math.floor(Date.now() / 1000),
    tags: [["p", recipientPubkey]],
    content: ciphertext,
  });

  await db.insert(timmyNostrEvents).values({
    id: randomUUID(),
    kind: 4,
    recipientPubkey,
    eventJson: JSON.stringify(event),
    amountSats: null,
    relayUrl: RELAY_URL || null,
  });

  if (RELAY_URL) {
    try {
      const r = await fetch(RELAY_URL, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(["EVENT", event]),
        signal: AbortSignal.timeout(10_000),
      });
      if (!r.ok) {
        logger.warn("relay rejected DM event", { status: r.status });
      } else {
        logger.info("DM event published to relay", { relay: RELAY_URL });
      }
    } catch (err) {
      logger.warn("relay publish failed", { error: err instanceof Error ? err.message : String(err) });
    }
  }

  return event.id;
}

async function runEngagementCycle(): Promise<void> {
  logger.info("engagement cycle started", { absentDays: ENGAGEMENT_ABSENT_DAYS });

  const threshold = new Date(Date.now() - ENGAGEMENT_ABSENT_DAYS * 24 * 60 * 60 * 1000);

  const absentPartners = await db
    .select({ pubkey: nostrIdentities.pubkey, trustScore: nostrIdentities.trustScore })
    .from(nostrIdentities)
    .where(
      and(
        gte(nostrIdentities.trustScore, MIN_TIER_SCORES.trusted),
        lt(nostrIdentities.lastSeen, threshold),
      ),
    )
    .limit(20);

  logger.info("absent trusted partners found", { count: absentPartners.length });

  for (const partner of absentPartners) {
    try {
      const prompt =
        `Write a short, friendly Nostr DM from Timmy (a whimsical wizard AI agent who believes in open, self-hostable AI — not walled gardens) to a trusted partner ` +
        `who hasn't visited the workshop in ${ENGAGEMENT_ABSENT_DAYS}+ days. ` +
        `Keep it under 120 characters, warm but not pushy. No emoji overload. ` +
        `If it fits naturally, you can hint at the open-source or self-sovereign nature of Timmy's magic.`;

      const message = await agentService.chatReply(prompt);

      const eventId = await sendDm(partner.pubkey, message);
      logger.info("proactive DM sent", {
        to: partner.pubkey.slice(0, 8),
        eventId,
      });
    } catch (err) {
      logger.warn("proactive DM failed for partner", {
        pubkey: partner.pubkey.slice(0, 8),
        error: err instanceof Error ? err.message : String(err),
      });
    }
  }

  logger.info("engagement cycle complete", { processed: absentPartners.length });
}

export function startEngagementEngine(): void {
  if (ENGAGEMENT_INTERVAL_DAYS <= 0) {
    logger.info("proactive engagement disabled (set ENGAGEMENT_INTERVAL_DAYS > 0 to enable)");
    return;
  }

  const intervalMs = ENGAGEMENT_INTERVAL_DAYS * 24 * 60 * 60 * 1000;
  logger.info("proactive engagement engine started", {
    intervalDays: ENGAGEMENT_INTERVAL_DAYS,
    absentDays: ENGAGEMENT_ABSENT_DAYS,
    relay: RELAY_URL || "none",
  });

  const run = () => {
    runEngagementCycle().catch(err => {
      logger.error("engagement cycle error", { error: err instanceof Error ? err.message : String(err) });
    });
  };

  setTimeout(run, 60_000);
  setInterval(run, intervalMs);
}
