from __future__ import annotations

import os
import re
from dataclasses import asdict, dataclass
from pathlib import Path
from types import SimpleNamespace
from typing import Any


DEFAULT_EXTENSIONS = {
    "photo": ".jpg",
    "document": "",
    "video": ".mp4",
    "animation": ".gif",
    "audio": ".mp3",
    "voice": ".ogg",
    "sticker": ".webp",
}

SAFE_NAME_RE = re.compile(r"[^A-Za-z0-9._-]+")


@dataclass(slots=True)
class TelegramInboundMedia:
    kind: str
    file_id: str
    file_unique_id: str | None = None
    original_name: str | None = None
    mime_type: str | None = None
    file_size: int | None = None
    caption: str | None = None
    message_text: str | None = None
    sticker_emoji: str | None = None

    def default_extension(self) -> str:
        if self.original_name:
            suffix = Path(self.original_name).suffix
            if suffix:
                return suffix.lower()
        if self.kind == "sticker" and self.mime_type == "application/x-tgsticker":
            return ".tgs"
        if self.kind == "sticker" and self.mime_type == "video/webm":
            return ".webm"
        return DEFAULT_EXTENSIONS.get(self.kind, "")

    def safe_stem(self) -> str:
        source = self.original_name or f"{self.kind}-{self.file_unique_id or self.file_id}"
        stem = Path(source).stem or source
        cleaned = SAFE_NAME_RE.sub("-", stem).strip("-._")
        return cleaned or self.kind

    def to_dict(self) -> dict[str, Any]:
        payload = asdict(self)
        payload["default_extension"] = self.default_extension()
        payload["safe_stem"] = self.safe_stem()
        return payload


def _pick_message_text(message: Any) -> str:
    return (getattr(message, "text", None) or getattr(message, "caption", None) or "").strip()


def _message_caption(message: Any) -> str | None:
    caption = getattr(message, "caption", None)
    return caption.strip() if isinstance(caption, str) and caption.strip() else None


def _document_name(document: Any, fallback: str) -> str:
    file_name = getattr(document, "file_name", None)
    if file_name:
        return file_name
    return fallback


def extract_telegram_media(message: Any) -> list[TelegramInboundMedia]:
    items: list[TelegramInboundMedia] = []
    message_text = _pick_message_text(message)
    caption = _message_caption(message)

    photos = getattr(message, "photo", None) or []
    if photos:
        photo = photos[-1]
        items.append(
            TelegramInboundMedia(
                kind="photo",
                file_id=str(photo.file_id),
                file_unique_id=str(getattr(photo, "file_unique_id", "") or ""),
                original_name=f"photo-{getattr(photo, 'file_unique_id', getattr(photo, 'file_id', 'image'))}.jpg",
                mime_type="image/jpeg",
                file_size=getattr(photo, "file_size", None),
                caption=caption,
                message_text=message_text,
            )
        )

    for kind in ["document", "video", "animation", "audio", "voice", "sticker"]:
        value = getattr(message, kind, None)
        if value is None:
            continue
        original_name = None
        if kind == "document":
            original_name = _document_name(value, f"document-{getattr(value, 'file_unique_id', getattr(value, 'file_id', 'file'))}")
        elif kind == "video":
            original_name = _document_name(value, f"video-{getattr(value, 'file_unique_id', getattr(value, 'file_id', 'video'))}.mp4")
        elif kind == "animation":
            original_name = _document_name(value, f"animation-{getattr(value, 'file_unique_id', getattr(value, 'file_id', 'animation'))}.gif")
        elif kind == "audio":
            original_name = _document_name(value, f"audio-{getattr(value, 'file_unique_id', getattr(value, 'file_id', 'audio'))}.mp3")
        elif kind == "voice":
            original_name = f"voice-{getattr(value, 'file_unique_id', getattr(value, 'file_id', 'voice'))}.ogg"
        elif kind == "sticker":
            original_name = f"sticker-{getattr(value, 'file_unique_id', getattr(value, 'file_id', 'sticker'))}"
        items.append(
            TelegramInboundMedia(
                kind=kind,
                file_id=str(value.file_id),
                file_unique_id=str(getattr(value, "file_unique_id", "") or ""),
                original_name=original_name,
                mime_type=getattr(value, "mime_type", None),
                file_size=getattr(value, "file_size", None),
                caption=caption,
                message_text=message_text,
                sticker_emoji=getattr(value, "emoji", None) if kind == "sticker" else None,
            )
        )

    return items


def build_inbox_root(config: dict[str, Any]) -> Path:
    data_dir = Path(str(config.get("data_dir") or Path(config.get("project_root", ".")) / "data")).expanduser().resolve()
    return data_dir / "telegram_inbox"


def build_media_save_path(
    inbox_root: str | Path,
    *,
    chat_id: int | str,
    message_id: int | str,
    item: TelegramInboundMedia,
) -> Path:
    root = Path(inbox_root).expanduser().resolve()
    chat_dir = root / str(chat_id)
    extension = item.default_extension()
    filename = f"msg{message_id}-{item.kind}-{item.safe_stem()}{extension}"
    return chat_dir / filename


def format_media_prompt_payload(
    *,
    message_text: str,
    saved_items: list[dict[str, Any]],
) -> str:
    lines: list[str] = []
    stripped = (message_text or "").strip()
    if stripped:
        lines.append(stripped)
    if saved_items:
        lines.append("[saved telegram attachments]")
        for item in saved_items:
            path = item.get("saved_path") or item.get("path") or ""
            kind = item.get("kind") or "file"
            original_name = item.get("original_name") or item.get("name") or ""
            details = f"kind={kind}; path={path}"
            if original_name:
                details += f"; original_name={original_name}"
            lines.append(details)
    return "\n".join(lines).strip() or "[empty message]"


async def _download_to_path(bot: Any, *, file_id: str, destination: Path) -> None:
    destination.parent.mkdir(parents=True, exist_ok=True)
    get_file = getattr(bot, "get_file", None)
    if not callable(get_file):
        raise RuntimeError("Telegram bot object does not support get_file")
    file_info = await get_file(file_id)
    file_path = getattr(file_info, "file_path", None)
    if not file_path:
        raise RuntimeError("Telegram get_file did not return file_path")

    download_file = getattr(bot, "download_file", None)
    if callable(download_file):
        await download_file(file_path, destination=str(destination))
        return

    download = getattr(bot, "download", None)
    if callable(download):
        await download(file_path, destination=str(destination))
        return

    raise RuntimeError("Telegram bot object does not support file download")


async def persist_telegram_media(
    bot: Any,
    message: Any,
    config: dict[str, Any],
) -> dict[str, Any]:
    inbox_root = build_inbox_root(config)
    media_items = extract_telegram_media(message)
    saved: list[dict[str, Any]] = []
    chat_id = getattr(getattr(message, "chat", None), "id", "unknown")
    message_id = getattr(message, "message_id", "unknown")

    for item in media_items:
        path = build_media_save_path(inbox_root, chat_id=chat_id, message_id=message_id, item=item)
        await _download_to_path(bot, file_id=item.file_id, destination=path)
        payload = item.to_dict()
        payload["saved_path"] = str(path)
        payload["exists"] = path.exists()
        saved.append(payload)

    message_text = _pick_message_text(message)
    return {
        "message_text": message_text,
        "caption": _message_caption(message),
        "saved_items": saved,
        "inbox_root": str(inbox_root),
        "prompt_payload": format_media_prompt_payload(message_text=message_text, saved_items=saved),
    }


def make_fake_message(**kwargs: Any) -> Any:
    return SimpleNamespace(**kwargs)
