# type: ignore
from discord import utils, AllowedMentions, File, InvalidArgument, Message
from discord.http import Route
from discord.flags import MessageFlags

from ...interactions import ActionRow


__all__ = (
    "create_message_with_components",
    "send_with_components",
    "edit_with_components",
    "MessageWithComponents"
)


def create_message_with_components(self, *, channel, data):
    return MessageWithComponents(state=self, channel=channel, data=data)


def send_message(self, channel_id, content, *, tts=False, embed=None, nonce=None,
                 allowed_mentions=None, message_reference=None,
                 components=None):
    r = Route('POST', '/channels/{channel_id}/messages', channel_id=channel_id)
    payload = {}

    if content:
        payload['content'] = content

    if tts:
        payload['tts'] = True

    if embed:
        payload['embed'] = embed

    if nonce:
        payload['nonce'] = nonce

    if allowed_mentions:
        payload['allowed_mentions'] = allowed_mentions

    if message_reference:
        payload['message_reference'] = message_reference

    if components:
        payload['components'] = components

    return self.request(r, json=payload)


def send_files(self, channel_id, *, files, content=None, tts=False, embed=None, nonce=None,
               allowed_mentions=None, message_reference=None,
               components=None):
    r = Route('POST', '/channels/{channel_id}/messages', channel_id=channel_id)
    form = []

    payload = {'tts': tts}
    if content:
        payload['content'] = content
    if embed:
        payload['embed'] = embed
    if nonce:
        payload['nonce'] = nonce
    if allowed_mentions:
        payload['allowed_mentions'] = allowed_mentions
    if message_reference:
        payload['message_reference'] = message_reference
    if components:
        payload['components'] = components

    form.append({'name': 'payload_json', 'value': utils.to_json(payload)})
    if len(files) == 1:
        file = files[0]
        form.append({
            'name': 'file',
            'value': file.fp,
            'filename': file.filename,
            'content_type': 'application/octet-stream'
        })
    else:
        for index, file in enumerate(files):
            form.append({
                'name': 'file%s' % index,
                'value': file.fp,
                'filename': file.filename,
                'content_type': 'application/octet-stream'
            })

    return self.request(r, form=form, files=files)


async def send_with_components(messageable, content=None, *,
                               tts=False, embed=None,
                               components=None,
                               file=None, files=None,
                               delete_after=None, nonce=None,
                               allowed_mentions=None, reference=None,
                               mention_author=None):
    channel = await messageable._get_channel()
    state = messageable._state
    content = str(content) if content is not None else None
    if embed is not None:
        embed = embed.to_dict()

    if components is not None:
        if len(components) > 5:
            raise InvalidArgument("components must be a list of up to 5 action rows")
        wrapped = []
        for comp in components:
            if isinstance(comp, ActionRow):
                wrapped.append(comp)
            else:
                wrapped.append(ActionRow(comp))
        components = [comp.to_dict() for comp in wrapped]

    if allowed_mentions is None:
        allowed_mentions = state.allowed_mentions and state.allowed_mentions.to_dict()

    elif state.allowed_mentions is not None:
        allowed_mentions = state.allowed_mentions.merge(allowed_mentions).to_dict()
    else:
        allowed_mentions = allowed_mentions.to_dict()
    if mention_author is not None:
        allowed_mentions = allowed_mentions or AllowedMentions().to_dict()
        allowed_mentions['replied_user'] = bool(mention_author)

    if reference is not None:
        try:
            reference = reference.to_message_reference_dict()
        except AttributeError:
            raise InvalidArgument('reference parameter must be Message or MessageReference') from None

    if file is not None and files is not None:
        raise InvalidArgument('cannot pass both file and files parameter to send()')

    if file is not None:
        if not isinstance(file, File):
            raise InvalidArgument('file parameter must be File')

        try:
            data = await send_files(state.http, channel.id, files=[file], allowed_mentions=allowed_mentions,
                                    content=content, tts=tts, embed=embed, nonce=nonce,
                                    message_reference=reference, components=components)
        finally:
            file.close()

    elif files is not None:
        if len(files) > 10:
            raise InvalidArgument('files parameter must be a list of up to 10 elements')
        elif not all(isinstance(file, File) for file in files):
            raise InvalidArgument('files parameter must be a list of File')

        try:
            data = await send_files(state.http, channel.id, files=files, content=content, tts=tts,
                                    embed=embed, nonce=nonce, allowed_mentions=allowed_mentions,
                                    message_reference=reference, components=components)
        finally:
            for f in files:
                f.close()
    else:
        data = await send_message(state.http, channel.id, content, tts=tts, embed=embed,
                                  nonce=nonce, allowed_mentions=allowed_mentions,
                                  message_reference=reference, components=components)

    ret = state.create_message(channel=channel, data=data)
    if delete_after is not None:
        await ret.delete(delay=delete_after)
    return ret


async def edit_with_components(message, **fields):
    try:
        content = fields['content']
    except KeyError:
        pass
    else:
        if content is not None:
            fields['content'] = str(content)

    try:
        embed = fields['embed']
    except KeyError:
        pass
    else:
        if embed is not None:
            fields['embed'] = embed.to_dict()

    try:
        components = fields['components']
    except KeyError:
        pass
    else:
        if components:
            _components = []
            for comp in components:
                if isinstance(comp, ActionRow):
                    _components.append(comp.to_dict())
                else:
                    _components.append(ActionRow(comp).to_dict())
            fields['components'] = _components

    try:
        suppress = fields.pop('suppress')
    except KeyError:
        pass
    else:
        flags = MessageFlags._from_value(message.flags.value)
        flags.suppress_embeds = suppress
        fields['flags'] = flags.value

    delete_after = fields.pop('delete_after', None)

    try:
        allowed_mentions = fields.pop('allowed_mentions')
    except KeyError:
        pass
    else:
        if allowed_mentions is not None:
            if message._state.allowed_mentions is not None:
                allowed_mentions = message._state.allowed_mentions.merge(allowed_mentions).to_dict()
            else:
                allowed_mentions = allowed_mentions.to_dict()
            fields['allowed_mentions'] = allowed_mentions

    if fields:
        data = await message._state.http.edit_message(message.channel.id, message.id, **fields)
        message._update(data)

    if delete_after is not None:
        await message.delete(delay=delete_after)


class MessageWithComponents(Message):
    def __init__(self, *, state, channel, data):
        super().__init__(
            state=state,
            channel=channel,
            data=data
        )
        self.components = [ActionRow.from_dict(dat) for dat in data.get("components", [])]

    def __repr__(self):
        res = super().__repr__()
        return f"{res[:-1]} components={self.components!r}>"
