import functools
import gc
import time

import telebot
from sqlalchemy.future import Engine

from .. import cryptography, database

Message = telebot.types.Message


def _send_tmp_message(
    bot: telebot.TeleBot, chat_id: telebot.types.Message, text: str, timeout: int = 5
) -> None:
    bot_mes = bot.send_message(chat_id, text, parse_mode="MarkdownV2")
    time.sleep(timeout)
    bot.delete_message(chat_id, bot_mes.id)


def get_accounts(
    bot: telebot.TeleBot, engine: Engine, mes: telebot.types.Message
) -> None:
    accounts = database.get.get_accounts(engine, mes.from_user.id)
    bot.delete_message(mes.chat.id, mes.id)

    return _send_tmp_message(
        bot,
        mes.chat.id,
        "Ваши аккаунты:\n" + "\n".join(accounts) if accounts else "У вас нет аккаунтов",
        timeout=30,
    )


def _base(bot: telebot.TeleBot, mes: Message, prev_mes: Message | None = None) -> None:
    bot.delete_message(mes.chat.id, mes.id)
    if prev_mes is not None:
        bot.delete_message(prev_mes.chat.id, prev_mes.id)


def delete_all(
    bot: telebot.TeleBot, engine: Engine, mes: telebot.types.Message
) -> None:
    _base(bot, mes)
    bot_mes = bot.send_message(
        mes.chat.id,
        "Вы действительно хотите удалить все ваши аккаунты? Это действие нельзя отменить. Отправьте YES для подтверждения",
    )
    bot.register_next_step_handler(
        mes, functools.partial(_delete_all, bot, engine, bot_mes)
    )


def _delete_all(
    bot: telebot.TeleBot, engine: Engine, prev_mes: Message, mes: Message
) -> None:
    _base(bot, mes, prev_mes)
    text = mes.text.strip()
    if text == "YES":
        database.delete.purge_accounts(engine, mes.from_user.id)
        database.delete.delete_master_pass(engine, mes.from_user.id)
        _send_tmp_message(bot, mes.chat.id, "Всё успешно удалено", timeout=10)
    else:
        _send_tmp_message(bot, mes.chat.id, "Вы отправили не YES, ничего не удалено")


def set_master_password(bot: telebot.TeleBot, engine: Engine, mes: Message) -> None:
    _base(bot, mes, None)
    if database.get.get_master_pass(engine, mes.from_user.id) is not None:
        return _send_tmp_message(bot, mes.chat.id, "Мастер пароль уже существует")
    bot_mes = bot.send_message(mes.chat.id, "Отправьте мастер пароль")
    bot.register_next_step_handler(
        mes, functools.partial(_set_master_pass2, bot, engine, bot_mes)
    )


def _set_master_pass2(
    bot: telebot.TeleBot, engine: Engine, prev_mes: Message, mes: Message
) -> None:
    _base(bot, mes, prev_mes)
    text = mes.text.strip()
    if text == "/cancel":
        return _send_tmp_message(bot, mes.chat.id, "Успешная отмена")

    hash_, salt = cryptography.master_pass.encrypt_master_pass(text)
    database.add.add_master_pass(engine, mes.from_user.id, salt, hash_)
    _send_tmp_message(bot, mes.chat.id, "Успех")
    del mes, text
    gc.collect()


def reset_master_pass(bot: telebot.TeleBot, engine: Engine, mes: Message) -> None:
    _base(bot, mes)
    if database.get.get_master_pass(engine, mes.from_user.id) is None:
        return _send_tmp_message(bot, mes.chat.id, "Мастер пароль не задан")
    bot_mes = bot.send_message(
        mes.chat.id,
        "Отправьте новый мастер пароль, осторожно, все текущие аккаунты будут удалены навсегда",
    )
    bot.register_next_step_handler(
        mes, functools.partial(_reset_master_pass2, bot, engine, bot_mes)
    )


def _reset_master_pass2(
    bot: telebot.TeleBot, engine: Engine, prev_mes: Message, mes: Message
) -> None:
    _base(bot, mes, prev_mes)
    text = mes.text.strip()
    if text == "/cancel":
        return _send_tmp_message(bot, mes.chat.id, "Успешная отмена")

    hash_, salt = cryptography.master_pass.encrypt_master_pass(text)
    database.delete.purge_accounts(engine, mes.from_user.id)
    database.change.change_master_pass(engine, mes.from_user.id, salt, hash_)
    _send_tmp_message(
        bot, mes.chat.id, "Все ваши аккаунты удалены, а мастер пароль изменён"
    )
    del mes, text
    gc.collect()


def add_account(bot: telebot.TeleBot, engine: Engine, mes: Message) -> None:
    _base(bot, mes)

    master_password_from_db = database.get.get_master_pass(engine, mes.from_user.id)
    if master_password_from_db is None:
        return _send_tmp_message(bot, mes.chat.id, "Нет мастер пароля")

    bot_mes = bot.send_message(mes.chat.id, "Отправьте название аккаунта")

    bot.register_next_step_handler(
        mes, functools.partial(_add_account2, bot, engine, bot_mes)
    )


def _add_account2(
    bot: telebot.TeleBot, engine: Engine, prev_mes: Message, mes: Message
) -> None:
    _base(bot, mes, prev_mes)
    text = mes.text.strip()
    if text == "/cancel":
        return _send_tmp_message(bot, mes.chat.id, "Успешная отмена")

    if text in database.get.get_accounts(engine, mes.from_user.id):
        return _send_tmp_message(
            bot, mes.chat.id, "Аккаунт с таким именем уже существует"
        )

    bot_mes = bot.send_message(mes.chat.id, "Отправьте логин")
    data = {"name": text}

    bot.register_next_step_handler(
        mes, functools.partial(_add_account3, bot, engine, bot_mes, data)
    )


def _add_account3(
    bot: telebot.TeleBot,
    engine: Engine,
    prev_mes: Message,
    data: dict[str, str],
    mes: Message,
) -> None:
    _base(bot, mes, prev_mes)
    text = mes.text.strip()
    if text == "/cancel":
        return _send_tmp_message(bot, mes.chat.id, "Успешная отмена")

    data["login"] = text

    bot_mes = bot.send_message(mes.chat.id, "Отправьте пароль от аккаунта")

    bot.register_next_step_handler(
        mes, functools.partial(_add_account4, bot, engine, bot_mes, data)
    )


def _add_account4(
    bot: telebot.TeleBot,
    engine: Engine,
    prev_mes: Message,
    data: dict[str, str],
    mes: Message,
) -> None:
    _base(bot, mes, prev_mes)
    text = mes.text.strip()
    if text == "/cancel":
        return _send_tmp_message(bot, mes.chat.id, "Успешная отмена")

    data["passwd"] = text

    bot_mes = bot.send_message(mes.chat.id, "Отправьте мастер пароль")

    bot.register_next_step_handler(
        mes, functools.partial(_add_account5, bot, engine, bot_mes, data)
    )


def _add_account5(
    bot: telebot.TeleBot,
    engine: Engine,
    prev_mes: Message,
    data: dict[str, str],
    mes: Message,
) -> None:
    _base(bot, mes, prev_mes)
    text = mes.text.strip()
    if text == "/cancel":
        return _send_tmp_message(bot, mes.chat.id, "Успешная отмена")

    salt, hash_ = database.get.get_master_pass(engine, mes.from_user.id)
    if cryptography.master_pass.encrypt_master_pass(text, salt) != hash_:
        return _send_tmp_message(bot, mes.chat.id, "Не подходит главный пароль")

    name, login, passwd = data["name"], data["login"], data["passwd"]

    enc_login, enc_pass, salt = cryptography.other_accounts.encrypt_account_info(
        login, passwd, text.encode("utf-8")
    )

    database.add.add_account(engine, mes.from_user.id, name, salt, enc_login, enc_pass)

    _send_tmp_message(bot, mes.chat.id, "Успех")

    del data, name, login, passwd, enc_login

    gc.collect()


def get_account(bot: telebot.TeleBot, engine: Engine, mes: Message) -> None:
    _base(bot, mes)
    bot_mes = bot.send_message(mes.chat.id, "Отправьте название аккаунта")
    bot.register_next_step_handler(
        mes, functools.partial(_get_account2, bot, engine, bot_mes)
    )


def _get_account2(
    bot: telebot.TeleBot, engine: Engine, prev_mes: Message, mes: Message
) -> None:
    _base(bot, mes, prev_mes)
    text = mes.text.strip()
    if text == "/cancel":
        return _send_tmp_message(bot, mes.chat.id, "Успешная отмена")

    if text not in database.get.get_accounts(engine, mes.from_user.id):
        return _send_tmp_message(bot, mes.chat.id, "Нет такого аккаунта")

    bot_mes = bot.send_message(mes.chat.id, "Отправьте мастер пароль")
    bot.register_next_step_handler(
        mes, functools.partial(_get_account3, bot, engine, bot_mes, text)
    )


def _get_account3(
    bot: telebot.TeleBot, engine: Engine, prev_mes: Message, name: str, mes: Message
) -> None:
    _base(bot, mes, prev_mes)
    text = mes.text.strip()
    if text == "/cancel":
        return _send_tmp_message(bot, mes.chat.id, "Успешная отмена")

    master_pass = database.get.get_master_pass(engine, mes.from_user.id)
    if master_pass is None:
        return _send_tmp_message(bot, mes.chat.id, "Нет мастер пароля")

    master_salt, hash_pass = master_pass

    if cryptography.master_pass.encrypt_master_pass(text, master_salt) != hash_pass:
        return _send_tmp_message(bot, mes.chat.id, "Не подходит мастер пароль")

    salt, enc_login, enc_pass = database.get.get_account_info(
        engine, mes.from_user.id, name
    )
    login, passwd = cryptography.other_accounts.decrypt_account_info(
        enc_login, enc_pass, text.encode("utf-8"), salt
    )
    _send_tmp_message(
        bot,
        mes.chat.id,
        f"Логин:\n`{login}`\nПароль:\n`{passwd}`\nНажмите на логин или пароль, чтобы скопировать",
        30,
    )

    del text, mes, passwd, login
    gc.collect()


def delete_account(bot: telebot.TeleBot, engine: Engine, mes: Message) -> None:
    _base(bot, mes)
    bot_mes = bot.send_message(
        mes.chat.id, "Отправьте название аккаунта, который вы хотите удалить"
    )

    bot.register_next_step_handler(
        mes, functools.partial(_delete_account2, bot, engine, bot_mes)
    )


def _delete_account2(
    bot: telebot.TeleBot, engine: Engine, prev_mes: Message, mes: Message
) -> None:
    _base(bot, mes, prev_mes)
    text = mes.text.strip()
    if text == "/cancel":
        return _send_tmp_message(bot, mes.chat.id, "Успешная отмена")

    if text not in database.get.get_accounts(engine, mes.from_user.id):
        return _send_tmp_message(bot, mes.chat.id, "Нет такого аккаунта")

    database.delete.delete_account(engine, mes.from_user.id, text)
    _send_tmp_message(bot, mes.chat.id, "Аккаунт удалён")


def help(bot: telebot.TeleBot, mes: telebot.types.Message) -> None:
    message = """Команды:
/set_master_pass - установить мастер пароль
/add_account - создать аккаунт
/get_accounts - получить список аккаунтов
/get_account - получить логин и пароль аккаунта
/delete_account - удалить аккаунт
/delete_all - удалить все аккаунты и мастер пароль
/reset_master_pass - удалить все аккаунты и изменить мастер пароль
/cancel - отмена текущего действия
/help - помощь"""
    bot.send_message(mes.chat.id, message)


def cancel(bot: telebot.TeleBot, mes: Message) -> None:
    _send_tmp_message(bot, mes.chat.id, "Нет активного действия")