import functools

from telebot import types

from db import db, Sponsor, AdminPermission
from utils import t, get_lang, get_args, parse_duration, format_duration
from services import ALL_PERMISSIONS, LOCK_TYPES


def register_handlers(bot, services):

    def reply_text(message, text):
        try:
            bot.reply_to(message, text)
        except Exception:
            pass

    def get_target_user_id(message):
        if message.reply_to_message and message.reply_to_message.from_user:
            return message.reply_to_message.from_user.id

        args = get_args(message)
        if args:
            arg = args[0].lstrip("@")
            if arg.isdigit():
                return int(arg)

        return None

    def guard(permission=None, sponsor_required=True):
        def decorator(func):
            @functools.wraps(func)
            def wrapper(message):
                if not message.from_user:
                    return

                user_id = message.from_user.id
                chat_id = message.chat.id
                lang = get_lang(chat_id, message.from_user, services.settings)

                if sponsor_required and message.chat.type in ("group", "supergroup"):
                    if not services.sponsors.ensure(message, user_id, chat_id, lang):
                        return

                if permission:
                    if not services.permissions.has_permission(chat_id, user_id, permission):
                        reply_text(message, t("no_permission", lang))
                        return

                return func(message, lang)

            return wrapper

        return decorator

    @bot.message_handler(commands=["start"])
    def cmd_start(message):
        lang = get_lang(message.chat.id, message.from_user, services.settings)

        if message.chat.type == "private":
            bot.send_message(message.chat.id, t("start", lang) + "\n\n" + t("help", lang))
            return

        if not services.sponsors.ensure(message, message.from_user.id, message.chat.id, lang):
            return

        bot.send_message(message.chat.id, t("start", lang))

    @bot.message_handler(commands=["help"])
    @guard(sponsor_required=True)
    def cmd_help(message, lang):
        bot.send_message(message.chat.id, t("help", lang))

    @bot.message_handler(commands=["id"])
    @guard(sponsor_required=False)
    def cmd_id(message, lang):
        text = (
            f"Chat ID: {message.chat.id}\n"
            f"User ID: {message.from_user.id}\n"
        )
        if message.reply_to_message and message.reply_to_message.from_user:
            text += f"Replied User ID: {message.reply_to_message.from_user.id}\n"

        bot.send_message(message.chat.id, text)

    @bot.message_handler(commands=["panel"])
    def cmd_panel(message):
        lang = get_lang(message.chat.id, message.from_user, services.settings)
        role = services.permissions.get_role(message.chat.id, message.from_user.id)

        if message.chat.type in ("group", "supergroup"):
            if role == "user":
                if not services.sponsors.ensure(message, message.from_user.id, message.chat.id, lang):
                    return

        kb = keyboards.main_panel_keyboard(role)
        bot.send_message(message.chat.id, t("panel_title", lang), reply_markup=kb)

    @bot.message_handler(commands=["ban"])
    @guard(permission="ban")
    def cmd_ban(message, lang):
        target_id = get_target_user_id(message)
        if not target_id:
            reply_text(message, t("user_not_found", lang))
            return

        if not services.moderation.can_moderate(message.from_user.id, target_id, message.chat.id):
            reply_text(message, t("cannot_moderate", lang))
            return

        try:
            services.moderation.ban_user(message.chat.id, target_id)
            reply_text(message, t("banned", lang))
        except Exception:
            reply_text(message, t("error", lang))

    @bot.message_handler(commands=["unban"])
    @guard(permission="unban")
    def cmd_unban(message, lang):
        target_id = get_target_user_id(message)
        if not target_id:
            reply_text(message, t("user_not_found", lang))
            return

        try:
            services.moderation.unban_user(message.chat.id, target_id)
            reply_text(message, t("unbanned", lang))
        except Exception:
            reply_text(message, t("error", lang))

    @bot.message_handler(commands=["mute"])
    @guard(permission="mute")
    def cmd_mute(message, lang):
        args = get_args(message)
        target_id = None
        duration = 3600

        if message.reply_to_message and message.reply_to_message.from_user:
            target_id = message.reply_to_message.from_user.id
            if args:
                parsed = parse_duration(args[0])
                if parsed:
                    duration = parsed
        else:
            if args and args[0].isdigit():
                target_id = int(args[0])
                if len(args) > 1:
                    parsed = parse_duration(args[1])
                    if parsed:
                        duration = parsed

        if not target_id:
            reply_text(message, t("user_not_found", lang))
            return

        if not services.moderation.can_moderate(message.from_user.id, target_id, message.chat.id):
            reply_text(message, t("cannot_moderate", lang))
            return

        try:
            services.moderation.mute_user(message.chat.id, target_id, duration)
            reply_text(message, f"{t('muted', lang)} ⏱ {format_duration(duration)}")
        except Exception:
            reply_text(message, t("error", lang))

    @bot.message_handler(commands=["unmute"])
    @guard(permission="unmute")
    def cmd_unmute(message, lang):
        target_id = get_target_user_id(message)
        if not target_id:
            reply_text(message, t("user_not_found", lang))
            return

        try:
            services.moderation.unmute_user(message.chat.id, target_id)
            reply_text(message, t("unmuted", lang))
        except Exception:
            reply_text(message, t("error", lang))

    @bot.message_handler(commands=["warn"])
    @guard(permission="warn")
    def cmd_warn(message, lang):
        target_id = None
        reason = None
        args = get_args(message)

        if message.reply_to_message and message.reply_to_message.from_user:
            target_id = message.reply_to_message.from_user.id
            reason = " ".join(args) if args else None
        else:
            if args and args[0].isdigit():
                target_id = int(args[0])
                reason = " ".join(args[1:]) if len(args) > 1 else None

        if not target_id:
            reply_text(message, t("user_not_found", lang))
            return

        if not services.moderation.can_moderate(message.from_user.id, target_id, message.chat.id):
            reply_text(message, t("cannot_moderate", lang))
            return

        try:
            count, punished = services.moderation.warn(
                message.chat.id,
                target_id,
                message.from_user.id,
                reason,
                lang,
            )

            if punished:
                reply_text(message, t("max_warn_action", lang))
            else:
                reply_text(message, f"{t('warn_added', lang)}\n{t('warn_count', lang, count=count)}")
        except Exception:
            reply_text(message, t("error", lang))

    @bot.message_handler(commands=["warns"])
    @guard(permission="warn")
    def cmd_warns(message, lang):
        target_id = get_target_user_id(message)
        if not target_id:
            reply_text(message, t("user_not_found", lang))
            return

        count = services.moderation.count_warnings(message.chat.id, target_id)
        reply_text(message, t("warn_count", lang, count=count))

    @bot.message_handler(commands=["delwarns"])
    @guard(permission="warn")
    def cmd_delwarns(message, lang):
        target_id = get_target_user_id(message)
        if not target_id:
            reply_text(message, t("user_not_found", lang))
            return

        services.moderation.clear_warnings(message.chat.id, target_id)
        reply_text(message, t("warnings_cleared", lang))

    @bot.message_handler(commands=["close"])
    @guard(permission="close")
    def cmd_close(message, lang):
        try:
            services.moderation.close_group(message.chat.id)
            reply_text(message, t("group_closed", lang))
        except Exception:
            reply_text(message, t("error", lang))

    @bot.message_handler(commands=["open"])
    @guard(permission="open")
    def cmd_open(message, lang):
        try:
            services.moderation.open_group(message.chat.id)
            reply_text(message, t("group_opened", lang))
        except Exception:
            reply_text(message, t("error", lang))

    @bot.message_handler(commands=["lock"])
    @guard(permission="lock")
    def cmd_lock(message, lang):
        args = get_args(message)
        if not args or args[0].lower() not in LOCK_TYPES:
            reply_text(message, t("invalid_lock", lang))
            return

        lock_type = args[0].lower()
        key = f"lock_{lock_type}" if lock_type != "flood" and lock_type != "duplicate" else f"{lock_type}_check"

        if lock_type == "flood":
            key = "flood_check"
        elif lock_type == "duplicate":
            key = "duplicate_check"
        elif lock_type == "fosh":
            key = "lock_fosh"
        else:
            key = f"lock_{lock_type}"

        services.settings.set_bool(message.chat.id, key, True)
        reply_text(message, t("lock_enabled", lang, lock=lock_type))

    @bot.message_handler(commands=["unlock"])
    @guard(permission="unlock")
    def cmd_unlock(message, lang):
        args = get_args(message)
        if not args or args[0].lower() not in LOCK_TYPES:
            reply_text(message, t("invalid_lock", lang))
            return

        lock_type = args[0].lower()

        if lock_type == "flood":
            key = "flood_check"
        elif lock_type == "duplicate":
            key = "duplicate_check"
        elif lock_type == "fosh":
            key = "lock_fosh"
        else:
            key = f"lock_{lock_type}"

        services.settings.set_bool(message.chat.id, key, False)
        reply_text(message, t("lock_disabled", lang, lock=lock_type))

    @bot.message_handler(commands=["echo"])
    @guard(permission="echo")
    def cmd_echo(message, lang):
        args = get_args(message)
        text = " ".join(args) if args else None

        if not text and message.reply_to_message:
            if message.reply_to_message.text:
                text = message.reply_to_message.text
            elif message.reply_to_message.caption:
                text = message.reply_to_message.caption

        if not text:
            reply_text(message, t("echo_empty", lang))
            return

        try:
            bot.send_message(message.chat.id, text)
        except Exception:
            reply_text(message, t("error", lang))

    @bot.message_handler(commands=["settings"])
    @guard(permission="settings")
    def cmd_settings(message, lang):
        kb = keyboards.settings_keyboard(message.chat.id, services.settings, lang)
        bot.send_message(message.chat.id, t("settings", lang), reply_markup=kb)

    @bot.message_handler(commands=["setlang"])
    @guard(permission="settings")
    def cmd_setlang(message, lang):
        args = get_args(message)
        if not args or args[0].lower() not in ("fa", "en"):
            reply_text(message, "/setlang fa | en")
            return

        services.settings.set(message.chat.id, "lang", args[0].lower())
        reply_text(message, t("lang_changed", args[0].lower()))

    @bot.message_handler(commands=["grant"])
    @guard(permission="grant")
    def cmd_grant(message, lang):
        args = get_args(message)
        target_id = None
        permission = None

        if message.reply_to_message and message.reply_to_message.from_user and args:
            target_id = message.reply_to_message.from_user.id
            permission = args[0].lower()
        elif len(args) >= 2 and args[0].isdigit():
            target_id = int(args[0])
            permission = args[1].lower()

        if not target_id or not permission:
            reply_text(message, t("usage_grant", lang))
            return

        if permission not in ALL_PERMISSIONS:
            reply_text(message, f"Permissions:\n" + "\n".join(ALL_PERMISSIONS))
            return

        services.permissions.grant(message.chat.id, target_id, permission)
        reply_text(message, t("permission_added", lang, perm=permission))

    @bot.message_handler(commands=["revoke"])
    @guard(permission="grant")
    def cmd_revoke(message, lang):
        args = get_args(message)
        target_id = None
        permission = None

        if message.reply_to_message and message.reply_to_message.from_user and args:
            target_id = message.reply_to_message.from_user.id
            permission = args[0].lower()
        elif len(args) >= 2 and args[0].isdigit():
            target_id = int(args[0])
            permission = args[1].lower()

        if not target_id or not permission:
            reply_text(message, t("usage_grant", lang))
            return

        services.permissions.revoke(message.chat.id, target_id, permission)
        reply_text(message, t("permission_removed", lang, perm=permission))

    @bot.message_handler(commands=["perms"])
    @guard(permission="grant")
    def cmd_perms(message, lang):
        target_id = get_target_user_id(message)
        if not target_id:
            reply_text(message, t("user_not_found", lang))
            return

        permissions = services.permissions.list_permissions(message.chat.id, target_id)
        if not permissions:
            reply_text(message, f"{t('perm_list', lang)}\n{t('none', lang)}")
            return

        reply_text(message, f"{t('perm_list', lang)}\n" + "\n".join(permissions))

    @bot.message_handler(commands=["add_sponsor"])
    @guard(sponsor_required=False)
    def cmd_add_sponsor(message, lang):
        if not services.permissions.is_bot_owner(message.from_user.id):
            reply_text(message, t("bot_owner_only", lang))
            return

        args = get_args(message)
        if not args:
            reply_text(message, t("usage_sponsor", lang))
            return

        sponsor_target = args[0]
        link = args[1] if len(args) > 1 else None

        ok, msg_key = services.sponsors.add(sponsor_target, link, message.from_user.id)
        reply_text(message, t(msg_key, lang))

    @bot.message_handler(commands=["remove_sponsor", "del_sponsor"])
    @guard(sponsor_required=False)
    def cmd_remove_sponsor(message, lang):
        if not services.permissions.is_bot_owner(message.from_user.id):
            reply_text(message, t("bot_owner_only", lang))
            return

        args = get_args(message)
        if not args:
            reply_text(message, t("usage_sponsor", lang))
            return

        ok = services.sponsors.remove(args[0])
        reply_text(message, t("sponsor_removed" if ok else "invalid_sponsor", lang))

    @bot.message_handler(commands=["sponsors"])
    @guard(sponsor_required=False)
    def cmd_sponsors(message, lang):
        sponsors = services.sponsors.active_sponsors()

        if not sponsors:
            reply_text(message, t("none", lang))
            return

        lines = [t("sponsors_list", lang)]
        for sponsor in sponsors:
            lines.append(f"- {sponsor.title} | {sponsor.link}")

        bot.send_message(message.chat.id, "\n".join(lines))

    @bot.message_handler(content_types=[
        "text",
        "photo",
        "video",
        "audio",
        "voice",
        "video_note",
        "sticker",
        "document",
        "contact",
        "location",
        "poll",
        "animation",
    ])
    def auto_moderation(message):
        if message.chat.type not in ("group", "supergroup"):
            return

        if not message.from_user:
            return

        user_id = message.from_user.id
        chat_id = message.chat.id
        lang = get_lang(chat_id, message.from_user, services.settings)

        if services.permissions.is_bot_owner(user_id):
            return

        member = services.permissions.get_member(chat_id, user_id)
        if member and member.status in ("creator", "administrator"):
            return

        content = message.content_type
        text = message.text or message.caption or ""

        def delete_and_warn(reason_key: str = None):
            services.moderation.delete_message(chat_id, message.message_id)

            if reason_key:
                try:
                    bot.send_message(
                        chat_id,
                        f"⚠️ {message.from_user.first_name or user_id}: {reason_key}",
                    )
                except Exception:
                    pass

        # Lock all
        if services.settings.get_bool(chat_id, "lock_all", False):
            delete_and_warn()
            return

        # Media locks
        media_types = ["photo", "video", "audio", "voice", "video_note", "animation", "document"]
        if services.settings.get_bool(chat_id, "lock_media", False) and content in media_types:
            delete_and_warn("Media locked")
            return

        if services.settings.get_bool(chat_id, "lock_sticker", False) and content == "sticker":
            delete_and_warn("Sticker locked")
            return

        if services.settings.get_bool(chat_id, "lock_forward", False) and getattr(message, "forward_from", None):
            delete_and_warn("Forward locked")
            return

        # Text-based checks
        if text:
            if services.settings.get_bool(chat_id, "lock_links", False):
                entities = message.entities or message.caption_entities or []
                has_link_entity = any(e.type in ("url", "text_link") for e in entities)

                if has_link_entity or utils_contains_url(text):
                    delete_and_warn("Links are not allowed")
                    return

            if services.settings.get_bool(chat_id, "lock_username", False):
                if utils_contains_username(text):
                    delete_and_warn("Usernames are not allowed")
                    return

            if services.settings.get_bool(chat_id, "lock_fosh", False):
                if services.profanity.contains(text):
                    delete_and_warn("Profanity is not allowed")
                    services.moderation.warn(chat_id, user_id, None, "Profanity", lang)
                    return

        # Flood
        if services.spam.record_and_check_flood(chat_id, user_id):
            services.moderation.delete_message(chat_id, message.message_id)
            try:
                services.moderation.mute_user(chat_id, user_id, 300)
                bot.send_message(chat_id, "🚫 Flood detected. User muted for 5 minutes.")
            except Exception:
                pass
            return

        # Duplicate
        if text and services.spam.is_duplicate(chat_id, user_id, text):
            delete_and_warn("Duplicate message detected")
            services.moderation.warn(chat_id, user_id, None, "Duplicate", lang)
            return

    @bot.callback_query_handler(func=lambda call: True)
    def callback_handler(call):
        try:
            chat_id = call.message.chat.id
            user_id = call.from_user.id
            lang = get_lang(chat_id, call.from_user, services.settings)

            if call.data == "sponsor_check":
                missing = services.sponsors.missing_sponsors(user_id)

                if not missing:
                    bot.answer_callback_query(call.id, t("joined", lang), show_alert=False)
                else:
                    bot.answer_callback_query(call.id, t("not_joined", lang), show_alert=True)

                return

            if call.data.startswith("toggle:"):
                if not services.permissions.has_permission(chat_id, user_id, "settings"):
                    bot.answer_callback_query(call.id, t("no_permission", lang), show_alert=True)
                    return

                key = call.data.split(":", 1)[1]
                services.settings.toggle(chat_id, key, False)

                kb = keyboards.settings_keyboard(chat_id, services.settings, lang)
                bot.edit_message_reply_markup(
                    chat_id=chat_id,
                    message_id=call.message.message_id,
                    reply_markup=kb,
                )

                bot.answer_callback_query(call.id, t("done", lang), show_alert=False)
                return

            if call.data.startswith("panel:"):
                section = call.data.split(":", 1)[1]

                if section == "settings":
                    if not services.permissions.has_permission(chat_id, user_id, "settings"):
                        bot.answer_callback_query(call.id, t("no_permission", lang), show_alert=True)
                        return

                    kb = keyboards.settings_keyboard(chat_id, services.settings, lang)
                    bot.send_message(chat_id, t("settings", lang), reply_markup=kb)

                elif section == "permissions_help":
                    text = (
                        "برای دادن دسترسی:\n"
                        "/grant echo\n"
                        "یا:\n"
                        "/grant 123456 echo\n\n"
                        "برای گرفتن دسترسی:\n"
                        "/revoke echo\n\n"
                        "لیست دسترسی‌ها:\n" + "\n".join(ALL_PERMISSIONS)
                    )
                    bot.send_message(chat_id, text)

                elif section == "sponsors":
                    if not services.permissions.is_bot_owner(user_id):
                        bot.answer_callback_query(call.id, t("bot_owner_only", lang), show_alert=True)
                        return

                    sponsors = services.sponsors.active_sponsors()
                    if not sponsors:
                        bot.send_message(chat_id, t("none", lang))
                    else:
                        lines = [t("sponsors_list", lang)]
                        for sponsor in sponsors:
                            lines.append(f"- {sponsor.title} | {sponsor.link}")
                        bot.send_message(chat_id, "\n".join(lines))

                elif section == "help":
                    bot.send_message(chat_id, t("help", lang))

                bot.answer_callback_query(call.id, t("done", lang), show_alert=False)
                return

            bot.answer_callback_query(call.id, t("done", lang), show_alert=False)

        except Exception:
            try:
                bot.answer_callback_query(call.id, t("error", "en"), show_alert=True)
            except Exception:
                pass

    # Helpers for auto moderation to avoid importing utils in closure
    from utils import contains_url as utils_contains_url, contains_username as utils_contains_username