"""
core/neuro_comments.py — нейрокомментинг под постами в Telegram-каналах.

Юзер указывает каналы и аккаунты-комментаторы. Воркер мониторит свежие
посты в этих каналах и публикует осмысленные комментарии (через
core/ai_engine.generate_comment).

Логика:
  1. Каждый канал проверяется раз в N минут (job.poll_interval_min).
  2. Если есть пост за последние M минут (job.recency_min) и под ним нет
     комментариев от наших акков — берём свободный аккаунт и пишем коммент.
  3. После публикации — пауза перед следующим аккаунтом (антибан).
  4. Лимит комментов на акк/день и в канале — настраиваемый.

Защита от детекции:
  - На один пост — НЕ больше N комментов от наших акков (default 1-2).
  - Между комментами от разных акков — рандомная задержка 2-15 мин.
  - На один акк — не больше M комментов в час (default 5).
"""
import asyncio
import logging
import random
import time
from dataclasses import dataclass, field
from typing import List, Dict, Set, Optional

logger = logging.getLogger("telepush.neuro_comments")


@dataclass
class NeuroCommentJob:
    owner_id: int = 0
    accounts: list = field(default_factory=list)   # List[Account]
    channels: List[str] = field(default_factory=list)  # @username каналов
    cta: str = ""                  # CTA-блок для AI (опционально)
    custom_prompt: str = ""        # Пользовательский system prompt
    # Тайминги
    poll_interval_min: int = 5     # Раз в сколько минут проверяем каналы
    recency_min: int = 60          # Постов старше Х минут не трогаем
    delay_between_acc_min: int = 2 # Минимальная пауза между нашими комментами (мин)
    delay_between_acc_max: int = 15
    max_comments_per_post: int = 1 # Максимум наших комментов под одним постом
    max_per_acc_per_hour: int = 5
    max_per_acc_per_day: int = 30
    # Состояние
    status: str = "idle"           # idle / running / stopped
    started_at: float = 0.0
    stop_flag: bool = False
    # Учёт
    posts_seen: Set[int] = field(default_factory=set)        # message_id постов которые уже видели
    posts_commented: Dict[int, List[str]] = field(default_factory=dict)  # post_id -> [phones кто комментил]
    acc_history: Dict[str, List[float]] = field(default_factory=dict)    # phone -> [timestamps комментов]
    log_lines: list = field(default_factory=list)
    counts: dict = field(default_factory=lambda: {
        "commented": 0,
        "skipped_recent": 0,
        "skipped_already": 0,
        "skipped_no_acc": 0,
        "errors": 0,
    })


def _log(job: NeuroCommentJob, msg: str):
    line = f"[{time.strftime('%H:%M:%S')}] {msg}"
    job.log_lines.append(line)
    if len(job.log_lines) > 500:
        job.log_lines = job.log_lines[-500:]
    logger.info("[neuro-comments] %s", msg)


def _can_acc_post(job: NeuroCommentJob, phone: str) -> bool:
    """Проверяет лимиты на акк (час/день)."""
    now = time.time()
    history = job.acc_history.get(phone, [])
    # Чистим историю старше 24ч
    history = [t for t in history if now - t < 86400]
    job.acc_history[phone] = history
    # Лимит за день
    if len(history) >= job.max_per_acc_per_day:
        return False
    # Лимит за час
    last_hour = sum(1 for t in history if now - t < 3600)
    if last_hour >= job.max_per_acc_per_hour:
        return False
    return True


async def _comment_under_post(job: NeuroCommentJob, acc, channel_username: str,
                                msg_id: int, post_text: str) -> bool:
    """Пишем коммент под msg_id в канале. Возвращает True если опубликовано."""
    from core.ai_engine import generate_comment
    # Persona на акк — стабильный seed по phone.
    personas = [
        "обычный подписчик, иногда комментирует если есть что сказать",
        "скептик, любит оспаривать, но без агрессии",
        "энтузиаст, поддерживает посты, делится своим опытом",
        "юморист, добавляет лёгкую шутку",
        "практик, спрашивает «как именно?» / «на каком моменте?»",
    ]
    idx = sum(ord(c) for c in (acc.phone or "default")) % len(personas)
    persona = personas[idx]
    text = await generate_comment(
        persona=persona,
        post_text=post_text,
        channel_name=channel_username,
        cta=job.cta or "",
        custom_system=job.custom_prompt or "",
    )
    if not text:
        _log(job, f"⚠ {acc.phone}: AI не вернул текст для @{channel_username}")
        return False
    # Постим коммент в discussion-группу канала (Telethon требует send_message
    # в комментарии = reply_to=msg_id в discussion группе или используем
    # GetDiscussionMessageRequest для resolve).
    try:
        from telethon.tl.functions.messages import GetDiscussionMessageRequest
        # Получаем discussion message_id для исходного поста.
        ent = await acc.client.get_input_entity(channel_username)
        disc = await acc.client(GetDiscussionMessageRequest(peer=ent, msg_id=msg_id))
        # disc.messages[0] — это reply-thread message в discussion-чате.
        if not disc.messages:
            _log(job, f"⚠ {acc.phone}: у @{channel_username} нет комментариев (discussion off)")
            return False
        thread_root = disc.messages[0]
        disc_chat = thread_root.peer_id
        await acc.client.send_message(disc_chat, text, comment_to=msg_id)
        return True
    except Exception as e:
        ename = type(e).__name__
        if "Discussion" in ename or "linked" in str(e).lower():
            _log(job, f"⚠ @{channel_username}: комменты выключены (нет discussion-группы)")
            return False
        _log(job, f"❌ {acc.phone} @{channel_username}: {ename}: {str(e)[:120]}")
        job.counts["errors"] += 1
        return False


async def _check_channel_once(job: NeuroCommentJob, channel: str):
    """Один проход по каналу: ищем свежие посты, комментируем."""
    if not job.accounts:
        return
    # Берём первый живой акк для разведки (read-only — собрать список постов).
    scout = None
    for a in job.accounts:
        if not getattr(a, "dead", False) and not getattr(a, "frozen", False):
            scout = a
            break
    if not scout or not scout.client:
        return
    try:
        ent = await scout.client.get_input_entity(channel)
        # Берём последние 5 постов канала
        posts = []
        async for msg in scout.client.iter_messages(ent, limit=5):
            if msg.id and msg.text:
                posts.append(msg)
    except Exception as e:
        _log(job, f"⚠ @{channel}: разведка упала — {type(e).__name__}: {str(e)[:80]}")
        return

    now_ts = time.time()
    for msg in posts:
        # Свежесть: пост не старше recency_min
        msg_ts = msg.date.timestamp() if msg.date else 0
        if now_ts - msg_ts > job.recency_min * 60:
            continue
        # Уже комментили этот пост N раз?
        comments_so_far = job.posts_commented.get(msg.id, [])
        if len(comments_so_far) >= job.max_comments_per_post:
            job.counts["skipped_already"] += 1
            continue
        # Берём свободный акк который не лимитирован
        candidate = None
        for a in job.accounts:
            if getattr(a, "dead", False) or getattr(a, "frozen", False):
                continue
            if a.phone in comments_so_far:
                continue
            if not _can_acc_post(job, a.phone):
                continue
            candidate = a
            break
        if not candidate:
            job.counts["skipped_no_acc"] += 1
            _log(job, f"⏸ @{channel} пост {msg.id}: нет свободных акков (лимиты часа/дня)")
            continue
        # Подключение если не подключён
        try:
            from core.client_builder import ensure_connected
            if not await ensure_connected(candidate):
                continue
        except Exception:
            continue
        # Пишем коммент
        post_text = msg.text or msg.message or ""
        ok = await _comment_under_post(job, candidate, channel.lstrip("@"), msg.id, post_text)
        if ok:
            job.posts_commented.setdefault(msg.id, []).append(candidate.phone)
            job.acc_history.setdefault(candidate.phone, []).append(now_ts)
            job.counts["commented"] += 1
            _log(job, f"✅ {candidate.phone} → @{channel} #{msg.id}")
        # Пауза перед следующим аккаунтом/постом
        delay = random.uniform(job.delay_between_acc_min * 60, job.delay_between_acc_max * 60)
        await asyncio.sleep(delay)


async def run_neuro_comments_job(job: NeuroCommentJob):
    """Главный цикл — крутится пока stop_flag=False."""
    job.status = "running"
    job.started_at = time.time()
    _log(job, f"🚀 Старт нейрокомментинга: {len(job.channels)} каналов, {len(job.accounts)} акков")
    while not job.stop_flag:
        for channel in job.channels:
            if job.stop_flag:
                break
            try:
                await _check_channel_once(job, channel)
            except Exception as e:
                _log(job, f"❌ @{channel}: {type(e).__name__}: {str(e)[:120]}")
        # Спим до следующего прохода
        if not job.stop_flag:
            await asyncio.sleep(job.poll_interval_min * 60)
    job.status = "stopped"
    _log(job, "🛑 Остановлен")
