import fs from "node:fs/promises";
import path from "node:path";
import type { ChatRole, Prisma } from "@prisma/client";
import { prisma } from "../utils/prisma";
import { datasetRoot } from "../dataset/paths";
import { ensureDataset } from "../dataset/exporter";
import { runLangGraphCrmAgentFor } from "./langgraphCrmAgent";
import type { ChangeSet } from "../utils/changeSet";

type ContactIndexRow = {
  id: string;
  name: string;
  company: string | null;
  lastMessageAt: string | null;
  nextEventAt: string | null;
  updatedAt: string;
};

export type AgentReply = {
  text: string;
  plan: string[];
  tools: string[];
  thinking?: string[];
  toolRuns?: Array<{
    name: string;
    status: "ok" | "error";
    input: string;
    output: string;
    at: string;
  }>;
  dbWrites?: Array<{ kind: string; detail: string }>;
};

export type AgentTraceEvent = {
  text: string;
  toolRun?: {
    name: string;
    status: "ok" | "error";
    input: string;
    output: string;
    at: string;
  };
};

function normalize(s: string) {
  return s.trim().toLowerCase();
}

function isToday(date: Date) {
  const now = new Date();
  return (
    date.getFullYear() === now.getFullYear() &&
    date.getMonth() === now.getMonth() &&
    date.getDate() === now.getDate()
  );
}

async function readContactIndex(): Promise<ContactIndexRow[]> {
  throw new Error("readContactIndex now requires dataset root");
}

async function readContactIndexFrom(root: string): Promise<ContactIndexRow[]> {
  const p = path.join(root, "index", "contacts.json");
  const raw = await fs.readFile(p, "utf8");
  return JSON.parse(raw);
}

async function countJsonlLines(p: string): Promise<number> {
  const raw = await fs.readFile(p, "utf8");
  if (!raw.trim()) return 0;
  // cheap line count (JSONL is 1 item per line)
  return raw.trimEnd().split("\n").length;
}

async function readJsonl(p: string): Promise<any[]> {
  const raw = await fs.readFile(p, "utf8");
  if (!raw.trim()) return [];
  return raw
    .trimEnd()
    .split("\n")
    .filter(Boolean)
    .map((line) => JSON.parse(line));
}

function formatContactLine(c: ContactIndexRow) {
  const company = c.company ? ` (${c.company})` : "";
  const lastAt = c.lastMessageAt ? new Date(c.lastMessageAt).toLocaleString("ru-RU") : "нет";
  return `- ${c.name}${company} · последнее: ${lastAt}`;
}

export async function runCrmAgent(userText: string): Promise<AgentReply> {
  throw new Error("runCrmAgent now requires auth context");
}

export async function runCrmAgentFor(
  input: {
    teamId: string;
    userId: string;
    userText: string;
    requestId?: string;
    conversationId?: string;
    onTrace?: (event: AgentTraceEvent) => Promise<void> | void;
  },
): Promise<AgentReply> {
  const mode = (process.env.CF_AGENT_MODE ?? "langgraph").toLowerCase();
  const llmApiKey =
    process.env.OPENROUTER_API_KEY ||
    process.env.LLM_API_KEY ||
    process.env.OPENAI_API_KEY ||
    process.env.DASHSCOPE_API_KEY ||
    process.env.QWEN_API_KEY;
  const hasGigachat = Boolean((process.env.GIGACHAT_AUTH_KEY ?? "").trim() && (process.env.GIGACHAT_SCOPE ?? "").trim());

  if (mode !== "rule") {
    return runLangGraphCrmAgentFor(input);
  }

  if (!llmApiKey && !hasGigachat) {
    throw new Error("LLM API key is not configured. Set OPENROUTER_API_KEY or GIGACHAT_AUTH_KEY/GIGACHAT_SCOPE.");
  }

  await ensureDataset({ teamId: input.teamId, userId: input.userId });
  const q = normalize(input.userText);
  const root = datasetRoot({ teamId: input.teamId, userId: input.userId });
  const contacts = await readContactIndexFrom(root);

  // "10 лучших клиентов"
  if (q.includes("10 лучших") || (q.includes("топ") && q.includes("клиент"))) {
    const ranked = await Promise.all(
      contacts.map(async (c) => {
        const msgPath = path.join(root, "messages", `${c.id}.jsonl`);
        const evPath = path.join(root, "events", `${c.id}.jsonl`);
        const msgCount = await countJsonlLines(msgPath).catch(() => 0);
        const ev = await readJsonl(evPath).catch(() => []);
        const todayEvCount = ev.filter((e) => (e?.startsAt ? isToday(new Date(e.startsAt)) : false)).length;
        const score = msgCount * 2 + todayEvCount * 3;
        return { c, score };
      }),
    );

    ranked.sort((a, b) => b.score - a.score);
    const top = ranked.slice(0, 10).map((x) => x.c);

    return {
      plan: [
        "Загрузить индекс контактов из файлового датасета",
        "Посчитать активность по JSONL (сообщения/события сегодня)",
        "Отсортировать и показать топ",
      ],
      tools: ["read index/contacts.json", "read messages/{contactId}.jsonl", "read events/{contactId}.jsonl"],
      toolRuns: [
        {
          name: "dataset:index_contacts",
          status: "ok",
          input: "index/contacts.json",
          output: "Loaded contacts index",
          at: new Date().toISOString(),
        },
      ],
      text:
        `Топ-10 по активности (сообщения + события):\n` +
        top.map(formatContactLine).join("\n") +
        `\n\nЕсли хочешь, скажи критерий "лучший" (выручка/стадия/вероятность/давность) и я пересчитаю.`,
    };
  }

  // "чем заняться сегодня"
  if (q.includes("чем") && (q.includes("сегодня") || q.includes("заняться"))) {
    const todayEvents: Array<{ who: string; title: string; at: Date; note?: string | null }> = [];

    for (const c of contacts) {
      const evPath = path.join(root, "events", `${c.id}.jsonl`);
      const ev = await readJsonl(evPath).catch(() => []);
      for (const e of ev) {
        if (!e?.startsAt) continue;
        const at = new Date(e.startsAt);
        if (!isToday(at)) continue;
        todayEvents.push({ who: c.name, title: e.title ?? "Event", at, note: e.note ?? null });
      }
    }

    todayEvents.sort((a, b) => a.at.getTime() - b.at.getTime());

    const followups = [...contacts]
      .map((c) => ({ c, last: c.lastMessageAt ? new Date(c.lastMessageAt).getTime() : 0 }))
      .sort((a, b) => a.last - b.last)
      .slice(0, 3)
      .map((x) => x.c);

    const lines: string[] = [];
    if (todayEvents.length > 0) {
      lines.push("Сегодня по календарю:");
      for (const e of todayEvents) {
        const hhmm = e.at.toLocaleTimeString("ru-RU", { hour: "2-digit", minute: "2-digit" });
        lines.push(`- ${hhmm} · ${e.title} · ${e.who}${e.note ? ` · ${e.note}` : ""}`);
      }
    } else {
      lines.push("Сегодня нет запланированных событий в календаре.");
    }

    lines.push("");
    lines.push("Фокус дня (если нужно добить прогресс):");
    for (const c of followups) {
      lines.push(`- Написать follow-up: ${c.name}${c.company ? ` (${c.company})` : ""}`);
    }

    return {
      plan: [
        "Прочитать события на сегодня из файлового датасета",
        "Найти контакты без свежего касания (по lastMessageAt)",
        "Сформировать короткий список действий",
      ],
      tools: ["read index/contacts.json", "read events/{contactId}.jsonl"],
      toolRuns: [
        {
          name: "dataset:query_events",
          status: "ok",
          input: "events/*.jsonl (today)",
          output: `Found ${todayEvents.length} events`,
          at: new Date().toISOString(),
        },
      ],
      text: lines.join("\n"),
    };
  }

  throw new Error(
    "Rule mode supports only structured built-in queries. Use a supported query or switch to langgraph mode with a configured LLM API key.",
  );
}

export async function persistChatMessage(input: {
  role: ChatRole;
  text: string;
  plan?: string[];
  tools?: string[];
  thinking?: string[];
  toolRuns?: Array<{
    name: string;
    status: "ok" | "error";
    input: string;
    output: string;
    at: string;
  }>;
  changeSet?: ChangeSet | null;
  requestId?: string;
  eventType?: "user" | "trace" | "assistant" | "note";
  phase?: "pending" | "running" | "final" | "error";
  transient?: boolean;
  messageKind?: "change_set_summary";
  teamId: string;
  conversationId: string;
  authorUserId?: string | null;
}) {
  const hasStoredPayload = Boolean(input.changeSet || input.messageKind);
  const data: Prisma.ChatMessageCreateInput = {
    team: { connect: { id: input.teamId } },
    conversation: { connect: { id: input.conversationId } },
    authorUser: input.authorUserId ? { connect: { id: input.authorUserId } } : undefined,
    role: input.role,
    text: input.text,
    planJson: hasStoredPayload
      ? ({
          messageKind: input.messageKind ?? null,
          changeSet: input.changeSet ?? null,
        } as any)
      : undefined,
  };
  return prisma.chatMessage.create({ data });
}
