"""Ловля сообщений по ключевым словам — без команд панели."""

from __future__ import annotations

import html
from typing import Awaitable, Callable, Optional

from telethon import TelegramClient, events
from telethon.tl.types import Channel

from db import get_watch_settings

AlertSender = Callable[[dict], Awaitable[None]]
_send_alert: Optional[AlertSender] = None


def set_alert_sender(fn: AlertSender) -> None:
    global _send_alert
    _send_alert = fn


def _internal_chat_id(chat, marked_chat_id=None) -> str | None:
    if isinstance(chat, Channel):
        return str(abs(int(chat.id)))
    marked = marked_chat_id if marked_chat_id is not None else getattr(chat, "id", None)
    if marked is None:
        return None
    raw = str(int(marked))
    if raw.startswith("-100"):
        return raw[4:]
    return None


def _topic_id(message) -> int | None:
    reply_to = getattr(message, "reply_to", None)
    if reply_to is None or not getattr(reply_to, "forum_topic", False):
        return None
    topic = getattr(reply_to, "reply_to_top_id", None) or getattr(reply_to, "reply_to_msg_id", None)
    try:
        topic = int(topic)
    except (TypeError, ValueError):
        return None
    return topic if topic > 0 else None


def _message_link(chat, message, marked_chat_id=None) -> str:
    message_id = int(getattr(message, "id", 0) or 0)
    if message_id <= 0:
        return ""
    username = (getattr(chat, "username", None) or "").strip()
    topic = _topic_id(message)
    if username:
        if topic:
            return f"https://t.me/{username}/{topic}/{message_id}"
        return f"https://t.me/{username}/{message_id}"
    internal = _internal_chat_id(chat, marked_chat_id)
    if not internal:
        return ""
    if topic:
        return f"https://t.me/c/{internal}/{topic}/{message_id}"
    return f"https://t.me/c/{internal}/{message_id}"


def _match_keywords(text: str, keywords: list[str]) -> list[str]:
    haystack = text.casefold()
    found = []
    for word in keywords:
        needle = word.casefold()
        if needle and needle in haystack:
            found.append(word)
    return found


def _sender_label(sender) -> str:
    if sender is None:
        return ""
    username = (getattr(sender, "username", None) or "").strip()
    title = (getattr(sender, "title", None) or "").strip()
    name = title or " ".join(
        part for part in (
            getattr(sender, "first_name", None),
            getattr(sender, "last_name", None),
        ) if part
    ).strip()
    if username and name:
        return f"{name} (@{username})"
    if username:
        return f"@{username}"
    if name:
        return name
    sender_id = getattr(sender, "id", None)
    return str(sender_id) if sender_id else ""


def format_watch_alert(payload: dict) -> tuple[str, Optional[dict]]:
    keyword = html.escape(str(payload.get("keyword") or ""))
    chat_title = html.escape(str(payload.get("chat_title") or payload.get("chat_id") or "чат"))
    who = html.escape(str(payload.get("sender") or "").strip())
    body = html.escape(str(payload.get("text") or ""))[:3500]
    link = str(payload.get("link") or "").strip()
    lines = [
        f"<tg-emoji emoji-id='5276395476646653290'>🔍</tg-emoji> <b>Ключевое слово:</b> <code>{keyword}</code>",
        f"<tg-emoji emoji-id='5278528159837348960'>📢</tg-emoji> <b>Чат:</b> {chat_title}",
    ]
    if who:
        lines.append(f"<tg-emoji emoji-id='5275979556308674886'>👤</tg-emoji> <b>Кто:</b> {who}")
    markup = None
    if link.startswith("https://"):
        safe_link = html.escape(link, quote=True)
        lines.append("")
        lines.append(
            f'<tg-emoji emoji-id="5278305362703835500">🔗</tg-emoji> '
            f'<a href="{safe_link}"><b>Перейти к сообщению</b></a>'
        )
        markup = {
            "inline_keyboard": [[{
                "text": "Перейти к сообщению",
                "url": link,
                "style": "primary",
                "icon_custom_emoji_id": "5206222720416643915",
            }]],
        }
    lines.append("")
    lines.append("<tg-emoji emoji-id='5206222720416643915'>🔔</tg-emoji> <b>Сообщение:</b>")
    lines.append(body)
    return "\n".join(lines), markup


async def handle_watch_message(
    event: events.NewMessage.Event,
    owner_tg_id: int,
    seen: set[tuple[int, int]],
) -> None:
    if event.out:
        return
    text = (event.raw_text or "").strip()
    if not text:
        return
    settings = get_watch_settings(owner_tg_id)
    if not settings.get("enabled"):
        return
    max_chars = settings.get("max_chars")
    try:
        max_chars = int(max_chars) if max_chars not in (None, "") else None
    except (TypeError, ValueError):
        max_chars = None
    if max_chars and len(text) > max_chars:
        return
    keywords = settings.get("keywords") or []
    chats = settings.get("chats") or []
    if not keywords or not chats:
        return
    watched = {int(item["id"]) for item in chats if item.get("id") is not None}
    chat_id = event.chat_id
    if chat_id not in watched:
        return
    alert_id = settings.get("alert_chat_id")
    try:
        alert_id = int(alert_id) if alert_id not in (None, "") else int(owner_tg_id)
    except (TypeError, ValueError):
        alert_id = int(owner_tg_id)
    if chat_id == alert_id:
        return
    sender = event.sender
    if sender is not None and getattr(sender, "bot", False):
        return
    matched = _match_keywords(text, keywords)
    if not matched:
        return
    key = (int(chat_id), int(event.id))
    if key in seen:
        return
    seen.add(key)
    if len(seen) > 4000:
        seen.clear()
        seen.add(key)
    if sender is None:
        try:
            sender = await event.get_sender()
        except Exception:
            sender = None
    if sender is not None and getattr(sender, "bot", False):
        return
    chat = await event.get_chat()
    title = getattr(chat, "title", None) or " ".join(
        part for part in (
            getattr(chat, "first_name", None),
            getattr(chat, "last_name", None),
        ) if part
    ) or str(chat_id)
    payload = {
        "owner_tg_id": int(owner_tg_id),
        "alert_chat_id": alert_id,
        "keyword": ", ".join(matched),
        "chat_id": chat_id,
        "chat_title": title,
        "sender": _sender_label(sender),
        "text": text,
        "link": _message_link(chat, event.message, chat_id),
    }
    if _send_alert is None:
        return
    await _send_alert(payload)


def register_watch_handlers(client: TelegramClient, owner_tg_id: int, seen: set[tuple[int, int]]) -> None:
    if getattr(client, "_catcher_watch", False):
        return
    client._catcher_watch = True

    @client.on(events.NewMessage(incoming=True))
    async def on_watch(event: events.NewMessage.Event) -> None:
        try:
            await handle_watch_message(event, owner_tg_id, seen)
        except Exception as exc:
            print(f"catcher watch [{owner_tg_id}]: {type(exc).__name__}: {exc}")
