from datetime import datetime
from base64 import b64decode
from uuid import uuid4
import re

from rollbot import as_command, initialize_data, RollbotFailure, Response, Command
from rollbot.injection import Data, Config, Subcommand, Lazy, Args, Origin, SenderName


KEY = "HANG_GUY_SINGLETON"
GUY_STAGES = [
    "",
    """\
😎
""",
    """\
😎
👔
""",
    """\
😎
👔
⛽
""",
    """\
😎
👔
⛽
⚡
""",
    """\
👍     😎
          👔
          ⛽
          ⚡
""",
    """\
👍     😎
🐛     👔
          ⛽
          ⚡
""",
    """\
🖕     😎
🐛💤👔
          ⛽
          ⚡
""",
    """\
🖕     😎
🐛💤👔🐛
          ⛽
          ⚡
""",
    """\
🖕     😟
🐛💤👔🐛
          ⛽ 👢
          ⚡
""",
    """\
🖕     😟
🐛💤👔🐛
          ⛽ 👢
          ⚡👊
""",
    """\
🖕     😨
🐛💤👔🐛
          ⛽  👢
          ⚡8=👊
""",
    """\
🖕     😨
🐛💤👔🐛
          ⛽  👢
          ⚡8=👊
      🎸
""",
    """\
🖕     😰
🐛💤👔🐛
          ⛽  👢
          ⚡8=👊
      🎸
  👢
""",
    """\
🖕     😰
🐛💤👔🐛
          ⛽  👢
          ⚡8=👊
      🎸    🌽
  👢
""",
    """\
🖕     😰
🐛💤👔🐛
          ⛽  👢
          ⚡8=👊
      🎸    🌽
  👢             👢
""",
    """\
🖕     😫
🐛💤👔🐛
          ⛽  👢
          ⚡8=👊=D
      🎸    🌽
  👢             👢
""",
    """\
🖕     😵
🐛💤👔🐛
          ⛽  👢
          ⚡8=👊=D💦
      🎸    🌽
  👢             👢
""",
]


@initialize_data
class HangGuy:
    puzzle: str | None = None
    game_state: str | None = None
    bad_guesses: str = ""
    guy_state: int = 0
    guy_lifetime: int = 0

    def new_game(self, phrase: str):
        self.puzzle = b64decode(phrase).decode("utf-8").strip().upper()
        self.game_state = re.sub("[A-Z]", "_", self.puzzle)
        self.bad_guesses = ""

    def is_active(self):
        return self.puzzle is not None

    def is_finished(self):
        return self.puzzle == self.game_state

    def end_game(self):
        self.puzzle = None

    def reset_guy(self):
        self.guy_state = 0
        self.guy_lifetime = 0

    def is_dead(self):
        return self.guy_state >= (len(GUY_STAGES) - 1)

    def survival_msg(self):
        return f"The current guy has survived {self.guy_lifetime} game{'' if self.guy_lifetime == 1 else 's'}"

    def render(self):
        if 0 <= self.guy_state < len(GUY_STAGES):
            guy = GUY_STAGES[self.guy_state]
        elif self.guy_state >= len(GUY_STAGES):
            guy = GUY_STAGES[-1]
        else:
            guy = f"INVALID GUY STATE {self.guy_state}"

        if not self.is_active():
            return guy

        return f"{guy}\n{' '.join(self.game_state)}\nBad Guesses: {', '.join(self.bad_guesses)}"


@initialize_data
class DeadGuy:
    state: int
    lifetime: int
    timestamp: float

    @staticmethod
    def from_guy(hg: HangGuy):
        return DeadGuy(
            state=hg.guy_state,
            lifetime=hg.guy_lifetime,
            timestamp=datetime.now().timestamp(),
        )


@as_command
async def hangguy(
    cmd: Command,
    name: SenderName,
    origin: Origin,
    store: Data(HangGuy),
    dead_store: Data(DeadGuy),
    alert_channel: Config("hangguy.alert_channel"),
):
    if len(cmd.args) == 0:
        RollbotFailure.INVALID_ARGUMENTS.raise_exc(
            detail="Must provide subcommand or guess"
        )

    game = await store.load_or(KEY)
    is_active = game.is_active()

    subc = cmd.get_subcommand(inherit_bang=False)  # get subcommand, requiring bang

    if subc is None or cmd.bang != subc.bang:
        if game.is_active():
            guess = cmd.args.strip().upper()
            prefix = ""
            if len(guess) == 1:
                if not guess.isalpha():
                    prefix = "You should try sticking to letters!"
                elif guess in game.bad_guesses or guess in game.game_state:
                    prefix = f"You've already guessed '{guess}'!"
                else:
                    find = [i for i, x in enumerate(game.puzzle) if x == guess]
                    if len(find) == 0:
                        game.bad_guesses += guess
                        game.guy_state += 1
                        prefix = "Bad guess!"
                    else:
                        game.game_state = "".join(
                            guess if i in find else s
                            for i, s in enumerate(game.game_state)
                        )
                        prefix = f"Great! '{guess}' appears {len(find)} time{'' if len(find) == 1 else 's'}!"
            elif game.puzzle.split() == guess.split():
                prefix = "You've guessed the full phrase!"
                game.game_state = game.puzzle
            else:
                prefix = f"{guess} is not the phrase!"
                game.guy_state += 2

            txt = f"{prefix}\n{game.render()}"

            if game.is_finished():
                game.end_game()
                game.guy_lifetime += 1
                txt += (
                    f"\nThe game is over, and the guy lives on!\n{game.survival_msg()}"
                )

            if game.is_dead():
                game.end_game()
                dead = DeadGuy.from_guy(game)
                game.reset_guy()
                await dead_store.save(
                    f"{uuid4().hex}-{datetime.now().isoformat()}", dead_guy
                )
                txt += f"\noh god oh fuck, the guy is dead!\nYou failed to guess {game.puzzle}\nThe guy has been buried."

            await store.save(KEY, game)
            return txt
        RollbotFailure.MISSING_SUBCOMMAND.raise_exc(
            detail="You are not currently in a game, and so you must use one of the subcommands: !view, !retire, !start, !alert"
        )

    if subc.name == "alert":
        return Response(
            origin_id=origin,
            channel_id=alert_channel,
            text=f"{name} wants you to check out the hangguy chat!",
        )

    if subc.name == "cancel" and is_active:
        game.end_game()
        await store.save(KEY, game)
        return "The game has been cancelled. The guy has not been reset."

    if subc.name == "retire" and not is_active:
        txt = f"{game.survival_msg()} and is retiring\n{game.render()}"
        dead_guy = DeadGuy.from_guy(game)
        game.reset_guy()
        await dead_store.save(f"{uuid4().hex}-{datetime.now().isoformat()}", dead_guy)
        await store.save(KEY, game)
        return txt

    if subc.name == "start" and not is_active:
        game.new_game(subc.args)
        await store.save(KEY, game)
        return game.render()

    if subc.name == "view" and not is_active:
        return f"{game.survival_msg()}\n" + game.render()

    if is_active:
        RollbotFailure.INVALID_SUBCOMMAND.raise_exc(
            detail="The only in-game subcommands are !cancel to end the game and !alert to alert the main chat."
        )

    RollbotFailure.INVALID_SUBCOMMAND.raise_exc(
        detail="You must use one of the subcommands: !view, !retire, !start, !alert"
    )
