from cmath import log
from logging import Logger
import random
import asyncio
import re

from aiohttp import MultipartWriter
from bs4 import BeautifulSoup
from commands.teamspeak import teamspeak

from rollbot import as_command, RollbotFailure, Attachment
from rollbot.injection import Request, Args, Arg, Config, Reply


@as_command
async def inspire(req: Request, logger: Logger):
    """
    Using the command !inspire will request an inspiration image from http://inspirobot.me/
    """
    try:
        async with req.get("http://inspirobot.me/api?generate=true") as res:
            return await res.text()
    except:
        logger.exception("Failed !inspire")
        RollbotFailure.SERVICE_DOWN.raise_exc(detail="Could not reach inspirobot")


@as_command
async def shield(req: Request, blazon: Args, logger: Logger):
    boundary = f"----RollbotBoundary{''.join(str(random.randint(0, 9)) for _ in range(10))}"
    headers = {
        "Content-Type": f"multipart/form-data; boundary={boundary}",
        "Cache-Control": "no-cache",
    }
    with MultipartWriter(boundary=boundary) as form:
        form.append(blazon).set_content_disposition("form-data", name="blazon")
        form.append("1").set_content_disposition("form-data", name="asfile")
        form.append("wappenwiki").set_content_disposition("form-data", name="palette")
        form.append("flat").set_content_disposition("form-data", name="effect")
        form.append("500").set_content_disposition("form-data", name="size")
    try:
        async with req.post(
            "https://drawshield.net/include/drawshield.php",
            headers=headers,
            data=form,
        ) as res:
            res.raise_for_status()
            return Attachment("image", await res.read())
    except:
        logger.exception("Failed !shield")
        RollbotFailure.SERVICE_DOWN.raise_exc(detail="Could not reach DrawShield")


SCP_CLASS_LOOKUPS = (
    ("contain-class", "Containment Class"),
    ("second-class", "Secondary Class"), 
    ("disrupt-class", "Disruption Class"), 
    ("risk-class", "Risk Class"),
)


@as_command
async def scp(
    number: Arg(0, convert=int, fail_msg="Could not parse argument {} into integer"), req: Request
):
    page_url = f"http://www.scp-wiki.net/scp-{number:03d}"
    series_url = "http://www.scp-wiki.net/scp-series"
    series = (number // 1000) + 1
    if series != 1:
        series_url += "-" + str(series)

    async def get_obj_class():
        try:
            async with req.get(page_url) as page:
                page_html = BeautifulSoup(await page.text(), "html.parser")

            if page_html.find(class_="main-class"):
                return "\n".join(
                    f"{name}: {el.find(class_='class-text').text.title()}"
                    for cl, name in SCP_CLASS_LOOKUPS
                    if (el := page_html.find(class_=cl)) is not None and "$" not in el.text
                )

            for p in page_html.find_all("p"):
                if "Object Class" in p.text:
                    try:
                        # make one attempt to strip footnotes and strikethroughs
                        cls = next(ss for s in p.children if isinstance(s, str) and len(ss := s.strip()) > 0)
                        return f"Object Class: {cls}"
                    except:
                        return p.text
        except:
            pass

        return "Error retrieving object class!"

    query = f"SCP-{number:03d}"

    async def get_title():
        if number == 4000:
            return "[Title intentionally left blank per protocol 4000-Eshu]"
        try:
            async with req.get(series_url) as series:
                series_html = BeautifulSoup(await series.text(), "html.parser")

            for li in series_html.find_all("li"):
                if query in li.text:
                    return li.text.split("-", 2)[-1].strip()
        except:
            pass
        return "Error retrieving title!"

    obj_class, title = await asyncio.gather(get_obj_class(), get_title())
    # use https here, bet not above bc their certs are bad
    return f"Item # {number} - {title}\n{obj_class}\nhttps://www.scp-wiki.net/scp-{number:03d}"


@as_command
async def riddle(args: Args, req: Request, logger: Logger, sleep: Config("riddle.sleep")):
    if args.lower() != "me piss":
        RollbotFailure.INVALID_ARGUMENTS.raise_exc()

    rnum = None
    riddle = None
    for n in range(20):
        rnum = random.randint(2, 10050)
        async with req.get(f"https://www.riddles.com/{rnum}") as res:
            riddle_page = BeautifulSoup(await res.text(), "html.parser")

        riddle_panel = riddle_page.find("div", class_="panel-body")
        if riddle_panel is not None:
            parts = [p.text for p in riddle_panel.find_all("p")]
            if (
                len(parts) == 2
                and not parts[0].startswith("Riddle Status: User Rejected")
                and not parts[0].startswith("Riddle Status: User Moderated")
            ):
                riddle = parts[0]
                answer = parts[1]
                break
        logger.info(f"Failed to parse riddle #{rnum}, attempt #{n}")

    else:
        RollbotFailure.SERVICE_DOWN.raise_exc(detail="Failed to find a riddle fast enough")

    yield "\n\n".join(
        (
            f"Riddle #{rnum}",
            riddle,
            f"I'll post the response in about {sleep} seconds!",
        )
    )

    # TODO need to confirm this works without blocking in a proper task deploying system
    await asyncio.sleep(sleep)

    yield "\n\n".join(("Here's the riddle answer from before!", riddle, answer))


@as_command
async def selfie(req: Request, logger: Logger):
    try:
        async with req.get(
            "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/SECTOR/ne/GEOCOLOR/latest.jpg"
        ) as res:
            res.raise_for_status()
            return Attachment("image", await res.read())
    except:
        logger.exception("Failed to call GOES16")
        RollbotFailure.SERVICE_DOWN.raise_exc(detail="Failed to call GOES16")


@as_command
async def southie(req: Request, logger: Logger):
    try:
        async with req.get(
            "https://cdn.star.nesdis.noaa.gov/GOES16/ABI/SECTOR/se/GEOCOLOR/latest.jpg"
        ) as res:
            res.raise_for_status()
            return Attachment("image", await res.read())
    except:
        logger.exception("Failed to call GOES16")
        RollbotFailure.SERVICE_DOWN.raise_exc(detail="Failed to call GOES16")


@as_command
async def cat(req: Request, logger: Logger):
    """
    The !cat command grabs a cat from https://thiscatdoesnotexist.com/
    """
    try:
        async with req.get(
            "https://thiscatdoesnotexist.com/", headers={"User-Agent": "Rollbot"}
        ) as res:
            res.raise_for_status()
            return Attachment("image", await res.read())
    except:
        logger.exception("Failed to call cat generator")
        RollbotFailure.SERVICE_DOWN.raise_exc(detail="Failed to call cat generator")


@as_command
async def horse(req: Request, logger: Logger):
    """
    The !horse command grabs a horse from https://thishorsedoesnotexist.com/
    """
    try:
        async with req.get(
            "https://thishorsedoesnotexist.com/", headers={"User-Agent": "Rollbot"}
        ) as res:
            res.raise_for_status()
            return Attachment("image", await res.read())
    except:
        logger.exception("Failed to call horse generator")
        RollbotFailure.SERVICE_DOWN.raise_exc(detail="Failed to call horse generator")


@as_command
async def npc(req: Request, logger: Logger):
    """
    The !horse command grabs a person from https://thispersondoesnotexist.com/
    """
    try:
        async with req.get(
            "https://thispersondoesnotexist.com/", headers={"User-Agent": "Rollbot"}
        ) as res:
            res.raise_for_status()
            return Attachment("image", await res.read())
    except:
        logger.exception("Failed to call person generator")
        RollbotFailure.SERVICE_DOWN.raise_exc(detail="Failed to call person generator")


art_number = 0


@as_command
async def art(req: Request, logger: Logger):
    """
    The !art command uses the 9gans gallery at https://9gans.com/ which generates 9 images every hour.
    This command will cycle through those 9 images, so if you fire it 10 times in quick succession, the tenth
    piece of art might be the same as the first.
    """
    global art_number
    art_number += 1
    art_number %= 9
    try:
        async with req.get(
            f"https://storage.googleapis.com/9gans/mini/{art_number + 1}.jpg"
        ) as res:
            res.raise_for_status()
            return Attachment("image", await res.read())
    except:
        logger.exception("Failed to call art generator")
        RollbotFailure.SERVICE_DOWN.raise_exc(detail="Failed to call art generator")


@as_command
async def imagine(args: Args, api_key: Config("text2img.api_key"), req: Request, logger: Logger, reply: Reply):
    """
    The !imagine command uses the text2img API at https://deepai.org/machine-learning-model/text2img
    """

    args = args.strip()

    if len(args) == 0:
        RollbotFailure.INVALID_ARGUMENTS.raise_exc(
            detail="The !imagine command needs text to imagine!"
        )

    try:
        async with req.post(
            "https://api.deepai.org/api/text2img", data={"text": args}, headers={"api-key": api_key}
        ) as res:
            res.raise_for_status()
            print(await res.text())
            js = await res.json()
            return js.get("output_url", None), reply
    except:
        logger.exception("Failed to call text2img service")
        RollbotFailure.SERVICE_DOWN.raise_exc(detail="Failed to call text2img service")


@as_command
async def minecraft(minecraft_url: Config("minecraft.url"), req: Request, logger: Logger):
    try:
        async with req.get(minecraft_url) as res:
            res.raise_for_status()
            js = await res.json()
            if not js['online']:
                return "Server is offline!"
            data = js['data']
            return f"I see {data['currentPlayers']} people playing {data['motd']}"
    except:
        logger.exception("Failed to call MC service")
        RollbotFailure.SERVICE_DOWN.raise_exc(detail="Failed to query Minecraft server")


@as_command
async def mcts(ts_query: Config("teamspeak.query"), minecraft_url: Config("minecraft.url"), req: Request, logger: Logger):
    ts, mc = await asyncio.gather(teamspeak(ts_query, req, logger), minecraft(minecraft_url, req, logger))
    return f"Teamspeak: {ts}\nMinecraft: {mc}"


def get_openai(api_key, req, logger):
    async def make_call(prompt, echo=True):
        try:
            async with req.post(
                "https://api.openai.com/v1/completions", 
                json={
                    "model": "text-davinci-002",
                    "prompt": prompt,
                    "max_tokens": 1024,
                    "echo": echo,
                }, 
                headers={"Authorization": f"Bearer {api_key}"}
            ) as res:
                logger.info(await res.text())
                res.raise_for_status()
                js = await res.json()
                return js["choices"][0]["text"].strip()
        except:
            logger.exception("Failed to call OpenAI service")
            RollbotFailure.SERVICE_DOWN.raise_exc(detail="Failed to call OpenAI service")
    return make_call


@as_command
async def greentext(args: Args, api_key: Config("greentext.api_key"), req: Request, logger: Logger):
    """
    The !greentext command uses the text completion API at https://beta.openai.com/playground
    """
    openai = get_openai(api_key, req, logger)
    args = args.strip()

    if len(args) == 0:
        first_line = ">"
    else:
        first_line = f">{args.strip('>')}\n>"

    return re.sub(r"\n+", "\n", (await openai(f"Write me a 4chan greentext \n{first_line}")).replace(">\n", ""))


@as_command
async def item(args: Args, api_key: Config("greentext.api_key"), req: Request, logger: Logger):
    """    
    The !item command uses the text completion API at https://beta.openai.com/playground
    """
    openai = get_openai(api_key, req, logger)
    args = args.strip()

    prompt = "Write the stat block for a magical item for dungeons and dragons"
    if len(args) > 0:
        prompt = prompt + " " + args
    
    return re.sub(r"\n+", "\n", await openai(prompt))

@as_command
async def creature(args: Args, api_key: Config("greentext.api_key"), req: Request, logger: Logger):
    """    
    The !creature command uses the text completion API at https://beta.openai.com/playground
    """
    openai = get_openai(api_key, req, logger)
    args = args.strip()

    prompt = "Write the stat block for a creature for dungeons and dragons"
    if len(args) > 0:
        prompt = prompt + " " + args
    
    return re.sub(r"\n+", "\n", await openai(prompt))
