"""Fun features — Hafez fal, Persian calendar, dictionary, photo↔sticker converter.

Inspired by DIGI ANTI's «فال حافظ», «تقویم», «معنی», «عکس به استیکر», «استیکر به عکس».
"""
from __future__ import annotations

import io
import logging
import random
from datetime import datetime
from typing import Optional

import httpx
from PIL import Image
from telegram import Update
from telegram.constants import ParseMode
from telegram.error import BadRequest
from telegram.ext import ContextTypes

from bot.persian_router import register_mixed

logger = logging.getLogger(__name__)


# ───────── Hafez fal — poems ─────────
HAFEZ_POEMS = [
    ("دل می‌رود ز دستم صاحب‌دلان خدا را\nز راه مهر تو هرگز به در نمی‌شود", "غزل ۱"),
    ("اگر آن ترک شیرازی به دست آرد دل ما را\nبه خال هندویش بخشم سمرقند و بخارا را", "غزل ۲"),
    ("سال‌ها دل طلب جام جم از ما می‌کرد\nوآنچه خود داشت ز بیگانه تمنّا می‌کرد", "غزل ۳"),
    ("صبا اگر گذری بر مردم بی‌وفا کن\nبه یاد آور که ما هم روزی در این خاکین", "غزل ۴"),
    ("سلام ای پیک دل پیغام‌بر یار سفر کن\nبه یاد آن عرق و آن لب گهربار سفر کن", "غزل ۵"),
    ("مرا عهدیست با جانان که تا جان در بدن دارم\nهواداران کویش را چو جان خویشتن دارم", "غزل ۶"),
    ("اگر خموشی رمزی دارد\nز من بپرس که من سخی نیستم", "رباعی ۱"),
    ("دلا بسوز که سوز تو کارها بکند\nنیاز نیم‌شبی دفعِ بلابکند", "رباعی ۲"),
    ("چون قلم اندر نوشتن می‌شتافت\nچون به عشق می‌رسد، می‌ایستد", "رباعی ۳"),
    ("میان گریه می‌خندم که چون شمعِ اندر این مجلس\nزبانِ آتشینم هست لیکن در نهان سوزم", "غزل ۱۰"),
    ("ای که در پیراهنی از من مگر زینهار نیست\nبر در میخانه‌ام مأوا و بُن بست‌وگر کاری‌ست", "غزل ۸"),
    ("نقد صوفی نه همه صافی بی‌غش باشد\nای بسا خرقه که مستوجبِ آتش باشد", "غزل ۱۲"),
    ("سحر با باد می‌گفتم حدیثِ آرزومندی\nخطی از سرّ دل بر لوحِ غیبی باز خواندم", "غزل ۲"),
    ("دل بد راهی داشت و ما را مهر آن دل در سینه\nدوش تا صبح ایستاده بودیم در پیشِ درگاهش", "غزل ۱۵"),
    ("گر صبا را یار باشد با غم ما\nمی‌نگنجد در دل اندوه غم ما", "غزل ۲۰"),
]

INTERPRETATIONS = [
    "تعبیر: روزای خوش در راهه. صبر داشته باش",
    "تعبیر: مژده برآورده شدن آرزوی دیرینه",
    "تعبیر: دل به دریا بزن، بخت باهاته",
    "تعبیر: یه کم مراقب باش، یه دشمنی تو راهه",
    "تعبیر: نیکی کن که عاقبتش خوشه",
    "تعبیر: صبر کلید همه چیزه",
    "تعبیر: به زودی یه خبر خوش می‌رسی",
    "تعبیر: راهی که میری درسته، ادامه بده",
    "تعبیر: به خودت تکیه کن، هیچکس بهتر از خودت نیست",
    "تعبیر: غم تموم میشه و شادی میاد",
]


async def hafez_cmd(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    """Hafez fal (fortune telling with poetry)."""
    poem, source = random.choice(HAFEZ_POEMS)
    interp = random.choice(INTERPRETATIONS)
    text = (
        f"<b>فال حافظ</b>\n"
        f"━━━━━━━━━━━━━━━\n"
        f"<i>{poem}</i>\n"
        f"{source}\n"
        f"━━━━━━━━━━━━━━━\n"
        f"{interp}\n\n"
        f"<i>خدایا هر چه صلاح توست همان مقدر باد</i>"
    )
    await update.message.reply_text(text, parse_mode=ParseMode.HTML)


# ───────── Persian calendar ─────────
PERSISH_MONTHS = [
    "فروردین", "اردیبهشت", "خرداد", "تیر", "مرداد", "شهریور",
    "مهر", "آبان", "آذر", "دی", "بهمن", "اسفند",
]
PERSISH_DAYS = [
    "یکشنبه", "دوشنبه", "سه‌شنبه", "چهارشنبه",
    "پنج‌شنبه", "جمعه", "شنبه",
]


def _gregorian_to_jalali(gy: int, gm: int, gd: int) -> tuple[int, int, int]:
    """Gregorian to Jalali (Persian) date conversion."""
    g_d_m = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334]
    if gy <= 1600:
        jy = 0
        gy -= 621
    else:
        jy = 979
        gy -= 1600
    gy2 = gy + 1 if gm > 2 else gy
    days = 365 * gy + (gy2 + 3) // 4 - (gy2 + 99) // 100 + (gy2 + 399) // 400 - 80 + gd + g_d_m[gm - 1]
    jy += 33 * (days // 12053)
    days %= 12053
    jy += 4 * (days // 1461)
    days %= 1461
    if days > 365:
        jy += (days - 1) // 365
        days = (days - 1) % 365
    if days < 186:
        jm = 1 + days // 31
        jd = 1 + days % 31
    else:
        jm = 7 + (days - 186) // 30
        jd = 1 + (days - 186) % 30
    return jy, jm, jd


async def calendar_cmd(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    """Show today's Persian (Shamsi) calendar date."""
    now = datetime.now()
    jy, jm, jd = _gregorian_to_jalali(now.year, now.month, now.day)
    day_of_week = PERSISH_DAYS[now.weekday()]
    month_name = PERSISH_MONTHS[jm - 1]

    # Day of year
    days_in_month = [31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 29]
    if (jy + 38) % 33 % 4 == 0:  # leap year correction
        days_in_month[11] = 30
    day_of_year = sum(days_in_month[: jm - 1]) + jd
    days_left = sum(days_in_month) - day_of_year
    percent = round(day_of_year / 366 * 100, 1)

    text = (
        f"<b>تقویم شمسی</b>\n"
        f"━━━━━━━━━━━━━━━\n"
        f"<b>{day_of_week} {jd} {month_name} {jy}</b>\n\n"
        f"میلادی: {now.year}/{now.month:02d}/{now.day:02d}\n"
        f"روز سال: {day_of_year} از ۳۶۶ ({percent}%)\n"
        f"باقی‌مانده: {days_left} روز\n"
        f"ساعت: {now.strftime('%H:%M:%S')}\n"
    )
    await update.message.reply_text(text, parse_mode=ParseMode.HTML)


# ───────── Dictionary lookup ─────────
DICT_TIMEOUT = 8.0


async def dictionary_cmd(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    """Look up a word's meaning from vajehyab.com."""
    text = update.message.text or ""
    parts = text.split(maxsplit=1)
    if len(parts) < 2:
        await update.message.reply_text(
            "مثلا بنویس <code>معنی عشق</code>",
            parse_mode=ParseMode.HTML,
        )
        return
    word = parts[1].strip()
    if not word:
        await update.message.reply_text("کلمه‌ای بنویس بعد امتحان کن")
        return

    status = await update.message.reply_text("دارم دنبالش می‌گردم...")
    try:
        async with httpx.AsyncClient(timeout=DICT_TIMEOUT, follow_redirects=True) as client:
            r = await client.get("https://www.vajehyab.com/", params={"q": word})
        if r.status_code != 200:
            await status.edit_text(f"نتونستم اطلاعات بگیرم (HTTP {r.status_code})")
            return
        html = r.text
        # Naive scrape for the first definition paragraph
        import re
        m = re.search(r'<p class="">(.+?)</p>', html, re.S | re.I)
        if not m:
            await status.edit_text(f"چیزی به اسم «{word}» پیدا نکردم")
            return
        from html import unescape
        raw = m.group(1)
        raw = re.sub(r'<[a-z0-9]+?>.+?</[a-z0-9]+?>|&.+?;', '', raw, flags=re.S)
        raw = re.sub(r'<br.+?>', '\n', raw, flags=re.S)
        clean = unescape(raw).strip()
        if not clean:
            await status.edit_text(f"چیزی به اسم «{word}» پیدا نکردم")
            return
        # Telegram max message length
        if len(clean) > 3500:
            clean = clean[:3500] + "..."
        await status.edit_text(
            f"<b>{word}</b>\n━━━━━━━━━━━━━━━\n{clean}",
            parse_mode=ParseMode.HTML,
        )
    except httpx.TimeoutException:
        await status.edit_text("نتایج خیلی دیر شدن، بعداً امتحان کن")
    except Exception as e:
        logger.exception("Dictionary lookup error: %s", e)
        await status.edit_text("یه مشکلی پیش اومد، بعداً امتحان کن")


# ───────── Photo ↔ Sticker converter ─────────
async def _download_photo_bytes(bot, file_id: str) -> bytes:
    f = await bot.get_file(file_id)
    bio = io.BytesIO()
    await f.download_to_memory(out=bio)
    return bio.getvalue()


async def photo2sticker_cmd(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    """Convert a photo (or replied-to photo) into a static sticker."""
    msg = update.message
    target = msg.reply_to_message if msg.reply_to_message and msg.reply_to_message.photo else None
    if not target:
        await msg.reply_text("روی یه عکس ریپلای کن تا استیکرش کنم")
        return
    photo = target.photo[-1]
    status = await msg.reply_text("دارم تبدیلش می‌کنم...")
    try:
        data = await _download_photo_bytes(context.bot, photo.file_id)
        img = Image.open(io.BytesIO(data)).convert("RGBA")
        # Resize to fit Telegram sticker constraints (512x512 max, must be square)
        side = 512
        # Crop to square (center)
        w, h = img.size
        min_side = min(w, h)
        left = (w - min_side) // 2
        top = (h - min_side) // 2
        img = img.crop((left, top, left + min_side, top + min_side))
        img = img.resize((side, side), Image.LANCZOS)
        out = io.BytesIO()
        out.name = "sticker.webp"
        img.save(out, format="WEBP", quality=90, method=6)
        out.seek(0)
        await status.delete()
        await msg.reply_sticker(sticker=out)
    except Exception as e:
        logger.exception("photo2sticker error: %s", e)
        try:
            await status.edit_text("نتونستم عکسو به استیکر تبدیل کنم")
        except BadRequest:
            pass


async def sticker2photo_cmd(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    """Convert a sticker (replied-to) into a PNG photo."""
    msg = update.message
    target = msg.reply_to_message if msg.reply_to_message and msg.reply_to_message.sticker else None
    if not target:
        await msg.reply_text("روی یه استیکر ریپلای کن تا عکسش کنم")
        return
    sticker = target.sticker
    if sticker.is_animated or sticker.is_video:
        await msg.reply_text("استیکر متحرک رو نمی‌تونم تبدیل کنم")
        return
    status = await msg.reply_text("دارم تبدیلش می‌کنم...")
    try:
        data = await _download_photo_bytes(context.bot, sticker.file_id)
        img = Image.open(io.BytesIO(data)).convert("RGBA")
        # Composite onto a white background
        bg = Image.new("RGBA", img.size, (255, 255, 255, 255))
        bg.alpha_composite(img)
        bg = bg.convert("RGB")
        out = io.BytesIO()
        out.name = "photo.png"
        bg.save(out, format="PNG")
        out.seek(0)
        await status.delete()
        await msg.reply_photo(photo=out)
    except Exception as e:
        logger.exception("sticker2photo error: %s", e)
        try:
            await status.edit_text("نتونستم استیکرو به عکس تبدیل کنم")
        except BadRequest:
            pass


# ───────── Echo (بگو) ─────────
async def say_cmd(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    """Echo back the user's text (like DIGI ANTI's بگوو)."""
    text = update.message.text or ""
    parts = text.split(maxsplit=1)
    if len(parts) < 2:
        await update.message.reply_text("بعد از <code>بگو</code> یه چیزی هم بنویس", parse_mode=ParseMode.HTML)
        return
    await update.message.reply_text(parts[1])


# ───────── Registration ─────────
def register(app) -> None:
    register_mixed(
        app,
        {
            "hafez": hafez_cmd,
            "fal": hafez_cmd,
            "calendar": calendar_cmd,
            "date": calendar_cmd,
            "dictionary": dictionary_cmd,
            "photo2sticker": photo2sticker_cmd,
            "sticker2photo": sticker2photo_cmd,
            "say": say_cmd,
            "echo": say_cmd,
        },
        {
            "فال حافظ": hafez_cmd,
            "فال": hafez_cmd,
            "حافظ": hafez_cmd,
            "فال بگیر": hafez_cmd,
            "یک فال بگیر": hafez_cmd,
            "فال حافظ بده": hafez_cmd,
            "نغمه": hafez_cmd,
            "تقویم": calendar_cmd,
            "تقویم شمسی": calendar_cmd,
            "تاریخ": calendar_cmd,
            "امروز چندمه": calendar_cmd,
            "امروز چندشنبه است": calendar_cmd,
            "چندم هستیم": calendar_cmd,
            "تاریخ امروز": calendar_cmd,
            "معنی": dictionary_cmd,
            "معنی کلمه": dictionary_cmd,
            "دیکشنری": dictionary_cmd,
            "یعنی چی": dictionary_cmd,
            "معنیش چیه": dictionary_cmd,
            "عکس به استیکر": photo2sticker_cmd,
            "عکسو استیکر کن": photo2sticker_cmd,
            "استیکرش کن": photo2sticker_cmd,
            "استیکر به عکس": sticker2photo_cmd,
            "استیکرو عکس کن": sticker2photo_cmd,
            "عکسش کن": sticker2photo_cmd,
            "بگو": say_cmd,
            "تکرار کن": say_cmd,
            "بگوو": say_cmd,
            "بگو دوباره": say_cmd,
        },
    )
