"""
core/ai_generator.py — генерация уникальных пулов для прогрева через AI.

Идея: вместо захардкоженных списков OWN_CHAT_TITLE_TEMPLATES /
NEUTRAL_CHANNEL_NAMES / GREETINGS / DIARY_THEMES — генерим пулы
индивидуально для каждой инсталляции TelePush. Это снимает риск
massive-fingerprint: Telegram уже не может «забанить пул из X слов»
потому что у каждого клиента свой набор.

Промпты ВСТРОЕНЫ в код — юзер не пишет их сам, только вставляет API key
в Настройки AI (уже существующий блок). Поддерживается всё что умеет
`core.ai_engine.chat_complete` (OpenAI / OpenRouter / DeepSeek / Groq /
Custom).

Кеш пулов лежит в DATA_DIR/ai_pool.json. Генерируется один раз
(юзер жмёт «Сгенерировать AI-пулы»), потом используется без обращений
к API. Перегенерить — кнопка в UI.
"""
from __future__ import annotations

import json
import logging
import re
import time
from pathlib import Path
from typing import Any, Dict, List, Optional

from core.ai_engine import chat_complete, ai_status

logger = logging.getLogger("telepush.ai.gen")


# ── Где лежит кеш ───────────────────────────────────────────────────────

def _pool_path() -> Path:
    from config import DATA_DIR
    return DATA_DIR / "ai_pool.json"


def _load_pool() -> Dict[str, Any]:
    try:
        p = _pool_path()
        if not p.exists():
            return {}
        return json.loads(p.read_text(encoding="utf-8"))
    except Exception as e:
        logger.warning("[gen] load pool: %s", e)
        return {}


def _save_pool(pool: Dict[str, Any]) -> None:
    try:
        p = _pool_path()
        p.parent.mkdir(parents=True, exist_ok=True)
        p.write_text(json.dumps(pool, ensure_ascii=False, indent=2), encoding="utf-8")
    except Exception as e:
        logger.warning("[gen] save pool: %s", e)


# ── Парсеры ответа AI ────────────────────────────────────────────────────

_NUMBERED_RE = re.compile(r"^\s*(?:\d+[\.\)]\s*|[-*•]\s*)?(.+?)\s*$")


def _parse_list(text: str, min_len: int = 1, max_len: int = 80) -> List[str]:
    """Парсит ответ AI как список строк. Принимаем формат «1. Foo\n2. Bar» или просто построчно."""
    out: List[str] = []
    if not text:
        return out
    seen = set()
    for raw in text.splitlines():
        m = _NUMBERED_RE.match(raw)
        if not m:
            continue
        v = m.group(1).strip().strip('"').strip("'").strip("«»").strip()
        # Сносим квоты, точки в конце, нумерацию которую кто-то добавил внутри
        v = re.sub(r"\s+", " ", v)
        if len(v) < min_len or len(v) > max_len:
            continue
        if v.lower() in seen:
            continue
        seen.add(v.lower())
        out.append(v)
    return out


# ── Промпты ─────────────────────────────────────────────────────────────

_PROMPT_CHAT_NAMES = """Сгенерируй {count} разнообразных нейтральных имён для приватной групп-чат или дневника-канала в Telegram.

ТРЕБОВАНИЯ:
- Каждое имя на отдельной строке (без нумерации, без кавычек)
- Длина 4-22 символа (с пробелами)
- На русском
- Звучит как название которое реальный человек дал бы своей маленькой приватной группе или дневнику
- Категории: бытовые («Заметки», «Дни», «Будни», «Кухня», «Двор»), личные («Своё», «Моё», «Тихо», «Уголок»), нейтральные с лёгкой иронией («Без темы», «Не помню зачем», «Так вышло», «Чё попало»)
- ИЗБЕГАЙ: слова «прогрев», «дневник + тема» (типа «Дневник обучения»), «мой + тема» (типа «Мой фриланс»), «записки + тема». Они известны Telegram как паттерны бот-сетей и блокируются.
- ИЗБЕГАЙ слов: «канал», «общение», «клуб», «сообщество», «беседа»

Только список — никаких пояснений, никаких заголовков."""


_PROMPT_USERNAMES = """Сгенерируй {count} реалистичных Telegram-юзернеймов в стиле живых пользователей.

ФОРМАТЫ (мешай разные, чтобы не было одинаковости):
- имя_год: anna_88, max_2024, lena_1995
- имя.цифры: ivan.99, kate.777, dmitry.42
- имяконкат: luna77, leo123, milo55
- имя_имя: anna_petrov, alex_smith
- имя_буквы: kate_xy, max_qq

ТРЕБОВАНИЯ:
- Только латиница, цифры, точка, нижнее подчёркивание
- 5-32 символа, начинается с буквы
- Каждый на отдельной строке (без нумерации, без @, без кавычек)
- БЕЗ префикса «user_», «bot_», «tg_», «account_» — это паттерны ботов
- Имена реалистичные русские/английские: anna, max, kate, alex, dmitry, lena, milo, leo, nick, ivan, vika, polina, kirill, sasha и т.п. — НЕ выдуманные слова

Только список — никаких пояснений."""


_PROMPT_GREETINGS = """Сгенерируй {count} разнообразных коротких приветствий для дружеского чата в Telegram (СНГ, разговорный стиль).

ТРЕБОВАНИЯ:
- Каждое приветствие на отдельной строке (без нумерации, без кавычек)
- 1-12 слов
- Разговорный неформальный стиль: «Прив», «Здарова», «Ку», «Йоу», «Привет, как дела» и т.п.
- Можно с лёгким сленгом, без матов
- Иногда вопросом, иногда просто хей
- Эмодзи допустимы (0-1), часто без
- Иногда со скобкой ) в конце (русский смайл)
- Регистр естественный — часто с маленькой буквы, иногда с большой
- НЕ повторяйся

Примеры (НЕ копируй, но похожий формат):
Прив)
Здарова, как сам?
Ку, чё делаешь
О, привет
Хей, как жизнь?

Только список — никаких пояснений."""


_DIARY_THEME_INSTRUCTIONS = {
    "work": "Story arc: поиск работы — отклики, собеседования, оффер, выход на работу, первая неделя. Линейная история.",
    "learning": "Story arc: обучение на курсе — старт, первые модули, проекты, защита финального. Линейная история.",
    "travel": "Story arc: путешествие — мечта, билеты, подготовка, прилёт, дни в поездке, возвращение, мысли о следующей.",
    "sport": "Story arc: спорт — начало, зал, бег, прогресс, первые соревнования.",
    "freelance": "Story arc: переход на фриланс — первый отклик, первый клиент, рост, постоянные клиенты.",
}


_PROMPT_DIARY_POSTS = """Сгенерируй {count} постов для приватного Telegram-канала-дневника. Это story arc — посты идут от первого к последнему линейно во времени.

ТЕМА КАНАЛА: {theme_title}

{instructions}

ТРЕБОВАНИЯ К КАЖДОМУ ПОСТУ:
- 1-3 предложения, разговорный стиль, как пишет обычный человек у себя в дневнике
- Без эмодзи (или 0-1 на пост)
- Без призывов, без «подписывайтесь», без рекламы
- НЕ упоминай слова «прогрев», «бот», «нейросеть», «AI», «ИИ», название темы напрямую в первом посте («решил начать поиск работы» — норм, но не «канал про поиск работы»)
- Личное от первого лица
- Между постами должна чувствоваться динамика (прошёл день, неделя, месяц)
- Используй разные формулировки, не начинай все посты одинаково

ФОРМАТ ОТВЕТА:
- Каждый пост на отдельной строке (один пост = одна строка, переносы внутри поста заменяй на «. »)
- БЕЗ нумерации, БЕЗ кавычек, БЕЗ заголовков, БЕЗ разделителей
- Не пиши «Пост 1:», «День 1:» и т.п. — просто текст поста

Только посты — никаких пояснений."""


# ── Публичные функции генерации ─────────────────────────────────────────

async def generate_chat_names(count: int = 50) -> List[str]:
    """Имена для own_chat и diary-каналов (используются одним пулом)."""
    prompt = _PROMPT_CHAT_NAMES.format(count=count)
    text = await chat_complete(
        [{"role": "user", "content": prompt}],
        max_tokens=1200,
        temperature=1.0,
    )
    items = _parse_list(text or "", min_len=3, max_len=30)
    return items[:count]


async def generate_usernames(count: int = 60) -> List[str]:
    """Юзернеймы реалистичных форматов (НЕ user_XXX)."""
    prompt = _PROMPT_USERNAMES.format(count=count)
    text = await chat_complete(
        [{"role": "user", "content": prompt}],
        max_tokens=1500,
        temperature=1.0,
    )
    items = _parse_list(text or "", min_len=5, max_len=32)
    # Жёсткий фильтр: только разрешённые символы, начинается с буквы
    clean = []
    for u in items:
        u = u.lstrip("@").strip()
        if not re.match(r"^[a-zA-Z][a-zA-Z0-9._]{3,31}$", u):
            continue
        # Запрещаем `user_XXX`-паттерн и подобные
        if re.match(r"^(user|bot|tg|account|admin|test)[_.]", u.lower()):
            continue
        clean.append(u.lower())
    # Уникальные
    seen = set()
    out = []
    for u in clean:
        if u in seen:
            continue
        seen.add(u)
        out.append(u)
    return out[:count]


async def generate_greetings(count: int = 50) -> List[str]:
    """Короткие приветствия для DM-прогрева."""
    prompt = _PROMPT_GREETINGS.format(count=count)
    text = await chat_complete(
        [{"role": "user", "content": prompt}],
        max_tokens=1200,
        temperature=1.05,
    )
    items = _parse_list(text or "", min_len=2, max_len=80)
    return items[:count]


async def generate_diary_posts_for_theme(theme_key: str, theme_title: str, count: int = 13) -> List[str]:
    """Уникальный story arc для дневника на тему. Возвращает count постов в порядке."""
    instructions = _DIARY_THEME_INSTRUCTIONS.get(theme_key, "Story arc — линейная история без рекламы.")
    prompt = _PROMPT_DIARY_POSTS.format(count=count, theme_title=theme_title, instructions=instructions)
    text = await chat_complete(
        [{"role": "user", "content": prompt}],
        max_tokens=2000,
        temperature=0.95,
    )
    items = _parse_list(text or "", min_len=20, max_len=600)
    return items[:count]


# ── Полный пайплайн «сгенерить все 4 пула» ──────────────────────────────

async def regenerate_all_pools(*, chat_names: int = 50, usernames: int = 60,
                                greetings: int = 50, diary_posts: int = 13,
                                themes: Optional[List[str]] = None,
                                progress_cb=None) -> Dict[str, Any]:
    """Генерирует все пулы и сохраняет в data/ai_pool.json.

    Возвращает dict {ok, chat_names: N, usernames: N, greetings: N, diary: {theme: N}, error?}.
    progress_cb(label, done, total) — опциональный колбэк прогресса для UI.
    """
    from core.warmup_defaults import DIARY_THEMES

    status = ai_status()
    if not status.get("enabled"):
        return {"ok": False, "error": "AI не настроен. Включите в Настройках → AI и вставьте ключ."}

    if themes is None:
        themes = list(DIARY_THEMES.keys())

    pool = _load_pool()
    total_steps = 3 + len(themes)
    step = 0

    def _tick(label: str):
        nonlocal step
        step += 1
        if progress_cb:
            try:
                progress_cb(label, step, total_steps)
            except Exception:
                pass

    # 1. Chat names
    _tick("Имена чатов и каналов")
    names = await generate_chat_names(chat_names)
    if names:
        pool["chat_names"] = names

    # 2. Usernames
    _tick("Юзернеймы аккаунтов")
    usernms = await generate_usernames(usernames)
    if usernms:
        pool["usernames"] = usernms

    # 3. Greetings
    _tick("Приветствия для DM")
    greets = await generate_greetings(greetings)
    if greets:
        pool["greetings"] = greets

    # 4. Diary posts per theme
    diary_out: Dict[str, List[str]] = pool.get("diary_posts") or {}
    for tkey in themes:
        tdata = DIARY_THEMES.get(tkey, {})
        ttitle = tdata.get("title", tkey)
        _tick(f"Дневник: {ttitle}")
        posts = await generate_diary_posts_for_theme(tkey, ttitle, diary_posts)
        if posts:
            diary_out[tkey] = posts
    pool["diary_posts"] = diary_out

    pool["generated_at"] = int(time.time())
    pool["model_used"] = status.get("model", "")
    _save_pool(pool)

    return {
        "ok": True,
        "chat_names": len(pool.get("chat_names") or []),
        "usernames": len(pool.get("usernames") or []),
        "greetings": len(pool.get("greetings") or []),
        "diary": {k: len(v) for k, v in (pool.get("diary_posts") or {}).items()},
        "generated_at": pool["generated_at"],
        "model_used": pool["model_used"],
    }


# ── Геттеры с фолбеком на статические пулы ──────────────────────────────
# Используются в warmup.py / warmup_diary.py / warmup_messages.py /
# web/app.py — если AI-пул задан и не пустой, берётся он, иначе
# дефолтный пул из warmup_defaults / warmup_messages.

def pool_chat_names() -> List[str]:
    """Имена для own_chat и для diary каналов — если AI-пул задан, иначе пусто."""
    return list((_load_pool().get("chat_names") or []))


def pool_usernames() -> List[str]:
    return list((_load_pool().get("usernames") or []))


def pool_greetings() -> List[str]:
    return list((_load_pool().get("greetings") or []))


def pool_diary_posts(theme_key: str) -> List[str]:
    return list((_load_pool().get("diary_posts") or {}).get(theme_key) or [])


def pool_status() -> Dict[str, Any]:
    """Сводка для UI: что в кеше есть и когда сгенерировано."""
    p = _load_pool()
    if not p:
        return {"empty": True}
    return {
        "empty": False,
        "chat_names": len(p.get("chat_names") or []),
        "usernames": len(p.get("usernames") or []),
        "greetings": len(p.get("greetings") or []),
        "diary": {k: len(v) for k, v in (p.get("diary_posts") or {}).items()},
        "generated_at": int(p.get("generated_at", 0) or 0),
        "model_used": p.get("model_used", ""),
    }


def clear_pool() -> None:
    """Кнопка «Сбросить AI-пулы»."""
    try:
        p = _pool_path()
        if p.exists():
            p.unlink()
    except Exception as e:
        logger.warning("[gen] clear: %s", e)
