"""Twitter/X downloader — multi-source: yt-dlp primary, x.com API, Nitter fallback.
v3.0: yt-dlp priority for reliable video, guest API for metadata, robust error handling."""
from __future__ import annotations

import json
import logging
import re
import time
from urllib.parse import urlparse, urlunparse

from telegram import InlineKeyboardButton, InlineKeyboardMarkup, InputMediaPhoto, Update
from telegram.constants import ParseMode
from telegram.ext import CallbackQueryHandler, ContextTypes, MessageHandler, filters

from bot.handlers.dl_utils import (
    TEMP_DIR,
    best_quality_img,
    cache_get,
    cache_set,
    download_file,
    dl_via_ytdlp,
    escape_md,
    format_size,
    get_http_client,
)

logger = logging.getLogger(__name__)

TWEET_RE = re.compile(
    r"(?:https?://)?(?:www\.|mobile\.)?(?:twitter\.com|x\.com)/(\w+)/status/(\d{6,25})",
    re.IGNORECASE,
)
IMAGE_URL_RE = re.compile(r"https://pbs\.twimg\.com/media/[a-zA-Z0-9_\-]+", re.IGNORECASE)
VIDEO_HINT_RE = re.compile(r'"type"\s*:\s*"video"|"video_info"|video\.twimg\.com', re.IGNORECASE)

NITTER_INSTANCES = [
    "https://nitter.net",
    "https://nitter.cz",
    "https://nitter.space",
    "https://nitter.privacydev.net",
    "https://nitter.poast.org",
]

VIDEO_QUALITIES = [
    ("🎥 خودکار", "best[ext=mp4]/best"),
    ("🖥 کیفیت بالا", "bestvideo[height<=1080]+bestaudio/best[height<=1080]"),
    ("💻 متوسط", "best[height<=720]"),
    ("📱 کم", "best[height<=360]"),
    ("🎵 فقط صدا", "bestaudio/best"),
]

_pending: dict[tuple[int, int], dict] = {}
PENDING_TTL = 300


def _extract_all_tweet_ids(text: str) -> list[tuple[str, str]]:
    return [(m.group(1), m.group(2)) for m in TWEET_RE.finditer(text)]


def _normalize_img_url(url: str) -> str:
    return urlunparse(urlparse(url)._replace(params="", query="", fragment=""))


def _dedup_images(urls: list[str]) -> list[str]:
    groups: dict[str, list[str]] = {}
    for u in urls:
        groups.setdefault(_normalize_img_url(u), []).append(u)
    result = []
    for key, variants in groups.items():
        preferred = variants[0]
        for v in variants:
            if "name=orig" in v:
                preferred = v
                break
            if "name=large" in v:
                preferred = v
        result.append(preferred)
    return [best_quality_img(u) for u in result]


def _fmt_count(n: int | None) -> str:
    if n is None:
        return "?"
    if n >= 1_000_000:
        return f"{n/1_000_000:.1f}M"
    if n >= 1_000:
        return f"{n//1000}K"
    return str(n)


async def twitter_handler(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    message = update.message
    if not message or not message.text:
        return

    tweets = _extract_all_tweet_ids(message.text)
    if not tweets:
        return

    status_msg = await message.reply_text("🔍 دریافت اطلاعات توییت...")

    for idx, (handle, tweet_id) in enumerate(tweets, 1):
        total = len(tweets)
        prefix = f"[{idx}/{total}] " if total > 1 else ""

        await status_msg.edit_text(f"{prefix}🔍 دریافت اطلاعات توییت...")

        data = cache_get(f"twitter:{tweet_id}")
        if not data:
            data = await _fetch_tweet(tweet_id, handle)
            if data:
                cache_set(f"twitter:{tweet_id}", data)

        if not data:
            await status_msg.edit_text(f"{prefix}❌ توییت دریافت نشد (حذف/خصوصی شده یا لینک نامعتبر).")
            continue

        await _send_tweet(message, data, status_msg, prefix)

    if status_msg.text and "❌" not in status_msg.text:
        await status_msg.delete()


async def _send_tweet(message, data: dict, status_msg, prefix: str = "") -> None:
    handle = data["author_handle"]
    text = data["text"]
    images = data.get("images", [])
    video_url = data.get("video_url", "")
    tweet_id = data["tweet_id"]
    likes = data.get("likes")
    retweets = data.get("retweets")
    replies = data.get("replies")
    date_str = data.get("date", "")

    caption_parts = []
    if text:
        text_clean = re.sub(r"https://t\.co/\w+", "", text).strip()[:400]
        if text_clean:
            caption_parts.append(escape_md(text_clean))

    stats = []
    if likes is not None:
        stats.append(f"❤️ {_fmt_count(likes)}")
    if retweets is not None:
        stats.append(f"🔄 {_fmt_count(retweets)}")
    if replies is not None:
        stats.append(f"💬 {_fmt_count(replies)}")
    stats.append(f"👤 @{handle}")
    if stats:
        caption_parts.append("  ".join(stats))
    if date_str:
        caption_parts.append(f"📅 {date_str[:10]}")
    caption = "\n\n".join(caption_parts)[:1024]

    tweet_url = f"https://x.com/{handle}/status/{tweet_id}"

    # Text-only tweet
    if not images and not video_url:
        kb = InlineKeyboardMarkup([
            [InlineKeyboardButton("🔗 مشاهده", url=tweet_url)],
        ])
        await message.reply_text(
            f"{prefix}📝 {caption}", parse_mode=ParseMode.MARKDOWN, reply_markup=kb,
        )
        return

    # Video — try yt-dlp
    if video_url:
        await status_msg.edit_text(f"{prefix}📥 دانلود ویدیو...")
        output = await dl_via_ytdlp(tweet_url, f"tw_{tweet_id}_video.mp4", "best[ext=mp4]/best")
        if not output:
            await status_msg.edit_text(f"{prefix}کیفیت ویدیو را انتخاب کنید:")
            await _send_video_buttons(message, data, status_msg, prefix)
            return
        try:
            filesize = output.stat().st_size
            await status_msg.edit_text(f"{prefix}📤 آپلود... ({format_size(filesize)})")
            kb = InlineKeyboardMarkup([
                [InlineKeyboardButton("🔗 مشاهده", url=tweet_url)],
            ])
            with open(output, "rb") as f:
                if filesize > 50 * 1024 * 1024:
                    await message.reply_document(
                        f, caption=caption, filename=f"@{handle}_tweet_{tweet_id}.mp4",
                        write_timeout=300,
                    )
                else:
                    await message.reply_video(
                        f, caption=caption, write_timeout=180,
                        supports_streaming=True, reply_markup=kb,
                    )
        finally:
            output.unlink(missing_ok=True)
        return

    # Images
    if len(images) == 1:
        await status_msg.edit_text(f"{prefix}📥 دانلود عکس...")
        path = await download_file(images[0], f"tw_{tweet_id}_0.jpg")
        if path:
            try:
                kb = InlineKeyboardMarkup([
                    [InlineKeyboardButton("🔗 مشاهده", url=tweet_url)],
                ])
                with open(path, "rb") as f:
                    await message.reply_photo(f, caption=caption, reply_markup=kb)
            finally:
                path.unlink(missing_ok=True)
        else:
            await message.reply_text(f"{prefix}❌ دانلود عکس ناموفق.")
        return

    # Multiple images
    await status_msg.edit_text(f"{prefix}📥 دانلود {len(images)} عکس...")
    media_group, cleanup = [], []
    for i, img_url in enumerate(images[:10]):
        path = await download_file(img_url, f"tw_{tweet_id}_{i}.jpg")
        if path:
            try:
                with open(path, "rb") as f:
                    media_group.append(
                        InputMediaPhoto(media=f.read(), caption=caption if i == 0 else None)
                    )
                cleanup.append(path)
            except Exception:
                path.unlink(missing_ok=True)
    if media_group:
        try:
            await status_msg.edit_text(f"{prefix}📤 آپلود...")
            await message.reply_media_group(media_group)
        finally:
            for p in cleanup:
                try:
                    p.unlink(missing_ok=True)
                except Exception:
                    pass
    else:
        await message.reply_text(f"{prefix}❌ دانلود عکس‌ها ناموفق.")


async def _send_video_buttons(message, data: dict, status_msg, prefix: str) -> None:
    tweet_id = data["tweet_id"]
    handle = data["author_handle"]

    buttons = [
        [InlineKeyboardButton(label, callback_data=f"twdl:{fmt}:{tweet_id}:{handle}")]
        for label, fmt in VIDEO_QUALITIES
    ]
    sent = await message.reply_text(
        f"{prefix}کیفیت مورد نظر را انتخاب کنید:",
        reply_markup=InlineKeyboardMarkup(buttons),
    )
    key = (message.chat_id, sent.message_id)
    _pending[key] = {
        "handle": handle,
        "tweet_id": tweet_id,
        "timeout": time.monotonic() + PENDING_TTL,
    }
    try:
        await status_msg.delete()
    except Exception:
        pass


async def tw_callback(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    query = update.callback_query
    if not query or not query.data:
        return
    await query.answer()

    parts = query.data.split(":", 3)
    if len(parts) < 3:
        return

    action, quality, tweet_id = parts[0], parts[1], parts[2]
    handle = parts[3] if len(parts) > 3 else "i"

    if action != "twdl":
        return

    key = (query.message.chat_id, query.message.message_id)
    pending = _pending.pop(key, None)
    if not pending:
        await query.edit_message_text("⏰ زمان انتخاب گذشته. دوباره لینک را بفرستید.")
        return

    await query.edit_message_text("📥 در حال دانلود...")
    tweet_url = f"https://x.com/{handle}/status/{tweet_id}"

    is_audio = "bestaudio" in quality
    out_fn = f"tw_{tweet_id}_sel.{'mp3' if is_audio else 'mp4'}"

    if is_audio:
        output = await dl_via_ytdlp(tweet_url, out_fn, quality, postprocessor_args=["-f", "mp3"])
    else:
        output = await dl_via_ytdlp(tweet_url, out_fn, quality)

    if not output:
        await query.edit_message_text("❌ دانلود ناموفق. لینک را دوباره بفرستید.")
        return

    try:
        filesize = output.stat().st_size
        caption = f"👤 @{handle}"
        await query.edit_message_text(f"📤 آپلود... ({format_size(filesize)})")
        if is_audio:
            with open(output, "rb") as f:
                await query.message.reply_audio(f, caption=caption, write_timeout=180)
        elif filesize > 50 * 1024 * 1024:
            with open(output, "rb") as f:
                await query.message.reply_document(
                    f, caption=caption, filename=f"@{handle}_tweet_{tweet_id}.mp4",
                    write_timeout=300,
                )
        else:
            kb = InlineKeyboardMarkup([
                [InlineKeyboardButton("🔗 مشاهده", url=tweet_url)],
            ])
            with open(output, "rb") as f:
                await query.message.reply_video(
                    f, caption=caption, write_timeout=180,
                    supports_streaming=True, reply_markup=kb,
                )
    finally:
        output.unlink(missing_ok=True)
    try:
        await query.message.delete()
    except Exception:
        pass


async def _fetch_tweet(tweet_id: str, expected_handle: str) -> dict | None:
    """Multi-source fetch: yt-dlp metadata → x.com scrape → Nitter."""
    # Source 1: yt-dlp metadata (best for videos, gets full info)
    data = await _fetch_via_ytdlp(tweet_id, expected_handle)
    if data:
        return data
    # Source 2: x.com scrape (HTML parsing)
    data = await _scrape_xcom(tweet_id)
    if data:
        return data
    # Source 3: Nitter instances
    for base in NITTER_INSTANCES:
        data = await _scrape_nitter(tweet_id, base)
        if data:
            return data
    # Source 4: minimal fallback with just the URL
    return {
        "author_handle": expected_handle,
        "author_name": "",
        "text": "",
        "images": [],
        "video_url": "ytdlp",
        "likes": None,
        "retweets": None,
        "replies": None,
        "date": "",
        "tweet_id": tweet_id,
    }


async def _fetch_via_ytdlp(tweet_id: str, handle: str) -> dict | None:
    """Use yt-dlp to extract tweet metadata (best for videos)."""
    url = f"https://x.com/{handle}/status/{tweet_id}"
    try:
        from bot.handlers.dl_utils import extract_info_ytdlp
        info = await extract_info_ytdlp(url)
        if not info:
            return None

        video_url = "ytdlp" if info.get("ext") in ("mp4", "mov", "webm") or info.get("formats") else ""
        thumb = info.get("thumbnail", "")

        # Get best quality images from thumbnails
        images = []
        if thumb and "pbs.twimg.com" in thumb:
            images.append(best_quality_img(thumb))

        return {
            "author_handle": info.get("channel_id", info.get("uploader", "")) or handle,
            "author_name": info.get("uploader", ""),
            "text": info.get("title", info.get("description", ""))[:1000],
            "images": images,
            "video_url": video_url,
            "likes": info.get("like_count"),
            "retweets": None,
            "replies": info.get("comment_count"),
            "date": str(info.get("upload_date", "")),
            "tweet_id": tweet_id,
        }
    except Exception as e:
        logger.debug("yt-dlp metadata failed for %s: %s", url, e)
        return None


async def _scrape_xcom(tweet_id: str) -> dict | None:
    url = f"https://x.com/i/status/{tweet_id}"
    try:
        async with get_http_client(timeout=15.0) as client:
            r = await client.get(url)
            if r.status_code != 200:
                logger.debug("x.com %s for %s", r.status_code, tweet_id)
                return None
            html = r.text
    except Exception as e:
        logger.debug("x.com scrape error: %s", e)
        return None

    author_handle = author_name = text = ""
    likes = retweets = replies = None
    date_str = ""

    # Try ld+json first
    for m in re.finditer(r'<script type="application/ld\+json"[^>]*>(.*?)</script>', html, re.DOTALL):
        try:
            data = json.loads(m.group(1))
            if isinstance(data, dict) and data.get("@type") == "SocialMediaPosting":
                text = data.get("articleBody", "") or text
                author = data.get("author", {}) or {}
                author_name = author.get("name", "") or author_name
                author_handle = author.get("alternateName", "").lstrip("@") or author_handle
                date_str = data.get("datePublished", "") or date_str
                for stat in data.get("interactionStatistic", []):
                    t = stat.get("interactionType", "")
                    c = stat.get("userInteractionCount")
                    if c is not None:
                        if "Like" in t:
                            likes = int(c)
                        elif "Share" in t or "Retweet" in t:
                            retweets = int(c)
                        elif "Comment" in t:
                            replies = int(c)
        except Exception:
            pass

    # Fallback: meta description
    if not text:
        m = re.search(r'<meta[^>]*name="description"[^>]*content="([^"]+)"', html)
        if m:
            text = m.group(1)
    if not author_handle:
        m = re.search(rf"https://x\.com/(\w+)/status/{tweet_id}", html)
        if m:
            author_handle = m.group(1)

    # Try __INITIAL_STATE__ JSON (rich data embedded by Twitter)
    if not text or not author_handle:
        m = re.search(r'window\.__INITIAL_STATE__\s*=\s*({.*?});', html, re.DOTALL)
        if m:
            try:
                state = json.loads(m.group(1))
                entries = (
                    state.get("entries", {})
                    or state.get("threadedConversation", {}).get("entries", {})
                    or {}
                )
                for entry_id, entry in entries.items():
                    if tweet_id in entry_id:
                        content = entry.get("content", {}).get("itemContent", {}).get("tweet_results", {}).get("result", {})
                        if content:
                            core = content.get("core", {}).get("user_results", {}).get("result", {})
                            leg = core.get("legacy", {})
                            author_handle = leg.get("screen_name", "") or author_handle
                            author_name = leg.get("name", "") or author_name
                            legacy = content.get("legacy", {})
                            text = legacy.get("full_text", legacy.get("text", "")) or text
                            date_str = legacy.get("created_at", "") or date_str
                            likes = legacy.get("favorite_count", likes)
                            retweets = legacy.get("retweet_count", retweets)
                            replies = legacy.get("reply_count", replies)
                            break
            except Exception:
                pass

    raw_images = IMAGE_URL_RE.findall(html)
    images = _dedup_images(raw_images)
    images = [img for img in images if "profile" not in img.lower()]

    video_url = "ytdlp" if VIDEO_HINT_RE.search(html) else ""

    if not author_handle and not text and not images and not video_url:
        return None

    return {
        "author_handle": author_handle or "i",
        "author_name": author_name or author_handle,
        "text": (text or "").strip()[:1000],
        "images": images,
        "video_url": video_url,
        "likes": likes,
        "retweets": retweets,
        "replies": replies,
        "date": date_str[:10] if date_str else "",
        "tweet_id": tweet_id,
    }


async def _scrape_nitter(tweet_id: str, base: str) -> dict | None:
    try:
        async with get_http_client(timeout=10.0) as client:
            r = await client.get(f"{base}/i/status/{tweet_id}/")
            if r.status_code != 200:
                return None
            html = r.text
    except Exception:
        return None

    text = author_handle = ""
    images = []
    video_url = ""

    m = re.search(r'<div class="tweet-content[^"]*"[^>]*>(.*?)</div>', html, re.DOTALL)
    if m:
        text = re.sub(r"<[^>]+>", "", m.group(1)).strip()

    m = re.search(r'<a class="username"[^>]*>@(\w+)</a>', html)
    if m:
        author_handle = m.group(1)

    raw = re.findall(r"https?://pbs\.twimg\.com/media/[a-zA-Z0-9_\-]+", html)
    images = _dedup_images(raw)
    images = [img for img in images if "profile" not in img.lower()]

    m = re.search(r'<source src="([^"]+)"', html)
    if m:
        video_url = m.group(1)

    return {
        "author_handle": author_handle or "i",
        "author_name": author_handle,
        "text": (text or "").strip()[:1000],
        "images": images,
        "video_url": video_url,
        "likes": None,
        "retweets": None,
        "replies": None,
        "date": "",
        "tweet_id": tweet_id,
    }


def register(app) -> None:
    app.add_handler(
        MessageHandler(filters.TEXT & ~filters.COMMAND, twitter_handler),
        group=-2,
    )
    app.add_handler(
        CallbackQueryHandler(tw_callback, pattern=r"^twdl:"),
        group=-2,
    )
    logger.info("Twitter downloader registered (v3.0 multi-source)")
