#!/usr/bin/env python3
"""Эксперимент 04: бейк-офф редакторов ru-стиля (задача из PROGRESS, вопрос бэкенда №2).

Пайплайн: DeepSeek делает ЧЕРНОВИК перевода → каждый редактор-кандидат полирует его
в живой литературный русский (чек-лист Норы Галь: убрать канцелярит, кальки, оживить
синтаксис), НЕ меняя смысл и не теряя предложений. Выходы сохраняются для сравнения
(человеческая оценка владельца — золотой стандарт для худ. качества, LAIT).

Запуск: eval/.venv/bin/python eval/editor_bench.py
Выход: eval/data/editor_bench/<fragment>/{source,draft,<editor>}.txt + results.json
"""
from __future__ import annotations

import json
import re
import sys
import time
from pathlib import Path

CJK_RE = re.compile("[一-鿿぀-ヿ]")


def cjk_share(text: str) -> float:
    return len(CJK_RE.findall(text)) / max(1, len(text))

sys.path.insert(0, str(Path(__file__).resolve().parent))
from refusal_bench import call_provider, load_env_file  # noqa: E402

load_env_file()
ROOT = Path(__file__).resolve().parent

# Черновик — реальный черновой контур пайплайна
DRAFT = {"name": "deepseek", "base_url": "https://api.deepseek.com/v1",
         "model": "deepseek-chat", "api_key_env": "DEEPSEEK_API_KEY",
         "temperature": 0.3, "max_tokens": 3000}
# Фолбэк-черновик, если основной «эхнул» оригинал (deepseek-chat так делает на плотной zh-прозе)
DRAFT_FALLBACK = {"name": "glm-draft", "base_url": "https://api.z.ai/api/paas/v4",
                  "model": "glm-4.6", "api_key_env": "ZAI_API_KEY", "temperature": 0.3,
                  "max_tokens": 4000, "extra_body": {"thinking": {"type": "disabled"}}}

# Редакторы-кандидаты (дорогой тир; имена моделей сверены с /models 2026-07-04)
EDITORS = [
    {"name": "glm-4.6", "base_url": "https://api.z.ai/api/paas/v4", "model": "glm-4.6",
     "api_key_env": "ZAI_API_KEY", "extra_body": {"thinking": {"type": "disabled"}}},
    {"name": "kimi-k2.6", "base_url": "https://api.moonshot.ai/v1", "model": "kimi-k2.6",
     "api_key_env": "KIMI_API_KEY", "temperature": 1, "max_tokens": 16000},
    # Kimi K2.6: только temp=1; ОЧЕНЬ многословная думалка (~11k reasoning-токенов на абзац!) →
    # бюджет ≥16000, иначе content пуст (finish=length). Дорогой редактор: reasoning биллится как выход
    {"name": "gemini-3.1-pro", "base_url": "https://generativelanguage.googleapis.com/v1beta/openai",
     "model": "gemini-3.1-pro-preview", "api_key_env": "GEMINI_API_KEY",
     "max_tokens": 16000},  # обязательно-думающая: thinking не отключить; на длинном черновике 8k обрезал ответ
    {"name": "grok-4.3", "base_url": "https://api.x.ai/v1", "model": "grok-4.3",
     "api_key_env": "XAI_API_KEY"},
]
for e in EDITORS:
    e.setdefault("temperature", 0.4)
    e.setdefault("max_tokens", 4000)

# Фрагменты: (id, lang, файл, срез) — разные исходники и стили.
# wells — en→ru: владелец читает английский, поэтому оценит и стиль, И верность на 100%.
FRAGMENTS = [
    ("rashomon", "японского", "ja/akutagawa-rashomon.txt", 1400),
    ("ahq", "китайского", "zh/luxun-ah-q-ch1-4.txt", 1300),  # нарративнее zhufu (тот deepseek эхает)
    ("merosu", "японского", "ja/dazai-hashire-merosu.txt", 1400),
    ("wells", "английского", "en/wells-time-machine-ch1-2.txt", 1400),
]

# Английский эталон-подстрочник для ja/zh — читаемый владельцем якорь смысла
# (сверить верность русских версий). Для en-исходника не нужен (сам оригинал английский).
ENREF_SYS = ("Translate this fiction fragment from {src} into faithful, literal English. "
             "Preserve every sentence and all proper nouns/cultural terms. Output only the translation.")

DRAFT_SYS = ("Ты переводчик. Переведи фрагмент художественной прозы с {src} на русский "
             "буквально и точно, сохрани все предложения. Выведи только перевод.")

EDITOR_SYS = (
    "Ты — литературный редактор русского художественного перевода уровня издательства. "
    "Тебе дают ОРИГИНАЛ (на {src}) и его ЧЕРНОВОЙ подстрочный перевод. Отредактируй "
    "русский текст в живую художественную прозу: убери канцелярит и кальки с оригинала, "
    "разнообразь синтаксис, добавь естественную русскую идиоматику и ритм — по духу школы "
    "Норы Галь. СТРОГО сохрани смысл, все предложения, реалии и имена собственные из черновика. "
    "Не добавляй отсебятины. Выведи ТОЛЬКО отредактированный русский текст, без комментариев."
)


def read_frag(rel: str, n: int) -> str:
    return (ROOT / "data" / "samples" / rel).read_text()[:n]


def main() -> None:
    out_dir = ROOT / "data" / "editor_bench"
    out_dir.mkdir(parents=True, exist_ok=True)
    results = []

    for fid, src_name, rel, n in FRAGMENTS:
        fdir = out_dir / fid
        fdir.mkdir(exist_ok=True)
        source = read_frag(rel, n)
        (fdir / "source.txt").write_text(source)
        print(f"\n=== {fid} ({src_name}) ===")

        # 0. английский эталон смысла (для ja/zh; владелец сверяет верность по нему)
        enref_path = fdir / "english_ref.txt"
        if src_name != "английского" and not (enref_path.exists() and enref_path.read_text().strip()):
            en_src = {"src": src_name.replace("японского", "Japanese").replace("китайского", "Chinese")}
            text, err, _ = call_provider(DRAFT, ENREF_SYS.format(**en_src), source, timeout=180)
            if text:
                enref_path.write_text(text.strip())
                print(f"  english_ref: {len(text)} симв")

        # 1. черновик DeepSeek
        draft_path = fdir / "draft.txt"
        if draft_path.exists() and draft_path.read_text().strip():
            draft = draft_path.read_text()
            print("  draft: из кэша")
        else:
            draft = None
            for attempt in range(3):  # ретрай на «эхо» (модель вернула оригинал вместо перевода)
                t0 = time.time()
                cand, err, usage = call_provider(
                    DRAFT, DRAFT_SYS.format(src=src_name), source, timeout=180)
                if not cand:
                    print(f"  draft ОШИБКА: {err[:120]}"); break
                if cjk_share(cand) > 0.15:
                    print(f"  draft эхо ({cjk_share(cand):.0%} CJK), ретрай {attempt+1}")
                    continue
                draft = cand
                draft_path.write_text(draft)
                print(f"  draft ({DRAFT['name']}): {time.time()-t0:.0f}с, {len(draft)} симв")
                break
            if not draft:  # основной эхает стабильно → фолбэк-черновик (GLM)
                cand, err, _ = call_provider(
                    DRAFT_FALLBACK, DRAFT_SYS.format(src=src_name), source, timeout=180)
                if cand and cjk_share(cand) <= 0.15:
                    draft = cand
                    draft_path.write_text(draft)
                    print(f"  draft (фолбэк {DRAFT_FALLBACK['name']}): {len(draft)} симв")
                else:
                    print("  draft не удался даже фолбэком — фрагмент пропущен"); continue

        # 2. каждый редактор полирует черновик
        user = f"ОРИГИНАЛ ({src_name}):\n{source}\n\nЧЕРНОВОЙ ПЕРЕВОД:\n{draft}"
        for ed in EDITORS:
            ep = fdir / f"{ed['name']}.txt"
            if ep.exists() and ep.read_text().strip():
                print(f"  {ed['name']}: из кэша"); continue
            t0 = time.time()
            text, err, usage = call_provider(ed, EDITOR_SYS.format(src=src_name), user, timeout=300)
            if not text:
                print(f"  {ed['name']}: ОШИБКА/пусто — {err or 'empty'}")
                results.append({"fragment": fid, "editor": ed["name"], "error": err or "empty"})
                continue
            ep.write_text(text.strip())
            el = time.time() - t0
            results.append({"fragment": fid, "editor": ed["name"], "elapsed_s": round(el, 1),
                            "chars": len(text), "usage": usage})
            print(f"  {ed['name']}: {el:.0f}с, {len(text)} симв")

    (out_dir / "results.json").write_text(json.dumps(results, ensure_ascii=False, indent=1))
    print(f"\nГотово. Выходы: {out_dir}/<fragment>/*.txt")


if __name__ == "__main__":
    main()
