/**
 * 🤖 CANVAS CHATBOT ENGINE
 *
 * Executes the saved canvas tree in real-time for incoming WhatsApp messages.
 * Uses GPT-4o-mini to:
 *  - Evaluate condition branches
 *  - Generate AI response node replies
 */

import { prisma } from "./db";
import { sendMessage, sendButtons } from "./waha";
import { detectIntent, applyAppointmentIntent } from "./appointment-intent";

// ─── Template variable injection ──────────────────────────────────
function injectVars(template: string, vars: Record<string, string>): string {
  return template.replace(/\{\{(\w+)\}\}/g, (_, k) => vars[k] ?? `{{${k}}}`);
}

// ─── Build vars map from appointment + lead ───────────────────────
function buildVars(appointment: any): Record<string, string> {
  const tz = appointment.clinic?.timezone ?? "UTC";
  const dt = new Date(appointment.dateTime);
  return {
    name:      appointment.lead?.name?.split(" ")[0] ?? "there",
    treatment: appointment.treatment?.name ?? "treatment",
    clinic:    appointment.clinic?.name ?? "clinic",
    phone:     appointment.lead?.phone ?? "",
    date: new Intl.DateTimeFormat("en-US", { timeZone: tz, weekday: "long", month: "long", day: "numeric" }).format(dt),
    time: new Intl.DateTimeFormat("en-US", { timeZone: tz, hour: "numeric", minute: "2-digit", hour12: true }).format(dt),
  };
}

// ─── Execute a single tree node ───────────────────────────────────
export async function executeCanvasNode(
  nodeId: string,
  appointmentId: string,
  incomingMessage: string
): Promise<{ nextNodeId: string | null; reply: string | null }> {

  const node = await prisma.treeNode.findUnique({
    where: { id: nodeId },
    include: { children: { orderBy: { sortOrder: "asc" } } },
  });
  if (!node) return { nextNodeId: null, reply: null };

  const appointment = await prisma.appointment.findUnique({
    where: { id: appointmentId },
    include: { lead: true, clinic: true, treatment: true },
  });
  if (!appointment) return { nextNodeId: null, reply: null };

  const vars = buildVars(appointment);
  const phone = appointment.lead.phone;
  const clinicId = appointment.clinicId;

  // Helper: log outgoing message
  const logMsg = async (text: string, waha?: any) => {
    await prisma.messageLog.create({
      data: {
        clinicId,
        appointmentId,
        phone,
        direction: "outgoing",
        message: text,
        status: "sent",
        wahaMessageId: (waha as any)?.key?.id ?? null,
        sentAt: new Date(),
      },
    });
  };

  // ── SEND MESSAGE ──────────────────────────────────────────────
  if (node.nodeType === "message" && node.content) {
    const text = injectVars(node.content, vars);
    const waha = await sendMessage(phone, text);
    await logMsg(text, waha);
    const next = node.children[0] ?? null;
    return { nextNodeId: next?.id ?? null, reply: text };
  }

  // ── SEND BUTTONS ──────────────────────────────────────────────
  if (node.nodeType === "buttons" && node.content) {
    let cfg: { message: string; buttons: { id: string; text: string }[] };
    try { cfg = JSON.parse(node.content); } catch { return { nextNodeId: null, reply: null }; }

    const text = injectVars(cfg.message ?? "", vars);
    const waha = await sendButtons(phone, text, cfg.buttons.slice(0, 3), appointment.clinic.name);
    const label = cfg.buttons.map((b: any) => b.text).join(" | ");
    await logMsg(`${text}\n[Options: ${label}]`, waha);

    // Wait for response — next node selection happens when user replies
    return { nextNodeId: null, reply: text };
  }

  // ── AI RESPONSE — AI disabled, skip silently and advance ─────
  if (node.nodeType === "ai_response") {
    const next = node.children[0] ?? null;
    return { nextNodeId: next?.id ?? null, reply: null };
  }

  // ── CONDITION ─────────────────────────────────────────────────
  if (node.nodeType === "condition") {
    let branches: any[] = [];
    try {
      const raw = node.conditions ? JSON.parse(node.conditions) : [];
      branches = Array.isArray(raw) ? raw : [];
    } catch { /* no branches */ }

    const aiPromptRaw = node.content ?? "Classify the patient intent.";
    const aiPrompt    = injectVars(aiPromptRaw, vars);

    let chosenBranchId = "else"; // default

    // Keyword matching
    if (chosenBranchId === "else") {
      const lc = incomingMessage.toLowerCase();
      for (const br of branches) {
        const kws: string[] = br.keywords ?? [];
        if (kws.some((kw: string) => lc.includes(kw.toLowerCase()))) {
          chosenBranchId = br.id;
          break;
        }
      }
    }

    // Find matching child node (its parentId edge handle matches the branch id)
    // We store branch id in the node metadata.canvasId edge source handle
    // Simpler: children are ordered — find child whose sortOrder index matches branch index
    const branchIndex = branches.findIndex((b: any) => b.id === chosenBranchId);
    const nextNode = branchIndex >= 0
      ? node.children[branchIndex]
      : node.children[node.children.length - 1]; // else = last child

    return { nextNodeId: nextNode?.id ?? null, reply: null };
  }

  // ── WAIT ──────────────────────────────────────────────────────
  if (node.nodeType === "wait") {
    const delayMs = (node.offsetMinutes ?? 60) * 60_000;
    const nextNode = node.children[0];
    if (nextNode) {
      setTimeout(() => {
        continueJourneyFromNode(nextNode.id, appointmentId, "");
      }, delayMs);
    }
    return { nextNodeId: null, reply: null };
  }

  // ── END ───────────────────────────────────────────────────────
  if (node.nodeType === "end") {
    await prisma.userJourney.updateMany({
      where: { appointmentId, status: "active" },
      data: { status: "completed", completedAt: new Date() },
    });
    return { nextNodeId: null, reply: null };
  }

  return { nextNodeId: null, reply: null };
}

// ─── Continue journey after a user response ───────────────────────
export async function handleCanvasReply(
  appointmentId: string,
  userMessage: string
): Promise<boolean> {
  // Find active journey
  const journey = await prisma.userJourney.findFirst({
    where: { appointmentId, status: "active" },
    include: { currentNode: { include: { children: { orderBy: { sortOrder: "asc" } } } } },
  });
  if (!journey || !journey.currentNode) return false;

  const currentNode = journey.currentNode;

  // For buttons node: match button click
  if (currentNode.nodeType === "buttons" && currentNode.content) {
    try {
      const cfg = JSON.parse(currentNode.content);
      const btns: { id: string; text: string }[] = cfg.buttons ?? [];
      const lc = userMessage.toLowerCase();

      // Find matching button
      let matchedBtn = btns.find((b) =>
        b.id === userMessage || b.text.toLowerCase() === lc || lc.includes(b.text.toLowerCase())
      );

      // Number shortcut: "1", "2", "3"
      if (!matchedBtn) {
        const num = parseInt(userMessage.trim());
        if (!isNaN(num) && num >= 1 && num <= btns.length) {
          matchedBtn = btns[num - 1];
        }
      }

      // Find child node corresponding to matched button
      const children = currentNode.children;
      if (matchedBtn && children.length > 0) {
        // Buttons children are ordered by button order
        const btnIndex = btns.indexOf(matchedBtn);
        const nextChild = children[Math.min(btnIndex, children.length - 1)];

        // ── Auto-update appointment status from button choice ──────
        const intent = detectIntent(userMessage, nextChild.name ?? null);
        if (intent) await applyAppointmentIntent(appointmentId, intent);

        await prisma.userJourney.update({
          where: { id: journey.id },
          data: { currentNodeId: nextChild.id, lastActiveAt: new Date() },
        });
        await continueJourneyFromNode(nextChild.id, appointmentId, userMessage);
        return true;
      }
    } catch { /* fall through */ }
  }

  // For other interactive nodes, route through condition or continue
  const { nextNodeId } = await executeCanvasNode(currentNode.id, appointmentId, userMessage);

  // ── Auto-update appointment status from text reply ─────────────
  if (nextNodeId) {
    const nextNode = await prisma.treeNode.findUnique({ where: { id: nextNodeId } });
    const intent = detectIntent(userMessage, nextNode?.name ?? null);
    if (intent) await applyAppointmentIntent(appointmentId, intent);
  }

  if (nextNodeId) {
    await prisma.userJourney.update({
      where: { id: journey.id },
      data: { currentNodeId: nextNodeId, lastActiveAt: new Date() },
    });
    await continueJourneyFromNode(nextNodeId, appointmentId, userMessage);
  }
  return true;
}

// ─── Walk the tree until a node that waits for user input ─────────
async function continueJourneyFromNode(
  nodeId: string,
  appointmentId: string,
  incomingMessage: string,
  depth = 0
): Promise<void> {
  if (depth > 20) return; // safety

  const { nextNodeId } = await executeCanvasNode(nodeId, appointmentId, incomingMessage);

  if (nextNodeId) {
    await prisma.userJourney.updateMany({
      where: { appointmentId, status: "active" },
      data: { currentNodeId: nextNodeId, lastActiveAt: new Date() },
    });

    const nextNode = await prisma.treeNode.findUnique({ where: { id: nextNodeId } });
    // Auto-continue for non-interactive nodes
    if (nextNode && !["buttons", "wait"].includes(nextNode.nodeType)) {
      await continueJourneyFromNode(nextNodeId, appointmentId, incomingMessage, depth + 1);
    }
  }
}

// ─── Start a fresh journey from the first node of a tree ─────────
export async function startCanvasJourney(
  appointmentId: string,
  triggerType: string
): Promise<void> {
  const appointment = await prisma.appointment.findUnique({
    where: { id: appointmentId },
    include: { clinic: true },
  });
  if (!appointment) return;

  const tree = await prisma.reminderTree.findFirst({
    where: { clinicId: appointment.clinicId, triggerType, isActive: true },
  });
  if (!tree) return;

  // Root node = no parent
  const rootNode = await prisma.treeNode.findFirst({
    where: { treeId: tree.id, parentId: null },
    orderBy: { sortOrder: "asc" },
  });
  if (!rootNode) return;

  // Create journey
  const journey = await prisma.userJourney.create({
    data: {
      appointmentId,
      treeId: tree.id,
      currentNodeId: rootNode.id,
      status: "active",
    },
  });

  await continueJourneyFromNode(rootNode.id, appointmentId, "", 0);
}
