/**
 * Flow Engine — maquina de estado que executa flow.json bloco a bloco.
 *
 * Funcao principal: dado um bloco atual + resposta do usuario,
 * determina o proximo bloco e os efeitos colaterais (set var, salvar msg, etc).
 */

import type { Flow, Block } from "./types/flow.js";
import {
  type DB,
  setVar,
  getVars,
  appendMessage,
  updateSessionBlock,
  completeSession,
  logEvent,
} from "./database.js";
import { interpolate } from "./services/interpolator.js";
import { runAiTurn, type AiTurnResult } from "./services/ai.js";
import { callWebhook } from "./services/webhook.js";

export type EngineStep =
  | { kind: "render"; block: RenderableBlock }
  | { kind: "complete"; redirect?: { url: string } }
  | { kind: "ai_turn"; reply: string; intent?: string };

export type RenderableBlock = {
  id: string;
  type: string;
  data: Record<string, unknown>;
};

/**
 * Executa um bloco e devolve o que mostrar pro frontend.
 * Blocos de logica (set_var, condition, webhook) sao processados internamente
 * em loop ate alcancar um bloco "renderizavel" (texto, input, choice, ai, redirect).
 */
export async function advance(
  flow: Flow,
  db: DB,
  sessionId: string,
  startBlockId: string,
): Promise<EngineStep> {
  let current: Block | undefined = flow.blocks[startBlockId];
  if (!current) {
    throw new Error(`Bloco '${startBlockId}' nao existe`);
  }

  // Loop processando blocos de logica ate achar um renderizavel
  for (let i = 0; i < 50; i++) {
    if (!current) break;

    const vars = getVars(db, sessionId);
    updateSessionBlock(db, sessionId, current.id);
    logEvent(db, sessionId, "block_viewed", current.id);

    switch (current.type) {
      case "text": {
        const content = interpolate(current.content, vars);
        appendMessage(db, sessionId, "bot", content, current.id);
        return {
          kind: "render",
          block: {
            id: current.id,
            type: "text",
            data: { content, delay_ms: current.delay_ms ?? 600, next: current.next },
          },
        };
      }

      case "image": {
        return {
          kind: "render",
          block: {
            id: current.id,
            type: "image",
            data: { url: current.url, alt: current.alt, next: current.next },
          },
        };
      }

      case "video": {
        return {
          kind: "render",
          block: {
            id: current.id,
            type: "video",
            data: { url: current.url, autoplay: current.autoplay, next: current.next },
          },
        };
      }

      case "input_text":
      case "input_email":
      case "input_phone": {
        const prompt = interpolate(current.prompt, vars);
        appendMessage(db, sessionId, "bot", prompt, current.id);
        return {
          kind: "render",
          block: {
            id: current.id,
            type: current.type,
            data: {
              prompt,
              placeholder:
                current.type === "input_text" ? current.placeholder : undefined,
              var: current.var,
              validation:
                current.type === "input_text" ? current.validation : undefined,
              country_default:
                current.type === "input_phone" ? current.country_default : undefined,
            },
          },
        };
      }

      case "choice": {
        const prompt = interpolate(current.prompt, vars);
        appendMessage(db, sessionId, "bot", prompt, current.id);
        return {
          kind: "render",
          block: {
            id: current.id,
            type: "choice",
            data: {
              prompt,
              var: current.var,
              options: current.options,
            },
          },
        };
      }

      case "multi_choice": {
        const prompt = interpolate(current.prompt, vars);
        appendMessage(db, sessionId, "bot", prompt, current.id);
        return {
          kind: "render",
          block: {
            id: current.id,
            type: "multi_choice",
            data: {
              prompt,
              var_array: current.var_array,
              options: current.options,
            },
          },
        };
      }

      case "set_var": {
        setVar(db, sessionId, current.var, current.value);
        current = flow.blocks[current.next];
        continue;
      }

      case "condition": {
        const next = evaluateCondition(current, vars);
        current = flow.blocks[next];
        continue;
      }

      case "ai": {
        return {
          kind: "render",
          block: {
            id: current.id,
            type: "ai",
            data: {
              system_prompt: current.system_prompt,
              max_turns: current.max_turns,
              exit_intents: current.exit_intents,
              fallback: current.fallback,
              variables_to_extract: current.variables_to_extract,
            },
          },
        };
      }

      case "webhook": {
        try {
          const result = await callWebhook(current, vars);
          logEvent(db, sessionId, "webhook_called", current.id, {
            status: result.status,
            ok: result.ok,
          });
          current = flow.blocks[result.ok ? current.on_success : current.on_error];
        } catch (err) {
          logEvent(db, sessionId, "webhook_called", current.id, {
            error: (err as Error).message,
          });
          current = flow.blocks[current.on_error];
        }
        continue;
      }

      case "redirect": {
        const url = buildRedirectUrl(current, vars, sessionId);
        completeSession(db, sessionId);
        logEvent(db, sessionId, "session_completed", current.id, { redirect: url });
        return { kind: "complete", redirect: { url } };
      }
    }
  }

  // Se sair do loop sem terminal, marca abandoned
  logEvent(db, sessionId, "session_abandoned", startBlockId, {
    reason: "loop_limit_or_no_terminal",
  });
  return { kind: "complete" };
}

// ─── Helpers ────────────────────────────────────────────────

function evaluateCondition(
  block: Extract<Block, { type: "condition" }>,
  vars: Record<string, string>,
): string {
  for (const rule of block.rules) {
    const lhs = vars[rule.if.var];
    if (lhs === undefined) continue;
    if (matches(lhs, rule.if.op, rule.if.value)) {
      return rule.then;
    }
  }
  return block.else;
}

function matches(
  lhs: string,
  op: string,
  rhs: string | number | boolean | string[],
): boolean {
  switch (op) {
    case "eq":
      return String(lhs) === String(rhs);
    case "neq":
      return String(lhs) !== String(rhs);
    case "gt":
      return Number(lhs) > Number(rhs);
    case "lt":
      return Number(lhs) < Number(rhs);
    case "gte":
      return Number(lhs) >= Number(rhs);
    case "lte":
      return Number(lhs) <= Number(rhs);
    case "contains":
      return String(lhs).includes(String(rhs));
    case "in":
      return Array.isArray(rhs) && rhs.includes(String(lhs));
    default:
      return false;
  }
}

function buildRedirectUrl(
  block: Extract<Block, { type: "redirect" }>,
  vars: Record<string, string>,
  sessionId: string,
): string {
  const url = new URL(interpolate(block.url, vars));
  if (block.utm) {
    url.searchParams.set("utm_source", block.utm.source);
    url.searchParams.set("utm_medium", block.utm.medium);
    url.searchParams.set("utm_campaign", block.utm.campaign);
    if (block.utm.content) url.searchParams.set("utm_content", block.utm.content);
    url.searchParams.set("utm_term", block.utm.term ?? sessionId);
  }
  return url.toString();
}

/**
 * Processa uma resposta do usuario num bloco renderizado.
 * Retorna o ID do proximo bloco a avancar.
 */
export function resolveAnswer(
  flow: Flow,
  db: DB,
  sessionId: string,
  blockId: string,
  answer: { value?: string; values?: string[]; choice_index?: number },
): string {
  const block = flow.blocks[blockId];
  if (!block) throw new Error(`Bloco '${blockId}' nao existe`);

  appendMessage(
    db,
    sessionId,
    "user",
    answer.value ?? answer.values?.join(", ") ?? "",
    blockId,
  );
  logEvent(db, sessionId, "block_answered", blockId, answer);

  switch (block.type) {
    case "input_text":
    case "input_email":
    case "input_phone": {
      if (!answer.value) throw new Error("Resposta vazia");
      setVar(db, sessionId, block.var, answer.value);
      if (!block.next) throw new Error(`Bloco '${blockId}' sem 'next'`);
      return block.next;
    }

    case "choice": {
      const idx = answer.choice_index ?? 0;
      const option = block.options[idx];
      if (!option) throw new Error(`Opcao ${idx} invalida em '${blockId}'`);
      if (block.var) setVar(db, sessionId, block.var, option.value);
      return option.next;
    }

    case "multi_choice": {
      setVar(db, sessionId, block.var_array, answer.values ?? []);
      return block.next;
    }

    case "text":
    case "image":
    case "video": {
      if (!block.next) throw new Error(`Bloco '${blockId}' sem 'next'`);
      return block.next;
    }

    default:
      throw new Error(`Bloco '${blockId}' tipo '${block.type}' nao aceita resposta direta`);
  }
}

export async function handleAiTurn(
  flow: Flow,
  db: DB,
  sessionId: string,
  blockId: string,
  userMessage: string,
): Promise<{ reply: string; intent?: string; nextBlockId?: string }> {
  const block = flow.blocks[blockId];
  if (!block || block.type !== "ai") {
    throw new Error(`Bloco '${blockId}' nao e AI`);
  }

  appendMessage(db, sessionId, "user", userMessage, blockId);
  logEvent(db, sessionId, "block_answered", blockId, {
    type: "ai_turn",
    msg: userMessage,
  });

  const vars = getVars(db, sessionId);
  const turn: AiTurnResult = await runAiTurn({
    systemPrompt: block.system_prompt,
    history: getRecentAiHistory(db, sessionId, blockId),
    userMessage,
    vars,
    exitIntents: block.exit_intents.map((e) => e.intent),
  });

  appendMessage(db, sessionId, "bot", turn.reply, blockId);

  if (turn.extracted) {
    for (const [k, v] of Object.entries(turn.extracted)) {
      setVar(db, sessionId, k, v);
    }
  }

  if (turn.intent) {
    const exit = block.exit_intents.find((e) => e.intent === turn.intent);
    if (exit) return { reply: turn.reply, intent: turn.intent, nextBlockId: exit.next };
  }

  return { reply: turn.reply, intent: turn.intent };
}

function getRecentAiHistory(
  db: DB,
  sessionId: string,
  blockId: string,
): Array<{ role: "user" | "model"; content: string }> {
  const stmt = db.prepare(
    `SELECT role, content FROM messages
     WHERE session_id = ? AND block_id = ?
     ORDER BY id ASC LIMIT 20`,
  );
  const rows = stmt.all(sessionId, blockId) as Array<{
    role: string;
    content: string;
  }>;
  return rows.map((r) => ({
    role: r.role === "bot" ? ("model" as const) : ("user" as const),
    content: r.content,
  }));
}
