#!/usr/bin/env python3
# tg_lists.py — list chats, topics, and members (personal account)
# Examples:
#   python tg_lists.py --session tg_session chats --find dq
#   python tg_lists.py --session tg_session topics --from -1002225920257 --find intake
#   python tg_lists.py --session tg_session members --from -1002225920257 --admins

import os, sys, argparse, asyncio, re
from typing import Optional
from urllib.parse import urlparse

# Windows-safe UTF-8 stdout/stderr for redirects (emoji, flags, etc.)
try:
    sys.stdout.reconfigure(encoding="utf-8", errors="replace")
    sys.stderr.reconfigure(encoding="utf-8", errors="replace")
except Exception:
    pass

try:
    from dotenv import load_dotenv
    load_dotenv()
except Exception:
    pass

from telethon import TelegramClient, functions, types
from telethon.errors import RPCError
from telethon.tl.types import User, Chat, Channel

def env(name, default=None, cast=str):
    v = os.getenv(name, default)
    if v is None: return None
    return cast(v) if (cast and v is not None) else v

def err(msg: str, code: int = 1):
    print(msg, file=sys.stderr)
    sys.exit(code)

def normalize_chat_ref(ref):
    """Allow -100… numeric strings, cast to int; pass @username as-is."""
    if isinstance(ref, str) and re.fullmatch(r"-?\d+", ref):
        try:
            return int(ref)  # critical for Telethon get_entity()
        except ValueError:
            return ref
    return ref

def parse_tme_link(s):
    """Support t.me/c/<internal_id>/<topic_id> → (-100<internal_id>, topic_id)."""
    try:
        u = urlparse(s)
        if u.netloc in {"t.me", "telegram.me"} and u.path.startswith("/c/"):
            parts = u.path.strip("/").split("/")
            if len(parts) >= 3 and parts[1].isdigit() and parts[2].isdigit():
                chat_id = int("-100" + parts[1])
                topic_id = int(parts[2])
                return chat_id, topic_id
    except Exception:
        pass
    return None, None

async def ensure_login(client: TelegramClient, phone: Optional[str]):
    if await client.is_user_authorized():
        return
    if not phone:
        err("First login requires PHONE_NUMBER in .env or pass --phone to the main command.", 2)
    await client.send_code_request(phone)
    code = input("Enter the login code you received in Telegram: ").strip()
    await client.sign_in(phone, code)

def chat_row(dlg):
    ent = dlg.entity
    if isinstance(ent, User):
        typ = "user"; forum = "-"
        uname = getattr(ent, "username", None)
    elif isinstance(ent, Chat):
        typ = "group"; forum = "no"
        uname = getattr(ent, "username", None)
    elif isinstance(ent, Channel):
        typ = "channel" if ent.broadcast else ("supergroup" if ent.megagroup else "channel")
        forum = "yes" if getattr(ent, "forum", False) else "no"
        uname = getattr(ent, "username", None)
    else:
        typ = "unknown"; forum = "-"
        uname = None
    return f"{dlg.id}\t{typ}\t{forum}\t{dlg.name or ''}\t{(uname or '')}"

async def do_chats(args, client: TelegramClient):
    print("id\ttype\tforum\tname\tusername")
    async for d in client.iter_dialogs():
        if args.find:
            q = args.find.lower()
            nm = (d.name or "").lower()
            un = (getattr(d.entity, "username", "") or "").lower()
            if q not in nm and q not in un:
                continue
        print(chat_row(d))

async def resolve_entity(client: TelegramClient, ref: str):
    # Accept t.me/c/.../... pasted as --from
    if isinstance(ref, str):
        chat_id, _topic_id = parse_tme_link(ref)
        if chat_id is not None:
            ref = chat_id
    ref = normalize_chat_ref(ref)
    try:
        return await client.get_entity(ref)
    except Exception as e:
        err(f"Could not resolve chat '{ref}': {e}")

async def do_topics(args, client: TelegramClient):
    ent = await resolve_entity(client, args.from_)
    if not isinstance(ent, Channel) or not ent.megagroup:
        err("The target is not a supergroup. Topics only exist for forum-enabled supergroups.")
    if not getattr(ent, "forum", False):
        err("This supergroup does not have forum/topics enabled.")

    print("topic_id\tis_pinned\tis_closed\ttitle\tunread\ttop_msg")
    offset_date = None
    offset_id = 0
    offset_topic = 0
    limit = 100
    while True:
        res = await client(functions.channels.GetForumTopicsRequest(
            channel=ent, q=None, offset_date=offset_date, offset_id=offset_id,
            offset_topic=offset_topic, limit=limit
        ))
        topics = res.topics or []
        if args.find:
            fq = args.find.lower()
            topics = [t for t in topics if fq in (t.title or "").lower()]
        for t in topics:
            print(f"{t.id}\t{('yes' if t.pinned else 'no')}\t{('yes' if t.closed else 'no')}\t{t.title}\tunread={getattr(t, 'unread_count', 0)}\ttop_msg={getattr(t, 'top_message', '-')}")

        if not topics or len(topics) < limit:
            break
        # Advance by last topic id; API pagination is topic-based here
        offset_topic = topics[-1].id

async def do_members(args, client: TelegramClient):
    ent = await resolve_entity(client, args.from_)
    if not isinstance(ent, (Channel, Chat)):
        err("Members can only be listed for groups/supergroups/channels.")
    print("user_id\tis_admin\tis_creator\tusername\tdisplay_name")
    if isinstance(ent, Channel):
        if args.admins:
            res = await client(functions.channels.GetParticipantsRequest(
                channel=ent,
                filter=types.ChannelParticipantsAdmins(),
                offset=0, limit=200, hash=0
            ))
            for u in res.users:
                uname = u.username or ""
                disp = f"{u.first_name or ''} {u.last_name or ''}".strip()
                is_creator = "yes" if getattr(u, "is_creator", False) else "no"
                print(f"{u.id}\tyes\t{is_creator}\t{uname}\t{disp}")
        else:
            offset = 0
            while True:
                res = await client(functions.channels.GetParticipantsRequest(
                    channel=ent,
                    filter=types.ChannelParticipantsSearch(""),
                    offset=offset, limit=200, hash=0
                ))
                if not res.users:
                    break
                for u in res.users:
                    uname = u.username or ""
                    disp = f"{u.first_name or ''} {u.last_name or ''}".strip()
                    print(f"{u.id}\tno\tno\t{uname}\t{disp}")
                offset += len(res.users)
    else:
        full = await client(functions.messages.GetFullChatRequest(chat_id=ent.id))
        for u in full.users:
            uname = u.username or ""
            disp = f"{u.first_name or ''} {u.last_name or ''}".strip()
            print(f"{u.id}\t-\t-\t{uname}\t{disp}")

def build_parser():
    p = argparse.ArgumentParser(description="List Telegram chats, topics and members.")
    sub = p.add_subparsers(dest="cmd", required=True)

    p_chats = sub.add_parser("chats", help="List your dialogs (DMs, groups, channels).")
    p_chats.add_argument("--find", help="Filter by substring (name/username), case-insensitive.")

    p_topics = sub.add_parser("topics", help="List forum topics in a forum-enabled supergroup.")
    p_topics.add_argument("--from", dest="from_", required=True, help="@username, numeric id (-100...), or t.me/c/.../....")
    p_topics.add_argument("--find", help="Filter topic titles by substring (case-insensitive).")

    p_mems = sub.add_parser("members", help="List members of a group/supergroup/channel.")
    p_mems.add_argument("--from", dest="from_", required=True, help="@username or numeric id (-100...).")
    p_mems.add_argument("--admins", action="store_true", help="Only list admins (supergroups/channels).")

    # global auth overrides (must appear BEFORE the subcommand on the CLI)
    p.add_argument("--session", default=env("SESSION","tg_session"))
    p.add_argument("--api-id", type=int, default=env("API_ID", None, int))
    p.add_argument("--api-hash", default=env("API_HASH"))
    p.add_argument("--phone", default=env("PHONE_NUMBER"))
    return p

def main():
    args = build_parser().parse_args()
    if not args.api_id or not args.api_hash:
        err("API_ID/API_HASH missing (.env or pass via --api-id/--api-hash).")

    client = TelegramClient(args.session, args.api_id, args.api_hash)

    async def run():
        await ensure_login(client, args.phone)
        if args.cmd == "chats":
            await do_chats(args, client)
        elif args.cmd == "topics":
            await do_topics(args, client)
        elif args.cmd == "members":
            await do_members(args, client)
        else:
            err("Unknown command", 2)

    with client:
        client.loop.run_until_complete(run())

if __name__ == "__main__":
    main()
