import traceback
import telebot
from telebot import apihelper
from telebot.types import InlineKeyboardMarkup, InlineKeyboardButton, MessageEntity, Message, CallbackQuery

from beancount_bot import transaction
from beancount_bot.config import get_config, load_config
from beancount_bot.dispatcher import Dispatcher
from beancount_bot.i18n import _
from beancount_bot.session import get_session, SESS_AUTH, get_session_for, set_session
from beancount_bot.task import load_task, get_task
from beancount_bot.transaction import get_manager
from beancount_bot.util import logger

apihelper.ENABLE_MIDDLEWARE = True

bot = telebot.TeleBot(token=None, parse_mode=None)


@bot.middleware_handler(update_types=['message'])
def session_middleware(bot_instance, message):
    """
    Session middleware
    :param bot_instance:
    :param message:
    :return:
    """
    bot_instance.session = get_session_for(message.from_user.id)


#######
# Authentication #
#######

def check_auth() -> bool:
    """
    Check if you log in
    :return:
    """
    return SESS_AUTH in bot.session and bot.session[SESS_AUTH]


@bot.message_handler(commands=['start'])
def start_handler(message: Message):
    """
    First chat time authentication
    :param message:
    :return:
    """
    auth = get_session(message.from_user.id, SESS_AUTH, False)
    if auth:
        bot.reply_to(message, _("Have been authenticated！"))
        return
    # 要求鉴权
    bot.reply_to(message, _("Welcome to the accounting robot!Please enter the authentication token:"))


def auth_token_handler(message: Message):
    """
    Login token callback
    :param message:
    :return:
    """
    if check_auth():
        return
    # Unconfirmation is considered an authentication token
    auth_token = get_config('bot.auth_token')
    if auth_token == message.text:
        set_session(message.from_user.id, SESS_AUTH, True)
        bot.reply_to(message, _("Authentic success！"))
    else:
        bot.reply_to(message, _("Authentication token error！"))


#######
# instruction #
#######


@bot.message_handler(commands=['reload'])
def reload_handler(message):
    """
    Overload configuration instruction
    :param message:
    :return:
    """
    if not check_auth():
        bot.reply_to(message, _("Please conduct authentication first！"))
        return
    load_config()
    load_task()
    bot.reply_to(message, _("Successful overload configuration！"))


@bot.message_handler(commands=['help'])
def help_handler(message):
    """
    Help instruction
    :param message:
    :return:
    """
    cmd = message.text
    dispatchers = get_manager().dispatchers
    if cmd == '/help':
        # Create a message button
        markup = InlineKeyboardMarkup()
        for ind, d in zip(range(len(dispatchers)), dispatchers):
            help_btn = _("help：{name}").format(name=d.get_name())
            markup.add(InlineKeyboardButton(help_btn, callback_data=f'help:{ind}'))
        # 帮助信息
        command_usage = [
            _("/start - Authentication"),
            _("/help - Using help"),
            _("/reload - Reload the configuration file"),
            _("/task - View, run the task"),
        ]
        help_text = \
            _("Account bill Bot\n\nAvailable instruction list：\n{command}\n\nTrade statement syntax help, select the corresponding module，Use /help [Module name] Check.").format(
                command='\n'.join(command_usage))
        bot.reply_to(message, help_text, reply_markup=markup)
    else:
        # Display detailed help
        name: str = cmd[6:]
        flag_found = False
        for d in dispatchers:
            if name.lower() == d.get_name().lower():
                show_usage_for(message, d)
                flag_found = True
        if not flag_found:
            bot.reply_to(message, _("The corresponding name of the transaction statement processor does not exist！"))


def show_usage_for(message: Message, d: Dispatcher):
    """
    Show the method of use of a specific processor
    :param message:
    :param d:
    :return:
    """
    usage = _("help：{name}\n\n{usage}").format(name=d.get_name(), usage=d.get_usage())
    bot.reply_to(message, usage)


@bot.callback_query_handler(func=lambda call: call.data[:4] == 'help')
def callback_help(call: CallbackQuery):
    """
    Help statement detailed help
    :param call:
    :return:
    """
    try:
        d_id = int(call.data[5:])
        dispatchers = get_manager().dispatchers
        show_usage_for(call.message, dispatchers[d_id])
    except Exception as e:
        logger.error(f'{call.id}：Unknown error！', e)
        logger.error(traceback.format_exc())
        bot.answer_callback_query(call.id, _("Unknown error！\n"+traceback.format_exc()))


@bot.message_handler(commands=['task'])
def task_handler(message):
    """
    Task instruction
    :param message:
    :return:
    """
    if not check_auth():
        bot.reply_to(message, _("Please conduct authentication first!"))
        return

    cmd = message.text
    tasks = get_task()
    if cmd == '/task':
        # Show all tasks
        all_tasks = ', '.join(tasks.keys())
        bot.reply_to(message,
                     _("Current registration task：{all_tasks}\n"
                       "able to pass /task [Task Name] Active trigger").format(all_tasks=all_tasks))
    else:
        # Run task
        dest = cmd[6:]
        if dest not in tasks:
            bot.reply_to(message, _("Task does not exist！"))
            return
        task = tasks[dest]
        task.trigger(bot)


#######
# trade #
#######


@bot.message_handler(func=lambda m: True)
def transaction_query_handler(message: Message):
    """
    Trading statement processing
    :param message:
    :return:
    """
    if not check_auth():
        auth_token_handler(message)
        return
    # Treated
    manager = get_manager()
    try:
        tx_uuid, tx = manager.create_from_str(message.text)
        # Create a message button
        markup = InlineKeyboardMarkup()
        markup.add(InlineKeyboardButton(_("Revoke trading"), callback_data=f'withdraw:{tx_uuid}'))
        # 回复
        bot.reply_to(message, transaction.stringfy(tx), reply_markup=markup)
    except ValueError as e:
        logger.info(f'{message.from_user.id}：Unable to add transactions', e)
        bot.reply_to(message, e.args[0])
    except Exception as e:
        logger.error(f'{message.from_user.id}：An unknown mistake!Adding a transaction failed.', e)
        bot.reply_to(message, _("An unknown mistake!Adding a transaction failed.\n"+traceback.format_exc()))


@bot.callback_query_handler(func=lambda call: call.data[:8] == 'withdraw')
def callback_withdraw(call: CallbackQuery):
    """
    Transaction withdrawal callback
    :param call:
    :return:
    """
    auth = get_session(call.from_user.id, SESS_AUTH, False)
    if not auth:
        bot.answer_callback_query(call.id, _("Please conduct authentication first！"))
        return
    tx_uuid = call.data[9:]
    manager = get_manager()
    try:
        manager.remove(tx_uuid)
        # Modify the original message reply
        message = _("Transaction has been withdrawn")
        code_format = MessageEntity('code', 0, len(message))
        bot.edit_message_text(message,
                              chat_id=call.message.chat.id,
                              message_id=call.message.message_id,
                              entities=[code_format])
    except ValueError as e:
        logger.info(f'{call.id}：Unable to create trading', e)
        bot.answer_callback_query(call.id, e.args[0])
    except Exception as e:
        logger.error(f'{call.id}：An unknown mistake!Withdrawal of the transaction failed.', e)
        bot.answer_callback_query(call.id, _("An unknown mistake!Withdrawal of the transaction failed."))


def serving():
    """
    start up Bot
    :return:
    """

    # set up Token
    token = get_config('bot.token')
    bot.token = token
    # Set a proxy
    proxy = get_config('bot.proxy')
    if proxy is not None:
        apihelper.proxy = {'https': proxy}
    # start up
    bot.infinity_polling()

[((17, 6, 17, 50), 'telebot.TeleBot', 'telebot.TeleBot', (), '', False, 'import telebot\n'), ((28, 27, 28, 64), 'beancount_bot.session.get_session_for', 'get_session_for', ({(28, 43, 28, 63): 'message.from_user.id'}, {}), '(message.from_user.id)', False, 'from beancount_bot.session import get_session, SESS_AUTH, get_session_for, set_session\n'), ((50, 11, 50, 62), 'beancount_bot.session.get_session', 'get_session', ({(50, 23, 50, 43): 'message.from_user.id', (50, 45, 50, 54): 'SESS_AUTH', (50, 56, 50, 61): 'False'}, {}), '(message.from_user.id, SESS_AUTH, False)', False, 'from beancount_bot.session import get_session, SESS_AUTH, get_session_for, set_session\n'), ((67, 17, 67, 45), 'beancount_bot.config.get_config', 'get_config', ({(67, 28, 67, 44): '"""bot.auth_token"""'}, {}), "('bot.auth_token')", False, 'from beancount_bot.config import get_config, load_config\n'), ((90, 4, 90, 17), 'beancount_bot.config.load_config', 'load_config', ({}, {}), '()', False, 'from beancount_bot.config import get_config, load_config\n'), ((91, 4, 91, 15), 'beancount_bot.task.load_task', 'load_task', ({}, {}), '()', False, 'from beancount_bot.task import load_task, get_task\n'), ((173, 12, 173, 22), 'beancount_bot.task.get_task', 'get_task', ({}, {}), '()', False, 'from beancount_bot.task import load_task, get_task\n'), ((206, 14, 206, 27), 'beancount_bot.transaction.get_manager', 'get_manager', ({}, {}), '()', False, 'from beancount_bot.transaction import get_manager\n'), ((229, 11, 229, 59), 'beancount_bot.session.get_session', 'get_session', ({(229, 23, 229, 40): 'call.from_user.id', (229, 42, 229, 51): 'SESS_AUTH', (229, 53, 229, 58): 'False'}, {}), '(call.from_user.id, SESS_AUTH, False)', False, 'from beancount_bot.session import get_session, SESS_AUTH, get_session_for, set_session\n'), ((234, 14, 234, 27), 'beancount_bot.transaction.get_manager', 'get_manager', ({}, {}), '()', False, 'from beancount_bot.transaction import get_manager\n'), ((259, 12, 259, 35), 'beancount_bot.config.get_config', 'get_config', ({(259, 23, 259, 34): '"""bot.token"""'}, {}), "('bot.token')", False, 'from beancount_bot.config import get_config, load_config\n'), ((262, 12, 262, 35), 'beancount_bot.config.get_config', 'get_config', ({(262, 23, 262, 34): '"""bot.proxy"""'}, {}), "('bot.proxy')", False, 'from beancount_bot.config import get_config, load_config\n'), ((55, 26, 55, 101), 'beancount_bot.i18n._', '_', ({(55, 28, 55, 100): '"""Welcome to the accounting robot!Please enter the authentication token:"""'}, {}), "('Welcome to the accounting robot!Please enter the authentication token:')", False, 'from beancount_bot.i18n import _\n'), ((69, 8, 69, 58), 'beancount_bot.session.set_session', 'set_session', ({(69, 20, 69, 40): 'message.from_user.id', (69, 42, 69, 51): 'SESS_AUTH', (69, 53, 69, 57): '(True)'}, {}), '(message.from_user.id, SESS_AUTH, True)', False, 'from beancount_bot.session import get_session, SESS_AUTH, get_session_for, set_session\n'), ((92, 26, 92, 67), 'beancount_bot.i18n._', '_', ({(92, 28, 92, 66): '"""Successful overload configuration！"""'}, {}), "('Successful overload configuration！')", False, 'from beancount_bot.i18n import _\n'), ((103, 18, 103, 31), 'beancount_bot.transaction.get_manager', 'get_manager', ({}, {}), '()', False, 'from beancount_bot.transaction import get_manager\n'), ((106, 17, 106, 39), 'telebot.types.InlineKeyboardMarkup', 'InlineKeyboardMarkup', ({}, {}), '()', False, 'from telebot.types import InlineKeyboardMarkup, InlineKeyboardButton, MessageEntity, Message, CallbackQuery\n'), ((210, 17, 210, 39), 'telebot.types.InlineKeyboardMarkup', 'InlineKeyboardMarkup', ({}, {}), '()', False, 'from telebot.types import InlineKeyboardMarkup, InlineKeyboardButton, MessageEntity, Message, CallbackQuery\n'), ((238, 18, 238, 53), 'beancount_bot.i18n._', '_', ({(238, 20, 238, 52): '"""Transaction has been withdrawn"""'}, {}), "('Transaction has been withdrawn')", False, 'from beancount_bot.i18n import _\n'), ((52, 30, 52, 61), 'beancount_bot.i18n._', '_', ({(52, 32, 52, 60): '"""Have been authenticated！"""'}, {}), "('Have been authenticated！')", False, 'from beancount_bot.i18n import _\n'), ((70, 30, 70, 55), 'beancount_bot.i18n._', '_', ({(70, 32, 70, 54): '"""Authentic success！"""'}, {}), "('Authentic success！')", False, 'from beancount_bot.i18n import _\n'), ((72, 30, 72, 64), 'beancount_bot.i18n._', '_', ({(72, 32, 72, 63): '"""Authentication token error！"""'}, {}), "('Authentication token error！')", False, 'from beancount_bot.i18n import _\n'), ((88, 30, 88, 73), 'beancount_bot.i18n._', '_', ({(88, 32, 88, 72): '"""Please conduct authentication first！"""'}, {}), "('Please conduct authentication first！')", False, 'from beancount_bot.i18n import _\n'), ((112, 12, 112, 40), 'beancount_bot.i18n._', '_', ({(112, 14, 112, 39): '"""/start - Authentication"""'}, {}), "('/start - Authentication')", False, 'from beancount_bot.i18n import _\n'), ((113, 12, 113, 35), 'beancount_bot.i18n._', '_', ({(113, 14, 113, 34): '"""/help - Using help"""'}, {}), "('/help - Using help')", False, 'from beancount_bot.i18n import _\n'), ((114, 12, 114, 56), 'beancount_bot.i18n._', '_', ({(114, 14, 114, 55): '"""/reload - Reload the configuration file"""'}, {}), "('/reload - Reload the configuration file')", False, 'from beancount_bot.i18n import _\n'), ((115, 12, 115, 43), 'beancount_bot.i18n._', '_', ({(115, 14, 115, 42): '"""/task - View, run the task"""'}, {}), "('/task - View, run the task')", False, 'from beancount_bot.i18n import _\n'), ((140, 12, 140, 41), 'beancount_bot.i18n._', '_', ({(140, 14, 140, 40): '"""help：{name}\n\n{usage}"""'}, {}), '("""help：{name}\n\n{usage}""")', False, 'from beancount_bot.i18n import _\n'), ((153, 22, 153, 35), 'beancount_bot.transaction.get_manager', 'get_manager', ({}, {}), '()', False, 'from beancount_bot.transaction import get_manager\n'), ((156, 8, 156, 56), 'beancount_bot.util.logger.error', 'logger.error', ({(156, 21, 156, 52): 'f"""{call.id}：Unknown error！"""', (156, 54, 156, 55): 'e'}, {}), "(f'{call.id}：Unknown error！', e)", False, 'from beancount_bot.util import logger\n'), ((169, 30, 169, 71), 'beancount_bot.i18n._', '_', ({(169, 32, 169, 70): '"""Please conduct authentication first!"""'}, {}), "('Please conduct authentication first!')", False, 'from beancount_bot.i18n import _\n'), ((213, 30, 213, 54), 'beancount_bot.transaction.stringfy', 'transaction.stringfy', ({(213, 51, 213, 53): 'tx'}, {}), '(tx)', False, 'from beancount_bot import transaction\n'), ((215, 8, 215, 78), 'beancount_bot.util.logger.info', 'logger.info', ({(215, 20, 215, 74): 'f"""{message.from_user.id}：Unable to add transactions"""', (215, 76, 215, 77): 'e'}, {}), "(f'{message.from_user.id}：Unable to add transactions', e)", False, 'from beancount_bot.util import logger\n'), ((218, 8, 218, 100), 'beancount_bot.util.logger.error', 'logger.error', ({(218, 21, 218, 96): 'f"""{message.from_user.id}：An unknown mistake!Adding a transaction failed."""', (218, 98, 218, 99): 'e'}, {}), "(\n    f'{message.from_user.id}：An unknown mistake!Adding a transaction failed.',\n    e)", False, 'from beancount_bot.util import logger\n'), ((231, 43, 231, 86), 'beancount_bot.i18n._', '_', ({(231, 45, 231, 85): '"""Please conduct authentication first！"""'}, {}), "('Please conduct authentication first！')", False, 'from beancount_bot.i18n import _\n'), ((245, 8, 245, 63), 'beancount_bot.util.logger.info', 'logger.info', ({(245, 20, 245, 59): 'f"""{call.id}：Unable to create trading"""', (245, 61, 245, 62): 'e'}, {}), "(f'{call.id}：Unable to create trading', e)", False, 'from beancount_bot.util import logger\n'), ((248, 8, 248, 96), 'beancount_bot.util.logger.error', 'logger.error', ({(248, 21, 248, 92): 'f"""{call.id}：An unknown mistake!Withdrawal of the transaction failed."""', (248, 94, 248, 95): 'e'}, {}), "(\n    f'{call.id}：An unknown mistake!Withdrawal of the transaction failed.', e)", False, 'from beancount_bot.util import logger\n'), ((109, 23, 109, 82), 'telebot.types.InlineKeyboardButton', 'InlineKeyboardButton', (), '', False, 'from telebot.types import InlineKeyboardMarkup, InlineKeyboardButton, MessageEntity, Message, CallbackQuery\n'), ((118, 12, 118, 174), 'beancount_bot.i18n._', '_', ({(118, 14, 118, 173): '"""Account bill Bot\n\nAvailable instruction list：\n{command}\n\nTrade statement syntax help, select the corresponding module，Use /help [Module name] Check."""'}, {}), '("""Account bill Bot\n\nAvailable instruction list：\n{command}\n\nTrade statement syntax help, select the corresponding module，Use /help [Module name] Check."""\n    )', False, 'from beancount_bot.i18n import _\n'), ((130, 34, 130, 118), 'beancount_bot.i18n._', '_', ({(130, 36, 130, 117): '"""The corresponding name of the transaction statement processor does not exist！"""'}, {}), "('The corresponding name of the transaction statement processor does not exist！'\n    )", False, 'from beancount_bot.i18n import _\n'), ((157, 21, 157, 43), 'traceback.format_exc', 'traceback.format_exc', ({}, {}), '()', False, 'import traceback\n'), ((184, 34, 184, 61), 'beancount_bot.i18n._', '_', ({(184, 36, 184, 60): '"""Task does not exist！"""'}, {}), "('Task does not exist！')", False, 'from beancount_bot.i18n import _\n'), ((211, 40, 211, 59), 'beancount_bot.i18n._', '_', ({(211, 42, 211, 58): '"""Revoke trading"""'}, {}), "('Revoke trading')", False, 'from beancount_bot.i18n import _\n'), ((249, 43, 249, 104), 'beancount_bot.i18n._', '_', ({(249, 45, 249, 103): '"""An unknown mistake!Withdrawal of the transaction failed."""'}, {}), "('An unknown mistake!Withdrawal of the transaction failed.')", False, 'from beancount_bot.i18n import _\n'), ((108, 23, 108, 41), 'beancount_bot.i18n._', '_', ({(108, 25, 108, 40): '"""help：{name}"""'}, {}), "('help：{name}')", False, 'from beancount_bot.i18n import _\n'), ((178, 21, 179, 71), 'beancount_bot.i18n._', '_', ({(178, 23, 179, 70): '"""Current registration task：{all_tasks}\nable to pass /task [Task Name] Active trigger"""'}, {}), '("""Current registration task：{all_tasks}\nable to pass /task [Task Name] Active trigger"""\n    )', False, 'from beancount_bot.i18n import _\n'), ((158, 66, 158, 88), 'traceback.format_exc', 'traceback.format_exc', ({}, {}), '()', False, 'import traceback\n'), ((219, 84, 219, 106), 'traceback.format_exc', 'traceback.format_exc', ({}, {}), '()', False, 'import traceback\n')]