// SMS adapter for DewLink — Twilio REST API (poll for inbound + send).
// Emits the same @@DEWLINK@@ events as the other channels; each phone number = a conversation.
//
// Needs THREE values:
//   TWILIO_SID   (ACxxxx…) — Account SID, also the Basic-auth username
//   TWILIO_AUTH  — Auth Token, the Basic-auth password
//   TWILIO_FROM  (+1…)     — your Twilio phone number (the From on outbound)
//
// Twilio has no realtime push without a public webhook, so we poll the Messages list API and emit
// any inbound SMS we haven't seen. Outbound goes via POST Messages.json.

import { emitEvent } from "./events.ts";

const SID = process.env.TWILIO_SID || "";
const AUTH = process.env.TWILIO_AUTH || "";
const FROM = process.env.TWILIO_FROM || "";
const BASE = `https://api.twilio.com/2010-04-01/Accounts/${SID}`;
// Adaptive poll: tight when texts are flowing, backs off to 60s when idle (fewer Twilio API calls).
const POLL_MIN = 15000;
const POLL_MAX = 60000;
let pollInterval = POLL_MIN;

let running = false;
let timer: ReturnType<typeof setTimeout> | null = null;
const seen = new Set<string>();   // Twilio message SIDs already emitted

function log(level: "info" | "warn" | "error", msg: string) {
  emitEvent({ type: "log", level, msg: `[sms] ${msg}` });
}

function auth(): string {
  return "Basic " + Buffer.from(`${SID}:${AUTH}`).toString("base64");
}

export async function startTwilio() {
  if (!SID || !AUTH || !FROM) {
    log("error", "missing TWILIO_SID / TWILIO_AUTH / TWILIO_FROM");
    emitEvent({ type: "status", state: "logged_out" });
    return;
  }
  running = true;
  emitEvent({ type: "status", state: "connecting" });
  // Validate credentials once (fetch the account) before declaring the channel open.
  try {
    const res = await fetch(`${BASE}.json`, { headers: { Authorization: auth() } });
    if (res.status === 401) { log("error", "invalid Twilio credentials"); emitEvent({ type: "status", state: "logged_out" }); return; }
    emitEvent({ type: "status", state: "open", user: FROM });
  } catch (e: any) {
    log("warn", `account check failed: ${e?.message}`);
  }
  // Seed `seen` with the current inbound history (and backfill it into the inbox), then poll for new.
  await poll(true);
  loop();
}

function loop() {
  if (!running) return;
  timer = setTimeout(async () => {
    const before = seen.size;
    await poll(false);
    pollInterval = seen.size > before ? POLL_MIN : Math.min(Math.round(pollInterval * 1.5), POLL_MAX);
    loop();
  }, pollInterval);
}

async function poll(initial: boolean) {
  try {
    // Inbound messages addressed to our Twilio number, newest first.
    const url = `${BASE}/Messages.json?To=${encodeURIComponent(FROM)}&PageSize=50`;
    const res = await fetch(url, { headers: { Authorization: auth() } });
    if (!res.ok) { if (res.status === 401) emitEvent({ type: "status", state: "logged_out" }); return; }
    const data = await res.json() as any;
    const msgs: any[] = data?.messages ?? [];
    // Oldest-first so the thread builds in order.
    for (const m of msgs.reverse()) {
      if (m.direction && !String(m.direction).startsWith("inbound")) continue;
      if (!m.sid || seen.has(m.sid)) continue;
      seen.add(m.sid);
      const from = m.from || "unknown";
      emitEvent({
        type: "message",
        convExternalId: from,                 // the other party's phone number = the conversation key
        convTitle: from,
        direction: "IN",
        body: m.body || "",
        sender: from,
        ts: m.date_sent ? new Date(m.date_sent).getTime() : Date.now(),
        providerMsgId: `sms:${m.sid}`,
        historical: initial,                  // first sweep = backfill (don't inflate unread)
      });
    }
  } catch (e: any) {
    log("warn", `poll failed: ${e?.message}`);
  }
}

export async function sendTwilio(toNumber: string, text: string) {
  const body = new URLSearchParams({ From: FROM, To: toNumber, Body: text });
  const res = await fetch(`${BASE}/Messages.json`, {
    method: "POST",
    headers: { Authorization: auth(), "Content-Type": "application/x-www-form-urlencoded" },
    body: body.toString(),
  });
  if (!res.ok) throw new Error(`twilio send ${res.status}`);
  return res.json();
}

export function stopTwilio() { running = false; if (timer) clearTimeout(timer); }
