import { load } from "cheerio";
import { env } from "../../core/env.js";
import { logger } from "../../core/logger.js";

type SummarizeMode = "auto" | "fast" | "quality";
type SummarizeEngine = "ai" | "basic";

type SummarizeResult = {
  summary: string;
  model: string;
  title: string;
  sourceUrl: string;
  inputChars: number;
  callNo: number;
};

const stats = {
  totalCalls: 0,
  modelCalls: new Map<string, number>(),
  basicCalls: 0,
};

function chooseModel(mode: SummarizeMode, inputChars: number): string {
  if (mode === "fast") return env.GEMINI_FLASH_MODEL;
  if (mode === "quality") return env.GEMINI_PRO_MODEL;
  return inputChars > 8000 ? env.GEMINI_PRO_MODEL : env.GEMINI_FLASH_MODEL;
}

function cleanWhitespace(text: string): string {
  return String(text || "").replace(/\s+/g, " ").trim();
}

function extractMainText(html: string): { title: string; text: string } {
  const $ = load(html);
  $("script,style,noscript,iframe,svg,header,footer,nav,aside,form").remove();

  const title = cleanWhitespace($("title").first().text()) || "Untitled";

  const collect = (selector: string) =>
    $(selector)
      .map((_, el) => cleanWhitespace($(el).text()))
      .get()
      .filter((t) => t.length >= 15);

  const articleLines = [...collect("article p"), ...collect("article li")];
  const mainLines = [...collect("main p"), ...collect("main li")];
  const bodyLines = [...collect("p"), ...collect("li")];

  const lines = articleLines.length
    ? articleLines
    : mainLines.length
      ? mainLines
      : bodyLines;

  const uniqLines = Array.from(new Set(lines));
  let text = uniqLines.join("\n");
  if (!text) {
    text = cleanWhitespace($("body").text());
  }
  text = text.slice(0, 20000);

  return { title, text };
}

function buildBasicSummary(text: string): string {
  const lines = text
    .split("\n")
    .map((line) => cleanWhitespace(line))
    .filter((line) => line.length >= 20);
  if (lines.length === 0) {
    return "본문에서 유의미한 문장을 찾지 못했습니다.";
  }

  const uniqLines = Array.from(new Set(lines)).slice(0, 200);
  const stopwords = new Set([
    "그리고",
    "그러나",
    "또한",
    "에서",
    "으로",
    "이다",
    "있다",
    "합니다",
    "the",
    "and",
    "for",
    "with",
    "that",
    "this",
    "from",
  ]);

  const tokenize = (input: string): string[] =>
    input
      .toLowerCase()
      .replace(/[^\p{L}\p{N}\s]/gu, " ")
      .split(/\s+/)
      .filter((tok) => tok.length >= 2 && !stopwords.has(tok));

  const freq = new Map<string, number>();
  for (const line of uniqLines) {
    for (const token of tokenize(line)) {
      freq.set(token, (freq.get(token) || 0) + 1);
    }
  }

  const scored = uniqLines.map((line, idx) => {
    const tokens = tokenize(line);
    const tokenScore =
      tokens.length === 0
        ? 0
        : tokens.reduce((sum, tok) => sum + (freq.get(tok) || 0), 0) / tokens.length;
    const lengthBonus = Math.min(line.length / 180, 1);
    return { line, idx, score: tokenScore + lengthBonus };
  });

  const top = scored
    .sort((a, b) => b.score - a.score)
    .slice(0, 5)
    .sort((a, b) => a.idx - b.idx)
    .map((item) => item.line);

  const bullets = top.map((line) => `- ${line}`).join("\n");
  const oneLine = top[0] || uniqLines[0];
  return `${bullets}\n한줄요약: ${oneLine}`;
}

async function fetchHtml(url: string): Promise<string> {
  const ctrl = new AbortController();
  const timer = setTimeout(() => ctrl.abort(), 12000);
  try {
    const response = await fetch(url, {
      signal: ctrl.signal,
      headers: {
        "User-Agent": "discord-multibot-summarizer/0.1",
        Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
      },
    });
    if (!response.ok) {
      throw new Error(`본문 요청 실패: HTTP ${response.status}`);
    }
    return await response.text();
  } finally {
    clearTimeout(timer);
  }
}

async function callGemini(model: string, prompt: string, maxOutputTokens = 1200): Promise<string> {
  if (!env.GEMINI_API_KEY) {
    throw new Error("GEMINI_API_KEY가 설정되지 않았습니다.");
  }

  const endpoint = `https://generativelanguage.googleapis.com/v1beta/models/${encodeURIComponent(model)}:generateContent?key=${encodeURIComponent(env.GEMINI_API_KEY)}`;
  const response = await fetch(endpoint, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      contents: [{ parts: [{ text: prompt }] }],
      generationConfig: {
        temperature: 0.2,
        maxOutputTokens,
      },
    }),
  });

  if (!response.ok) {
    const text = await response.text();
    throw new Error(`Gemini 요청 실패: HTTP ${response.status} ${text.slice(0, 180)}`);
  }

  const data = (await response.json()) as any;
  const text = data?.candidates?.[0]?.content?.parts
    ?.map((p: any) => String(p?.text || ""))
    .join("\n")
    .trim();

  if (!text) {
    throw new Error("Gemini 응답에서 요약 텍스트를 찾지 못했습니다.");
  }
  return text;
}

function looksTruncated(text: string): boolean {
  const t = String(text || "").trim();
  if (!t) return true;
  if (!t.includes("한줄요약:")) return true;
  const ending = t.slice(-1);
  return ![".", "!", "?", "”", "\"", "다"].includes(ending);
}

export const summarizeService = {
  async summarizeUrl(
    url: string,
    mode: SummarizeMode = "auto",
    engine: SummarizeEngine = "ai",
  ): Promise<SummarizeResult> {
    const cleanUrl = String(url || "").trim();
    if (!/^https?:\/\//i.test(cleanUrl)) {
      throw new Error("http/https URL만 요약할 수 있습니다.");
    }

    const html = await fetchHtml(cleanUrl);
    const { title, text } = extractMainText(html);
    if (!text || text.length < 80) {
      throw new Error("요약할 본문을 충분히 추출하지 못했습니다.");
    }

    if (engine === "basic") {
      const summary = buildBasicSummary(text);
      stats.basicCalls += 1;
      logger.info(
        {
          type: "summarize-basic",
          callNo: stats.basicCalls,
          model: "rule-based-v1",
          inputChars: text.length,
          url: cleanUrl,
        },
        "Summarize BASIC call",
      );
      return {
        summary,
        model: "rule-based-v1",
        title,
        sourceUrl: cleanUrl,
        inputChars: text.length,
        callNo: stats.basicCalls,
      };
    }

    const model = chooseModel(mode, text.length);
    const prompt = [
      "다음 웹페이지 본문을 한국어로 요약하라.",
      "요구사항:",
      "1) 핵심 요점 5개 이내 불릿",
      "2) 사실 위주, 과장 금지",
      "3) 마지막 줄에 '한줄요약:' 추가",
      "4) 전체 1200자 이내",
      "",
      `[제목] ${title}`,
      "[본문]",
      text,
    ].join("\n");

    let summary = await callGemini(model, prompt, 1400);
    if (looksTruncated(summary)) {
      const retryPrompt = `${prompt}\n\n주의: 응답이 중간에 끊기지 않게 완결된 문장으로 끝내고 반드시 '한줄요약:' 줄을 포함하세요.`;
      summary = await callGemini(model, retryPrompt, 1800);
    }

    stats.totalCalls += 1;
    const prev = stats.modelCalls.get(model) || 0;
    stats.modelCalls.set(model, prev + 1);

    logger.info(
      {
        type: "summarize",
        callNo: stats.totalCalls,
        model,
        modelCalls: stats.modelCalls.get(model),
        inputChars: text.length,
        url: cleanUrl,
      },
      "Summarize API call",
    );

    return {
      summary,
      model,
      title,
      sourceUrl: cleanUrl,
      inputChars: text.length,
      callNo: stats.totalCalls,
    };
  },
};

export type { SummarizeEngine, SummarizeMode, SummarizeResult };
