from typing import List, Tuple, Optional

from discord import Message
from discord.ext.commands import Context

from src.db.db import Db

icons = {
    "NOT_STARTED": ":white_large_square:",
    "STARTED": ":arrow_forward:",
    "CHECKED": ":white_check_mark:"
}


def get_icon(task):
    return icons[task["state"]]


def print_task_list(db, owner: Message.author, print_nums=True):
    task_list = db.get_tasks(get_list_items(db, owner))
    output = []
    for i, task in enumerate(task_list):
        output.append(f"{get_icon(task)} {task['name']}" + (f"    ({i + 1})" if print_nums else ""))
    return "\n".join(output)


def get_list_items(db, owner: Message.author):
    task_list = db.get_list_items(owner.id)
    if task_list is None:
        raise ListBotError(f"I don't have a list assigned to {owner.name}. "
                           f"Check ~help newlist to see how to create your own todo list.")
    return task_list


def find_task_id_in_list(db: Db, task_ids: List[int], item: str) -> int:
    """
    :param db:
    :param task_ids:
    :param item:
    :return:
    """
    if item.isdigit():
        try:
            return task_ids[int(item) - 1]
        except IndexError:
            raise ListBotError(f"{item} is not a valid list position.")
    else:
        filtered_task_ids = db.filter_task_ids_by_name(task_ids, item)
        if len(filtered_task_ids) == 0:
            raise ListBotError(f"Couldn't find any list item matching \"{item}\"")
        if len(filtered_task_ids) > 1:
            task_names = db.get_task_names(filtered_task_ids)
            raise ListBotError(f"Multiple items found matching \"{item}\":\n" + "\n".join(task_names))
        return filtered_task_ids[0]


def pretty_task_time(secs: int) -> str:
    secs = round(secs)
    if secs < 60:
        return f"{secs}s"
    if secs < 3600:
        return f"{int(secs / 60)}m{secs % 60}s"
    return f"{int(secs / 3600)}h{int(secs % 3600 / 60)}m{secs % 60}s"


def get_display_mode(context: Context, db: Db, owner: Message.author, result: str) -> \
        Tuple[Optional[str], Optional[str], Optional[int]]:
    """
    Checks the owner's display mode.
    If it's EDIT, it updates the owner's whole list in place.
    If it's POST, it pulls the owner's whole list and returns that as a new text post.
    If it's UPDATE, it updates the owner's whole list in place, and also posts the update text as a new text post.
    :param context: Discord message Context object
    :param db: Opened Db object
    :param owner:
    :param result: Output generated by the particular function. e.g., "[ ] Task name  (1)"
    :return: text to post, text used to edit last message, last message ID
    """
    list_output = f"{owner.name}'s list\n" + print_task_list(db, owner)
    display_mode = db.get_display_mode(owner.id)
    if display_mode == "POST":
        return list_output, None, None
    last_message_id = db.get_last_message_id(owner.id, context.channel.id)
    if last_message_id is None:
        return list_output, None, None
    if display_mode == "EDIT":
        return None, list_output, last_message_id
    if display_mode == "UPDATE":
        return result, list_output, last_message_id
    raise ValueError(f"{display_mode} is an unrecognized display mode.")


class ListBotError(Exception):
    pass

from typing import List, Tuple, Optional

from discord import Message
from discord.ext.commands import Context

from src.db.db import Db

icons = {
    "NOT_STARTED": ":white_large_square:",
    "STARTED": ":arrow_forward:",
    "CHECKED": ":white_check_mark:"
}


def get_icon(task):
    return icons[task["state"]]


def print_task_list(db, owner: Message.author, print_nums=True):
    task_list = db.get_tasks(get_list_items(db, owner))
    output = []
    for i, task in enumerate(task_list):
        output.append(f"{get_icon(task)} {task['name']}" + (f"    ({i + 1})" if print_nums else ""))
    return "\n".join(output)


def get_list_items(db, owner: Message.author):
    task_list = db.get_list_items(owner.id)
    if task_list is None:
        raise ListBotError(f"I don't have a list assigned to {owner.name}. "
                           f"Check ~help newlist to see how to create your own todo list.")
    return task_list


def find_task_id_in_list(db: Db, task_ids: List[int], item: str) -> int:
    """
    :param db:
    :param task_ids:
    :param item:
    :return:
    """
    if item.isdigit():
        try:
            return task_ids[int(item) - 1]
        except IndexError:
            raise ListBotError(f"{item} is not a valid list position.")
    else:
        filtered_task_ids = db.filter_task_ids_by_name(task_ids, item)
        if len(filtered_task_ids) == 0:
            raise ListBotError(f"Couldn't find any list item matching \"{item}\"")
        if len(filtered_task_ids) > 1:
            task_names = db.get_task_names(filtered_task_ids)
            raise ListBotError(f"Multiple items found matching \"{item}\":\n" + "\n".join(task_names))
        return filtered_task_ids[0]


def pretty_task_time(secs: int) -> str:
    secs = round(secs)
    if secs < 60:
        return f"{secs}s"
    if secs < 3600:
        return f"{int(secs / 60)}m{secs % 60}s"
    return f"{int(secs / 3600)}h{int(secs % 3600 / 60)}m{secs % 60}s"


def get_display_mode(context: Context, db: Db, owner: Message.author, result: str) -> \
        Tuple[Optional[str], Optional[str], Optional[int]]:
    """
    Checks the owner's display mode.
    If it's EDIT, it updates the owner's whole list in place.
    If it's POST, it pulls the owner's whole list and returns that as a new text post.
    If it's UPDATE, it updates the owner's whole list in place, and also posts the update text as a new text post.
    :param context: Discord message Context object
    :param db: Opened Db object
    :param owner:
    :param result: Output generated by the particular function. e.g., "[ ] Task name  (1)"
    :return: text to post, text used to edit last message, last message ID
    """
    list_output = f"{owner.name}'s list\n" + print_task_list(db, owner)
    display_mode = db.get_display_mode(owner.id)
    if display_mode == "POST":
        return list_output, None, None
    last_message_id = db.get_last_message_id(owner.id, context.channel.id)
    if last_message_id is None:
        return list_output, None, None
    if display_mode == "EDIT":
        return None, list_output, last_message_id
    if display_mode == "UPDATE":
        return result, list_output, last_message_id
    raise ValueError(f"{display_mode} is an unrecognized display mode.")


class ListBotError(Exception):
    pass

:param db:     :param task_ids:     :param item:     :return: Checks the owner's display mode.     If it's EDIT, it updates the owner's whole list in place.     If it's POST, it pulls the owner's whole list and returns that as a new text post.     If it's UPDATE, it updates the owner's whole list in place, and also posts the update text as a new text post.     :param context: Discord message Context object     :param db: Opened Db object     :param owner:     :param result: Output generated by the particular function. e.g., "[ ] Task name  (1)"     :return: text to post, text used to edit last message, last message ID