#!/usr/bin/env python3
"""
Полный анализ главы по блокам через Ollama: framework → insights → application → limitations.
Вход: вход_главы.json, промпты extract_framework_v2.txt, extract_insights_v3.txt,
      extract_application_v2.txt, extract_limitations_v3.txt.
Выход: только merge.json (объединённый JSON всех четырёх блоков).
"""

import json
import re
import sys
import time
import urllib.request
from pathlib import Path

OLLAMA_URL = "http://localhost:11434"
MODEL = "qwen3:14b-q8_0"
DIR = Path(__file__).resolve().parent

OLLAMA_OPTIONS = {
    "temperature": 0.3,
    "num_ctx": 9000,
    "repeat_penalty": 1.1,
}

BLOCKS = [
    ("framework", "extract_framework_v2.txt", False),   # без previous_blocks_json
    ("insights", "extract_insights_v3.txt", True),
    ("application", "extract_application_v2.txt", True),
    ("limitations", "extract_limitations_v3.txt", True),
]


def load_input() -> dict:
    """Загружает входной JSON главы."""
    with open(DIR / "вход_главы.json", encoding="utf-8") as f:
        return json.load(f)


def load_prompt(filename: str) -> str:
    """Загружает шаблон промпта из файла."""
    with open(DIR / filename, encoding="utf-8") as f:
        return f.read()


def substitute_prompt(
    prompt: str,
    data: dict,
    previous_blocks_json: str | None = None,
) -> str:
    """Подставляет в промпт поля главы и при необходимости накопленный JSON."""
    out = (
        prompt.replace("{book_title}", data.get("book_title", ""))
        .replace("{chapter_title}", data.get("chapter_title", ""))
        .replace("{chapter_text}", data.get("chapter_text", ""))
    )
    if previous_blocks_json is not None:
        out = out.replace("{previous_blocks_json}", previous_blocks_json)
    return out


def extract_json_from_response(text: str) -> dict:
    """Достаёт JSON из ответа модели (может быть обёрнут в ```json ... ```)."""
    text = text.strip()
    match = re.search(r"```(?:json)?\s*([\s\S]*?)\s*```", text)
    if match:
        text = match.group(1).strip()
    return json.loads(text)


def call_ollama(prompt: str) -> str:
    """Вызывает Ollama /api/chat и возвращает content ответа."""
    body = json.dumps(
        {
            "model": MODEL,
            "messages": [{"role": "user", "content": prompt}],
            "stream": False,
            "format": "json",
            "options": OLLAMA_OPTIONS,
            "keep_alive": 0,
        },
        ensure_ascii=False,
    ).encode("utf-8")
    req = urllib.request.Request(
        f"{OLLAMA_URL}/api/chat",
        data=body,
        headers={"Content-Type": "application/json"},
        method="POST",
    )
    try:
        with urllib.request.urlopen(req, timeout=None) as resp:
            data = json.load(resp)
        return data.get("message", {}).get("content", "")
    except urllib.error.HTTPError as e:
        body_b = b""
        if e.fp:
            try:
                body_b = e.fp.read()[:1000]
            except Exception:
                pass
        raise RuntimeError(
            f"Ollama HTTP {e.code}: {e.reason}. Body: {body_b.decode('utf-8', errors='replace')}"
        ) from e


def main() -> int:
    """Последовательно генерирует 4 блока, накапливает результат, пишет merge.json."""
    print("Загрузка вход_главы.json и промптов...")
    data = load_input()
    merged: dict = {}

    for i, (block_name, prompt_file, use_previous) in enumerate(BLOCKS, start=1):
        prompt_tpl = load_prompt(prompt_file)
        previous_json = (
            json.dumps(merged, ensure_ascii=False, indent=2) if use_previous else None
        )
        prompt = substitute_prompt(prompt_tpl, data, previous_json)

        print(f"[{i}/4] Блок «{block_name}» — вызов Ollama {MODEL}...")
        t0 = time.monotonic()
        try:
            raw = call_ollama(prompt)
        except Exception as e:
            print(f"Ошибка вызова Ollama на блоке «{block_name}»: {e}", file=sys.stderr)
            return 1
        elapsed = time.monotonic() - t0
        print(f"      Ответ за {elapsed:.1f} сек ({elapsed / 60:.1f} мин)")

        try:
            block_data = extract_json_from_response(raw)
        except json.JSONDecodeError as e:
            print(
                f"Не удалось распарсить JSON в блоке «{block_name}»: {e}",
                file=sys.stderr,
            )
            print("Первые 500 символов ответа:", raw[:500], file=sys.stderr)
            return 1

        if not isinstance(block_data, dict):
            print(
                f"Блок «{block_name}» вернул не объект: {type(block_data).__name__}",
                file=sys.stderr,
            )
            return 1
        merged.update(block_data)

    out_path = DIR / "merge.json"
    with open(out_path, "w", encoding="utf-8") as f:
        json.dump(merged, f, ensure_ascii=False, indent=2)
    print(f"Записано: {out_path}")
    return 0


if __name__ == "__main__":
    sys.exit(main())
