import re
import datetime
import telegram
import typing
import os
import aiohttp
from ..utils import Command, Call, InvalidInputError, InvalidConfigError, ExternalError
from ..database.tables import Royal, Diario, Alias
from ..utils import asyncify


# NOTE: Requires imgur api key for image upload, get one at https://apidocs.imgur.com
class DiarioCommand(Command):

    command_name = "diario"
    command_title = "Aggiungi una citazione al Diario."
    command_syntax = "[!] \"(testo)\" --[autore], [contesto]"

    require_alchemy_tables = {Royal, Diario, Alias}

    async def _telegram_to_imgur(self, photosizes: typing.List[telegram.PhotoSize], caption="") -> str:
        # Select the largest photo
        largest_photo = sorted(photosizes, key=lambda p: p.width * p.height)[-1]
        # Get the photo url
        photo_file: telegram.File = await asyncify(largest_photo.get_file)
        # Forward the url to imgur, as an upload
        try:
            imgur_api_key = os.environ["IMGUR_CLIENT_ID"]
        except KeyError:
            raise InvalidConfigError("Missing IMGUR_CLIENT_ID envvar, can't upload images to imgur.")
        async with aiohttp.request("post", "https://api.imgur.com/3/upload", data={
            "image": photo_file.file_path,
            "type": "URL",
            "title": "Diario image",
            "description": caption
        }, headers={
            "Authorization": f"Client-ID {imgur_api_key}"
        }) as request:
            response = await request.json()
            if not response["success"]:
                raise ExternalError("imgur returned an error in the image upload.")
            return response["data"]["link"]

    async def common(self, call: Call):
        # Find the creator of the quotes
        creator = await call.get_author()
        if creator is None:
            await call.reply("⚠️ Devi essere registrato a Royalnet per usare questo comando!")
            return
        # Recreate the full sentence
        raw_text = " ".join(call.args)
        # Pass the sentence through the diario regex
        match = re.match(r'(!)? *["«‘“‛‟❛❝〝"`]([^"]+)["»’”❜❞〞"`] *(?:(?:-{1,2}|—) *([\w ]+))?(?:, *([^ ].*))?', raw_text)
        # Find the corresponding matches
        if match is not None:
            spoiler = bool(match.group(1))
            text = match.group(2)
            quoted = match.group(3)
            context = match.group(4)
        # Otherwise, consider everything part of the text
        else:
            spoiler = False
            text = raw_text
            quoted = None
            context = None
        timestamp = datetime.datetime.now()
        # Ensure there is some text
        if not text:
            raise InvalidInputError("Missing text.")
        # Or a quoted
        if not quoted:
            quoted = None
        if not context:
            context = None
        # Find if there's a Royalnet account associated with the quoted name
        if quoted is not None:
            quoted_alias = await asyncify(call.session.query(call.alchemy.Alias).filter_by(alias=quoted.lower()).one_or_none)
        else:
            quoted_alias = None
        quoted_account = quoted_alias.royal if quoted_alias is not None else None
        if quoted_alias is not None and quoted_account is None:
            await call.reply("⚠️ Il nome dell'autore è ambiguo, quindi la riga non è stata aggiunta.\nPer piacere, ripeti il comando con un nome più specifico!")
            return
        # Create the diario quote
        diario = call.alchemy.Diario(creator=creator,
                                     quoted_account=quoted_account,
                                     quoted=quoted,
                                     text=text,
                                     context=context,
                                     timestamp=timestamp,
                                     media_url=None,
                                     spoiler=spoiler)
        call.session.add(diario)
        await asyncify(call.session.commit)
        await call.reply(f"✅ {str(diario)}")

    async def telegram(self, call: Call):
        update: telegram.Update = call.kwargs["update"]
        message: telegram.Message = update.message
        reply: telegram.Message = message.reply_to_message
        creator = await call.get_author()
        if creator is None:
            await call.reply("⚠️ Devi essere registrato a Royalnet per usare questo comando!")
            return
        if reply is not None:
            # Get the message text
            text = reply.text
            # Check if there's an image associated with the reply
            photosizes: typing.Optional[typing.List[telegram.PhotoSize]] = reply.photo
            if photosizes:
                # Text is a caption
                text = reply.caption
                # Python is doing some weird stuff here, self._telegram_to_imgur appears to be unbound?
                # noinspection PyArgumentList
                media_url = await self._telegram_to_imgur(self, photosizes, text if text is not None else "")
            else:
                media_url = None
            # Ensure there is a text or an image
            if not (text or media_url):
                raise InvalidInputError("Missing text.")
            # Find the Royalnet account associated with the sender
            quoted_tg = await asyncify(call.session.query(call.alchemy.Telegram).filter_by(tg_id=reply.from_user.id).one_or_none)
            quoted_account = quoted_tg.royal if quoted_tg is not None else None
            # Find the quoted name to assign
            quoted_user: telegram.User = reply.from_user
            quoted: str = quoted_user.full_name
            # Get the timestamp
            timestamp = reply.date
            # Set the other properties
            spoiler = False
            context = None
        else:
            # Get the current timestamp
            timestamp = datetime.datetime.now()
            # Get the message text
            raw_text = " ".join(call.args)
            # Check if there's an image associated with the reply
            photosizes: typing.Optional[typing.List[telegram.PhotoSize]] = message.photo
            if photosizes:
                # Python is doing some weird stuff here, self._telegram_to_imgur appears to be unbound?
                # noinspection PyArgumentList
                media_url = await self._telegram_to_imgur(self, photosizes, raw_text if raw_text is not None else "")
            else:
                media_url = None
            # Parse the text, if it exists
            if raw_text:
                # Pass the sentence through the diario regex
                match = re.match(r'(!)? *["«‘“‛‟❛❝〝"`]([^"]+)["»’”❜❞〞"`] *(?:(?:-{1,2}|—) *([\w ]+))?(?:, *([^ ].*))?',
                                 raw_text)
                # Find the corresponding matches
                if match is not None:
                    spoiler = bool(match.group(1))
                    text = match.group(2)
                    quoted = match.group(3)
                    context = match.group(4)
                # Otherwise, consider everything part of the text
                else:
                    spoiler = False
                    text = raw_text
                    quoted = None
                    context = None
                # Ensure there's a quoted
                if not quoted:
                    quoted = None
                if not context:
                    context = None
                # Find if there's a Royalnet account associated with the quoted name
                if quoted is not None:
                    quoted_alias = await asyncify(
                        call.session.query(call.alchemy.Alias).filter_by(alias=quoted.lower()).one_or_none)
                else:
                    quoted_alias = None
                quoted_account = quoted_alias.royal if quoted_alias is not None else None
            else:
                text = None
                quoted = None
                quoted_account = None
                spoiler = False
                context = None
            # Ensure there is a text or an image
            if not (text or media_url):
                raise InvalidInputError("Missing text.")
        # Create the diario quote
        diario = call.alchemy.Diario(creator=creator,
                                     quoted_account=quoted_account,
                                     quoted=quoted,
                                     text=text,
                                     context=context,
                                     timestamp=timestamp,
                                     media_url=media_url,
                                     spoiler=spoiler)
        call.session.add(diario)
        await asyncify(call.session.commit)
        await call.reply(f"✅ {str(diario)}")