"""
ربات گردآورنده محتوای تلگرام — نسخه کامل
==========================================
قابلیت‌ها:
  • فیلتر مخصوص هر کانال (همکار/خبرگزاری-whitelist/قیمت‌دار-exclude_price)
  • تجمیع آلبوم (چند عکس یک پست با هم)
  • نمایش نسخه خام + بازنویسی‌شده در ربات
  • ویرایش دستی متن قبل از انتشار (دکمه ✏️)
  • حذف تک‌تک عکس‌های آلبوم (دکمه 🗑 یا تایپ «حذف ۳»)
  • انتشار فوری یا زمان‌بندی‌شده (با schedule خود تلگرام)
  • انتخاب خودکار امضای مرتبط با AI، بعد از تایید

اجرا:  python main.py
"""
from __future__ import annotations
import asyncio
import json
import os
import re
import uuid
from datetime import datetime, timedelta, timezone

from telethon import TelegramClient, events, Button

import config
import filters
import signatures as sigmod

# اطمینان از وجود پوشه‌ی data (برای session و فایل‌های موقت)
os.makedirs("data", exist_ok=True)

user_client = TelegramClient(
    config.SESSION_NAME, config.API_ID, config.API_HASH,
    connection_retries=None,      # تلاش نامحدود برای اتصال مجدد
    retry_delay=2,                # فاصله‌ی هر تلاش (ثانیه)
    auto_reconnect=True,
)
bot_client = TelegramClient(
    "data/bot_session", config.API_ID, config.API_HASH,
    connection_retries=None,
    retry_delay=2,
    auto_reconnect=True,
)

# صف در انتظار تایید
#   pid -> {
#     "body": str,                # بدنه‌ی پاک‌شده (بدون امضا)
#     "rewritten_body": str|None, # بدنه‌ی بازنویسی‌شده
#     "media_paths": [str],       # مسیر فایل‌های آلبوم (ممکن است خالی باشد)
#     "chat_id": ...,
#   }
pending = {}

# حالت ویرایش متن:  admin_id -> pid
awaiting_edit = {}
# حالت زمان‌بندی:   admin_id -> (pid, which)   که which یکی از raw/rew است
awaiting_schedule = {}

# بافر تجمیع آلبوم:  grouped_id -> {"messages":[...], "task": handle}
album_buffers = {}

# نگه‌داری امضاها (یک‌بار بارگذاری)
SIGS = []


# ---------- ذخیره‌سازی ----------
def load_db():
    if os.path.exists(config.DB_PATH):
        with open(config.DB_PATH, "r", encoding="utf-8") as f:
            return json.load(f)
    return {}


def save_db():
    with open(config.DB_PATH, "w", encoding="utf-8") as f:
        json.dump(pending, f, ensure_ascii=False, indent=2)


# ---------- حذف امضای مبدا ----------
def _is_signature_line(line: str) -> bool:
    s = line.strip()
    if not s:
        return False
    low = s.lower()
    for sig in config.SOURCE_SIGNATURES:
        if sig.lower() in low:
            return True
    if config.STRIP_LEADING_TRAILING_LINKS:
        if "t.me/" in low or re.search(r"@[a-zA-Z][a-zA-Z0-9_]{3,}", s):
            if len(s) < 60:
                return True
    return False


def clean_body(text: str) -> str:
    """فقط امضای مبدا را حذف می‌کند و بدنه‌ی خالص را برمی‌گرداند (بدون افزودن امضای من)."""
    if not text:
        return ""
    lines = text.split("\n")
    while lines and _is_signature_line(lines[0]):
        lines.pop(0)
    while lines and _is_signature_line(lines[-1]):
        lines.pop()
    body = "\n".join(lines).strip()
    for sig in config.SOURCE_SIGNATURES:
        if sig.strip():
            body = re.sub(re.escape(sig), "", body, flags=re.IGNORECASE)
    return re.sub(r"\n{3,}", "\n\n", body).strip()


# ---------- بازنویسی با Claude ----------
async def rewrite_text(text: str) -> str:
    if not config.ENABLE_AI_REWRITE or not text.strip():
        return text
    try:
        import anthropic
        client = anthropic.Anthropic(api_key=config.ANTHROPIC_API_KEY)
        msg = await asyncio.to_thread(
            client.messages.create,
            model=config.AI_MODEL,
            max_tokens=1500,
            messages=[{"role": "user", "content": config.REWRITE_PROMPT.format(text=text)}],
        )
        return msg.content[0].text.strip()
    except Exception as e:
        print(f"[هشدار] بازنویسی ناموفق: {e}")
        return text


# ---------- پیش‌نمایش ----------
def build_preview(item: dict) -> str:
    parts = ["📥 *پست جدید برای تایید:*"]
    n_media = len(item["media_paths"])
    if n_media:
        parts.append(f"🖼 شامل {n_media} رسانه (شماره‌گذاری‌شده)")
    parts.append("\n— — — نسخه خام — — —")
    parts.append(item["body"] if item["body"].strip() else "(بدون متن)")
    if item["rewritten_body"] is not None:
        parts.append("\n— — — نسخه بازنویسی‌شده — — —")
        parts.append(item["rewritten_body"] if item["rewritten_body"].strip() else "(بدون متن)")
    return "\n".join(parts)


def build_buttons(pid: str, item: dict):
    rows = []
    pub = [Button.inline("✅ انتشار خام", data=f"raw:{pid}")]
    if item["rewritten_body"] is not None:
        pub.append(Button.inline("✨ انتشار بازنویسی‌شده", data=f"rew:{pid}"))
    rows.append(pub)
    rows.append([Button.inline("✏️ ویرایش متن", data=f"edit:{pid}")])
    # دکمه حذف برای هر رسانه
    if len(item["media_paths"]) > 1:
        del_row = []
        for i in range(len(item["media_paths"])):
            del_row.append(Button.inline(f"🗑 حذف {i+1}", data=f"del:{pid}:{i}"))
            if len(del_row) == 3:
                rows.append(del_row)
                del_row = []
        if del_row:
            rows.append(del_row)
    rows.append([Button.inline("❌ رد", data=f"no:{pid}")])
    return rows


async def send_preview(pid: str):
    """پیش‌نمایش پست را (با رسانه در صورت وجود) برای ادمین می‌فرستد."""
    item = pending[pid]
    preview = build_preview(item)
    buttons = build_buttons(pid, item)
    media = item["media_paths"]
    try:
        if media:
            # آلبوم را به‌صورت گروهی بفرست، سپس پیام کنترل با دکمه‌ها
            await bot_client.send_file(config.ADMIN_ID, media)
            await bot_client.send_message(
                config.ADMIN_ID, preview[:3500], buttons=buttons, link_preview=False
            )
        else:
            await bot_client.send_message(
                config.ADMIN_ID, preview[:3500], buttons=buttons, link_preview=False
            )
    except Exception as e:
        print(f"[خطا] ارسال پیش‌نمایش: {e}")


# ---------- پردازش یک پست (تک یا آلبوم) ----------
async def process_post(messages: list, channel_cfg: dict):
    """messages: لیست پیام‌های یک پست (آلبوم) یا یک پیام تکی در لیست."""
    # متن از اولین پیامی که متن دارد
    text = ""
    for m in messages:
        if m.message:
            text = m.message
            break

    # فیلتر عمومی + سیاست کانال (روی متن خام اعمال می‌شود)
    if filters.general_skip(text):
        return
    if not filters.passes_channel_policy(channel_cfg, text):
        return

    print(f"[پست تاییدشده‌ی فیلتر] از {channel_cfg['channel']} — {len(messages)} پیام")

    # دانلود رسانه‌ها
    media_paths = []
    os.makedirs("data/media", exist_ok=True)
    for m in messages:
        if m.media:
            p = await m.download_media(file="data/media/")
            if p:
                media_paths.append(p)

    body = clean_body(text)

    rewritten_body = None
    if config.ENABLE_AI_REWRITE and body.strip():
        rewritten_body = await rewrite_text(body)

    pid = uuid.uuid4().hex[:10]
    pending[pid] = {
        "body": body,
        "rewritten_body": rewritten_body,
        "media_paths": media_paths,
    }
    save_db()
    await send_preview(pid)


# ---------- هندلر پیام مبدا (با تجمیع آلبوم) ----------
def _find_channel_cfg(event):
    """تطبیق پیام با تنظیمات کانال مبدا."""
    for c in filters.get_channel_configs():
        try:
            ent = event.chat
            uname = getattr(ent, "username", None)
            cid = event.chat_id
            target = c["channel"]
            if isinstance(target, str) and uname and target.lstrip("@").lower() == uname.lower():
                return c
            if str(target) == str(cid):
                return c
        except Exception:
            continue
    # اگر تطبیق دقیق نشد، اولی را برنگردان؛ None یعنی نامشخص
    return None


async def _flush_album(grouped_id):
    buf = album_buffers.pop(grouped_id, None)
    if not buf:
        return
    messages = buf["messages"]
    await process_post(messages, buf["channel_cfg"])


@user_client.on(events.NewMessage(chats=filters.get_source_identifiers()))
async def on_source_message(event):
    channel_cfg = _find_channel_cfg(event)
    if channel_cfg is None:
        channel_cfg = {"channel": str(event.chat_id), "policy": "all", "keywords": []}

    gid = event.message.grouped_id
    if gid:
        # عضو یک آلبوم — در بافر جمع کن و debounce کن
        buf = album_buffers.get(gid)
        if buf is None:
            buf = {"messages": [], "channel_cfg": channel_cfg, "task": None}
            album_buffers[gid] = buf
        buf["messages"].append(event.message)
        if buf["task"]:
            buf["task"].cancel()

        async def _delayed(g=gid):
            try:
                await asyncio.sleep(config.ALBUM_DEBOUNCE_SECONDS)
                await _flush_album(g)
            except asyncio.CancelledError:
                pass

        buf["task"] = asyncio.create_task(_delayed())
    else:
        await process_post([event.message], channel_cfg)


# ---------- انتشار نهایی ----------
async def finalize_and_publish(pid: str, which: str, when: datetime | None = None):
    """امضا را انتخاب، به متن می‌چسباند و منتشر/زمان‌بندی می‌کند."""
    item = pending.get(pid)
    if not item:
        return "این مورد دیگر در صف نیست."

    body = item["rewritten_body"] if (which == "rew" and item["rewritten_body"] is not None) else item["body"]

    # انتخاب امضای مرتبط با AI (بعد از تایید)
    sig = await sigmod.pick_signature(body, SIGS)
    final_text = (body + "\n\n" + sig).strip() if sig else body

    media = item["media_paths"]
    kwargs = {}
    if when is not None:
        kwargs["schedule"] = when

    try:
        if media:
            await bot_client.send_file(
                config.TARGET_CHANNEL, media, caption=final_text[:1000], **kwargs
            )
        else:
            await bot_client.send_message(
                config.TARGET_CHANNEL, final_text, link_preview=False, **kwargs
            )
    except Exception as e:
        return f"⚠️ خطا در انتشار: {e}"
    finally:
        pending.pop(pid, None)
        save_db()

    if when is not None:
        local = when + timedelta(hours=config.TIMEZONE_OFFSET_HOURS)
        return f"⏰ برای {local.strftime('%Y-%m-%d %H:%M')} (وقت محلی) زمان‌بندی شد."
    return "✅ منتشر شد."


# ---------- هندلر دکمه‌ها ----------
@bot_client.on(events.CallbackQuery)
async def on_callback(event):
    if event.sender_id != config.ADMIN_ID:
        await event.answer("اجازه نداری.", alert=True)
        return

    data = event.data.decode()
    parts = data.split(":")
    action = parts[0]
    pid = parts[1] if len(parts) > 1 else None

    item = pending.get(pid)
    if not item and action not in ("schednow", "schedlater"):
        await event.answer("این مورد دیگر در صف نیست.", alert=True)
        return

    # رد
    if action == "no":
        pending.pop(pid, None)
        save_db()
        await event.edit("❌ رد شد.")
        return

    # ویرایش متن
    if action == "edit":
        awaiting_edit[event.sender_id] = pid
        await event.respond("✏️ متن جدید را بفرست تا جایگزین شود (هم خام و هم نسخه نهایی).")
        await event.answer()
        return

    # حذف یک رسانه از آلبوم
    if action == "del":
        idx = int(parts[2])
        if 0 <= idx < len(item["media_paths"]):
            removed = item["media_paths"].pop(idx)
            save_db()
            await event.answer(f"رسانه {idx+1} حذف شد.")
            await event.respond(f"🗑 رسانه {idx+1} حذف شد. باقی‌مانده: {len(item['media_paths'])}")
            await send_preview(pid)
        else:
            await event.answer("شماره نامعتبر.", alert=True)
        return

    # انتشار خام/بازنویسی → پرسش زمان
    if action in ("raw", "rew"):
        buttons = [[
            Button.inline("📤 انتشار فوری", data=f"pubnow:{pid}:{action}"),
            Button.inline("⏰ زمان‌بندی", data=f"sched:{pid}:{action}"),
        ]]
        await event.edit("کی منتشر شود؟", buttons=buttons)
        return

    # انتشار فوری
    if action == "pubnow":
        which = parts[2]
        result = await finalize_and_publish(pid, which, when=None)
        await event.edit(result)
        return

    # زمان‌بندی — نمایش گزینه‌های آماده
    if action == "sched":
        which = parts[2]
        buttons = [
            [Button.inline("۱ ساعت بعد", data=f"do_sched:{pid}:{which}:60"),
             Button.inline("۳ ساعت بعد", data=f"do_sched:{pid}:{which}:180")],
            [Button.inline("فردا همین ساعت", data=f"do_sched:{pid}:{which}:1440"),
             Button.inline("✍️ زمان دلخواه", data=f"customtime:{pid}:{which}")],
        ]
        await event.edit("چه زمانی منتشر شود؟", buttons=buttons)
        return

    # زمان‌بندی آماده
    if action == "do_sched":
        which = parts[2]
        minutes = int(parts[3])
        when = datetime.now(timezone.utc) + timedelta(minutes=minutes)
        result = await finalize_and_publish(pid, which, when=when)
        await event.edit(result)
        return

    # زمان دلخواه — منتظر ورودی کاربر
    if action == "customtime":
        which = parts[2]
        awaiting_schedule[event.sender_id] = (pid, which)
        await event.respond("زمان را به این فرمت بفرست:\n`YYYY-MM-DD HH:MM`\nمثال: 2026-07-01 14:30")
        await event.answer()
        return


# ---------- هندلر پیام‌های ادمین (ویرایش متن / زمان دلخواه) ----------
@bot_client.on(events.NewMessage())
async def on_admin_message(event):
    uid = event.sender_id
    if uid != config.ADMIN_ID:
        return
    # فقط در حالت ویرایش یا زمان‌بندی واکنش نشان بده (تا پیام‌های دیگر را نادیده بگیرد)
    if uid not in awaiting_edit and uid not in awaiting_schedule:
        return

    # حالت ویرایش متن
    if uid in awaiting_edit:
        pid = awaiting_edit.pop(uid)
        item = pending.get(pid)
        if not item:
            await event.respond("این مورد دیگر در صف نیست.")
            return
        new_text = event.message.message or ""
        item["body"] = new_text
        item["rewritten_body"] = new_text  # متن دستی برای هر دو نسخه یکی می‌شود
        save_db()
        await event.respond("✅ متن جایگزین شد. پیش‌نمایش جدید:")
        await send_preview(pid)
        return

    # حالت زمان دلخواه
    if uid in awaiting_schedule:
        pid, which = awaiting_schedule.pop(uid)
        raw = (event.message.message or "").strip()
        try:
            local_dt = datetime.strptime(raw, "%Y-%m-%d %H:%M")
            offset = timedelta(hours=config.TIMEZONE_OFFSET_HOURS)
            dt = (local_dt - offset).replace(tzinfo=timezone.utc)
        except ValueError:
            await event.respond("فرمت اشتباه بود. دوباره با فرمت `YYYY-MM-DD HH:MM` بفرست.")
            awaiting_schedule[uid] = (pid, which)
            return
        result = await finalize_and_publish(pid, which, when=dt)
        await event.respond(result)
        return


async def main():
    global pending, SIGS
    pending = load_db()
    SIGS = sigmod.load_signatures()
    print("در حال اتصال...")
    await bot_client.start(bot_token=config.BOT_TOKEN)
    await user_client.start()
    print("✅ آماده. در حال مانیتور کانال‌های مبدا...")
    print(f"   کانال‌های مبدا: {len(filters.get_channel_configs())}")
    print(f"   امضاهای بارگذاری‌شده: {len(SIGS)}")
    print(f"   کانال مقصد: {config.TARGET_CHANNEL}")
    # هر دو کلاینت روی همان event loop تا قطع اتصال اجرا می‌مانند
    await user_client.run_until_disconnected()


if __name__ == "__main__":
    # از loop خود کلاینت استفاده می‌کنیم تا باگ «attached to a different loop»
    # در Telethon (هنگام استارت دو کلاینت) رخ ندهد.
    with bot_client:
        bot_client.loop.run_until_complete(main())