import asyncio
import hashlib
import random
from typing import Any, Sequence

from nonetrip.compat import CQHttpError
from nonetrip.compat import Event as CQEvent
from nonetrip.compat import escape

from . import NoneBot
from .typing import Expression_T, Message_T


def context_id(event: CQEvent,
               *,
               mode: str = 'default',
               use_hash: bool = False) -> str:
    """
    Calculate a unique id representing the context of the given event.

    mode:
      default: one id for one context
      group: one id for one group or discuss
      user: one id for one user

    :param event: the event object
    :param mode: unique id mode: "default", "group", or "user"
    :param use_hash: use md5 to hash the id or not
    """
    ctx_id = ''
    if mode == 'default':
        if event.group_id:
            ctx_id = f'/group/{event.group_id}'
        elif event.discuss_id:
            ctx_id = f'/discuss/{event.discuss_id}'
        if event.user_id:
            ctx_id += f'/user/{event.user_id}'
    elif mode == 'group':
        if event.group_id:
            ctx_id = f'/group/{event.group_id}'
        elif event.discuss_id:
            ctx_id = f'/discuss/{event.discuss_id}'
        elif event.user_id:
            ctx_id = f'/user/{event.user_id}'
    elif mode == 'user':
        if event.user_id:
            ctx_id = f'/user/{event.user_id}'

    if ctx_id and use_hash:
        ctx_id = hashlib.md5(ctx_id.encode('ascii')).hexdigest()
    return ctx_id


async def send(bot: NoneBot,
               event: CQEvent,
               message: Message_T,
               *,
               ensure_private: bool = False,
               ignore_failure: bool = True,
               **kwargs) -> Any:
    """Send a message ignoring failure by default."""
    try:
        if ensure_private:
            event = event.copy()
            event['message_type'] = 'private'
        return await bot.send(event, message, **kwargs)
    except CQHttpError:
        if not ignore_failure:
            raise
        return None


async def send_to_superusers(bot: NoneBot, message: Message_T,
                             **kwargs) -> None:
    """
    Send a private message to all superusers that are defined in config.

    :param bot: nonebot object
    :param message: message to send to each superuser
    :param kwargs: keyword arguments used in bot.send_private_msg()
    """
    tasks = [
        bot.send_private_msg(user_id=user_id, message=message, **kwargs)
        for user_id in bot.config.SUPERUSERS
    ]
    await asyncio.gather(*tasks)


def render_expression(expr: Expression_T,
                      *args,
                      escape_args: bool = True,
                      **kwargs) -> str:
    """
    Render an expression to message string.

    :param expr: expression to render
    :param escape_args: should escape arguments or not
    :param args: positional arguments used in str.format()
    :param kwargs: keyword arguments used in str.format()
    :return: the rendered message
    """
    result: str
    if callable(expr):
        result = expr(*args, **kwargs)
    elif isinstance(expr, Sequence) and not isinstance(expr, str):
        result = random.choice(expr)
    else:
        result = expr
    if escape_args:
        return result.format(
            *[escape(s) if isinstance(s, str) else s for s in args], **{
                k: escape(v) if isinstance(v, str) else v
                for k, v in kwargs.items()
            })
    return result.format(*args, **kwargs)


__all__ = [
    'context_id',
    'send',
    'send_to_superusers',
    'render_expression',
]
