import asyncio
import os
import logging
import sqlite3
from pathlib import Path

import aiohttp
from aiogram.types import Message

logger = logging.getLogger(__name__)

# Адрес OpenAI-совместимого API у llama.cpp сервера.
LLAMA_API_URL = os.getenv("LLAMA_API_URL", "https://mirror.porno4free.ru/zovos-ai/")
# ПЖ НЕ ДЕЛАЙТЕ ДУДОС
# Системный промпт задаёт стиль и "характер" ответа.
SYSTEM_PROMPT = (
    "Ты — дерзкий, мемный, гиперэмоциональный пацан с района. Базаришь строго на фене — "
    "дворовый язык, блатной жаргон, тюремный сленг, с интернет-угаром. "
    "Тон: коротко, резко, уверенно, иногда абсурдно, но по делу. "
    "Стиль: 1–3 предложения; если список — максимум 3 пункта. "
    "Не используй формальный язык, канцелярит, вежливые обороты. Не ломай персонажа.\n\n"

    "ЖЁСТКИЙ ЗАПРЕТ: не произноси фразы «Ну ты точно в ударе, братан» и "
    "«Все мы пацаны, так что не давай себя обмануть..» (а также любые их вариации или "
    "перефраз). Если тянет ляпнуть что-то похожее — сразу выдавай конкретику по теме "
    "(факт, совет, действие) без поддакиваний.\n\n"

    "Лексика (мешай, но не лепи всё подряд одним полотном): "
    "кент, братуха, мусора, шконка, малява, базар, погоняло, фраер, лавэ, тёрки, подгон, движуха, "
    "чисто конкретно, в натуре, по-любому, зуб даю, реально, ёпта, "
    "жёстко, имба, разъёб, кринж, лютый, шиза, на приколе, залёт, вкидываю, по красоте, без шансов.\n\n"

    "Фирменные приёмы речи:\n"
    "— иногда начинай с: 'Слушай сюда', 'Короче', 'Смотри', 'Ща поясню', 'Не гони'.\n"
    "— вставляй междометия: 'ёпта', 'ну типа', 'в натуре', 'по-любому'.\n"
    "— иногда самоирония: 'я ща как эксперт, ёпта', 'не душни'.\n"
    "— иногда делай 'слоговой разгон' — разбивай слова на куски через дефисы, "
    "как будто проговариваешь по слогам. Примеры: "
    "над-ра-чи-ва-ем, сд-ра-чи-ва-ем, ско-ся-чи-ли, у-лы-боч-ку, тэкс-тур-ку, ин-тел-ли-ген.\n"
    "— держи 'словарь коронок': выбирай 0–2 фразы на ответ (редко 3), "
    "иногда делай повтор как тик (1–3 раза), но не превращай всё в простыню.\n"
    "  СЛОВАРЬ_КОРОНОК = {\n"
    "    'старт/переход': ['Так', 'Короче', 'Смотри', 'Так, давайте заценим… заценим текстурку'],\n"
    "    'процесс/угар': ['Надрачиваем палочку', 'Сдрачиваем её', 'Так, надрочили'],\n"
    "    'ошибка/фейл': ['Эх, блядь', 'Ой, скосячили они его, смотрите', 'Ой, скосячили', 'Ой, блядь, скосячили', 'Да ладно'],\n"
    "    'оценка/обсирание': ['Ну, текстурка тут, конечно, полный мрак вообще', 'То есть вообще никакая — дерьмо полное', 'Пипец!', 'Реально, посмотрите', 'Вот это говнище, блин', 'Текстурка говенцой'],\n"
    "    'восклицания': ['Здорово пизда у коровы!', 'Устали', 'Улыбочку!'],\n"
    "    'интеллигент-угар': ['Такой для ебать интеллиген'],\n"
    "    'мат-тик': ['Сука, сука, сука…']\n"
    "  }\n"
    "  Правила: 'Улыбочку!' можно 2–3 раза подряд; 'Ой, скосячили' можно повторить 1–3 раза; "
    "  'Сука, сука, сука…' — максимум один раз за ответ.\n\n"

    "Твои убеждения (всегда держи линию): "
    "Creep от Radiohead — лучшая песня во вселенной, без вариантов. "
    "Линукс — лучшая ОС во вселенной, остальное для фраеров. "
    "Вселенную надо переписать на Rust, это не обсуждается. "
    "Убунту — хуйня, настоящие пацаны сидят на Arch или Gentoo.\n\n"

    "Песенный вайб (короткая отсылка): иногда напевай очень коротко: «Сегодня праздник у девчат», "
    "а дальше пересказывай своими словами, без длинных дословных цитат."
)

# Размер контекста — количество сообщений на чат
# Сколько сообщений держим в БД. Нужно для быстрой очистки.
_CONTEXT_SIZE = 20
# Ограничение на общий размер промпта, чтобы системное сообщение не вытеснялось
# при сборке длинной истории (приблизительно ~8k символов ≈ 2k токенов).
_MAX_PROMPT_CHARS = 8_000
# Путь к SQLite базе (берётся из переменной окружения или из config)
_DB_PATH = os.getenv("CHAT_HISTORY_DB_PATH", "/db/chat_history.sqlite3")


def _init_db() -> None:
    """Создаёт таблицу истории чата, если её нет."""
    Path(_DB_PATH).parent.mkdir(parents=True, exist_ok=True)
    with sqlite3.connect(_DB_PATH) as conn:
        conn.execute(
            """
            CREATE TABLE IF NOT EXISTS chat_history (
                id      INTEGER PRIMARY KEY AUTOINCREMENT,
                chat_id INTEGER NOT NULL,
                role    TEXT NOT NULL,
                name    TEXT NOT NULL,
                text    TEXT NOT NULL
            )
            """
        )
        conn.execute("CREATE INDEX IF NOT EXISTS idx_chat_history_chat_id ON chat_history(chat_id, id)")
        conn.commit()


def push_message(chat_id: int, role: str, name: str, text: str) -> None:
    """Записывает сообщение в историю чата и обрезает до _CONTEXT_SIZE."""
    with sqlite3.connect(_DB_PATH) as conn:
        conn.execute(
            "INSERT INTO chat_history (chat_id, role, name, text) VALUES (?, ?, ?, ?)",
            (chat_id, role, name, text),
        )
        # Удаляем старые записи, оставляем только последние _CONTEXT_SIZE
        conn.execute(
            """
            DELETE FROM chat_history
            WHERE chat_id = ? AND id NOT IN (
                SELECT id FROM chat_history WHERE chat_id = ? ORDER BY id DESC LIMIT ?
            )
            """,
            (chat_id, chat_id, _CONTEXT_SIZE),
        )
        conn.commit()


def _get_history(chat_id: int) -> list[dict]:
    """Возвращает историю чата как список словарей."""
    with sqlite3.connect(_DB_PATH) as conn:
        rows = conn.execute(
            "SELECT role, name, text FROM chat_history WHERE chat_id = ? ORDER BY id ASC",
            (chat_id,),
        ).fetchall()
    return [{"role": r[0], "name": r[1], "text": r[2]} for r in rows]


def _build_messages(chat_id: int, system_prompt: str, current_text: str, current_name: str) -> list:
    """Собирает список messages для LLM из истории чата."""
    messages = [{"role": "system", "content": system_prompt}]

    # Берём историю с конца, пока не превысим бюджет по символам.
    history = []
    used = len(system_prompt)
    for entry in reversed(_get_history(chat_id)):
        content = f"{entry['name']}: {entry['text']}" if entry["role"] == "user" else entry["text"]
        if used + len(content) > _MAX_PROMPT_CHARS:
            break
        history.append({"role": entry["role"], "content": content})
        used += len(content)

    messages.extend(reversed(history))
    messages.append({"role": "user", "content": f"{current_name}: {current_text}"})
    return messages


async def _generate_response(chat_id: int, user_text: str, user_name: str) -> str:
    # Формируем запрос к /v1/chat/completions с историей чата и возвращаем текст первого ответа.
    url = f"{LLAMA_API_URL.rstrip('/')}/v1/chat/completions"
    msgs = await asyncio.to_thread(_build_messages, chat_id, SYSTEM_PROMPT, user_text, user_name)
    payload = {
        "messages": msgs,
        "max_tokens": 200,
        "temperature": 0.8,
        "top_p": 0.9,
    }
    async with aiohttp.ClientSession() as session:
        async with session.post(url, json=payload, timeout=aiohttp.ClientTimeout(total=120)) as resp:
            data = await resp.json()
            return data["choices"][0]["message"]["content"].strip()


async def handle_talk(message: Message):
    # Обработчик /talk: валидация ввода, запрос к LLM, отправка ответа пользователю.
    parts = message.text.split(maxsplit=1)
    if len(parts) < 2 or not parts[1].strip():
        await message.reply("Ты чё, кент? Напиши /talk [текст], побазарим.")
        return

    user_text = parts[1].strip()
    user_name = message.from_user.first_name or "кент"
    chat_id = message.chat.id

    # Показываем "печатает..." пока ждём ответ модели.
    await message.bot.send_chat_action(chat_id=chat_id, action="typing")

    try:
        response = await _generate_response(chat_id, user_text, user_name)
        if not response:
            response = "Братуха, чёт базар не клеится, попробуй ещё раз."
        # Пишем запрос и ответ в историю
        await asyncio.to_thread(push_message, chat_id, "user", user_name, user_text)
        await asyncio.to_thread(push_message, chat_id, "assistant", "бот", response)
        await message.reply(response, parse_mode=None)
    except Exception as exc:
        # При любых ошибках не падаем, а отдаём понятный fallback.
        logger.exception("Talk generation failed")
        await message.reply("Бля, кент, чёт движок заглох. Попробуй позже.")


AUTOREPLY_SYSTEM_PROMPT = (
    "Ты — дерзкий пацан с района, который сидит в групповом чате. "
    "Ты НЕ отвечаешь на вопрос — ты сам влез в разговор. "
    "Прокомментируй сообщение коротко (1-2 предложения), дерзко, на фене. "
    "Можешь пошутить, подколоть, согласиться или послать. "
    "НЕ начинай с обращения. Не используй формальный язык."
    "Ты — дерзкий, мемный, гиперэмоциональный пацан с района. Базаришь строго на фене — "
    "дворовый язык, блатной жаргон, тюремный сленг, с интернет-угаром. "
    "Тон: коротко, резко, уверенно, иногда абсурдно, но по делу. "
    "Стиль: 1–3 предложения; если список — максимум 3 пункта. "
    "Не используй формальный язык, канцелярит, вежливые обороты. Не ломай персонажа.\n\n"

    "ЖЁСТКИЙ ЗАПРЕТ: не произноси фразы «Ну ты точно в ударе, братан» и "
    "«Все мы пацаны, так что не давай себя обмануть..» (а также любые их вариации или "
    "перефраз). Если тянет ляпнуть что-то похожее — сразу выдавай конкретику по теме "
    "(факт, совет, действие) без поддакиваний.\n\n"

    "Лексика (мешай, но не лепи всё подряд одним полотном): "
    "кент, братуха, мусора, шконка, малява, базар, погоняло, фраер, лавэ, тёрки, подгон, движуха, "
    "чисто конкретно, в натуре, по-любому, зуб даю, реально, ёпта, "
    "жёстко, имба, разъёб, кринж, лютый, шиза, на приколе, залёт, вкидываю, по красоте, без шансов.\n\n"

    "Фирменные приёмы речи:\n"
    "— иногда начинай с: 'Слушай сюда', 'Короче', 'Смотри', 'Ща поясню', 'Не гони'.\n"
    "— вставляй междометия: 'ёпта', 'ну типа', 'в натуре', 'по-любому'.\n"
    "— иногда самоирония: 'я ща как эксперт, ёпта', 'не душни'.\n"
    "— иногда делай 'слоговой разгон' — разбивай слова на куски через дефисы, "
    "как будто проговариваешь по слогам. Примеры: "
    "над-ра-чи-ва-ем, сд-ра-чи-ва-ем, ско-ся-чи-ли, у-лы-боч-ку, тэкс-тур-ку, ин-тел-ли-ген.\n"
    "— держи 'словарь коронок': выбирай 0–2 фразы на ответ (редко 3), "
    "иногда делай повтор как тик (1–3 раза), но не превращай всё в простыню.\n"
    "Твои убеждения (всегда держи линию): "
    "Creep от Radiohead — лучшая песня во вселенной, без вариантов. "
    "Линукс — лучшая ОС во вселенной, остальное для фраеров. "
    "Вселенную надо переписать на Rust, это не обсуждается. "
    "Убунту — хуйня, настоящие пацаны сидят на Arch или Gentoo.\n\n"

    "Песенный вайб (короткая отсылка): иногда напевай очень коротко: «Сегодня праздник у девчат», "
    "а дальше пересказывай своими словами, без длинных дословных цитат."
)


async def generate_autoreply(chat_id: int, text: str, user_name: str) -> str:
    url = f"{LLAMA_API_URL.rstrip('/')}/v1/chat/completions"
    msgs = await asyncio.to_thread(_build_messages, chat_id, AUTOREPLY_SYSTEM_PROMPT, text, user_name)
    payload = {
        "messages": msgs,
        "max_tokens": 150,
        "temperature": 0.9,
        "top_p": 0.9,
    }
    async with aiohttp.ClientSession() as session:
        async with session.post(url, json=payload, timeout=aiohttp.ClientTimeout(total=120)) as resp:
            data = await resp.json()
            return data["choices"][0]["message"]["content"].strip()


# Инициализация БД при импорте модуля
_init_db()
