from collections import namedtuple
from datetime import datetime
from enum import Enum, auto
from typing import TYPE_CHECKING

import discord
from dateutil import tz
from sqlalchemy import BigInteger, Column, DateTime, ForeignKey, Integer, String
from sqlalchemy.orm import relationship
from sqlalchemy.sql.expression import text

from spellbot.settings import Settings

from . import Base, now

if TYPE_CHECKING:  # pragma: no cover
    from .channel import Channel  # noqa
    from .guild import Guild  # noqa
    from .user import User  # noqa


class GameStatus(Enum):
    PENDING = auto()
    STARTED = auto()


# Additional metadata related to supported game formats.
FormatDetails = namedtuple("FormatDetails", ["players"])


class GameFormat(Enum):
    def __new__(cls, *args, **kwargs):  # pylint: disable=W0613
        """Give each enum value an increasing numerical value starting at 1."""
        value = len(cls.__members__) + 1
        obj = object.__new__(cls)
        obj._value_ = value
        return obj

    def __init__(self, players: int):
        """Each enum has certain additional properties taken from FormatDetails."""
        self.players = players

    def __str__(self) -> str:
        return self.name.replace("_", " ").title()

    COMMANDER = FormatDetails(players=4)
    STANDARD = FormatDetails(players=2)
    SEALED = FormatDetails(players=2)
    MODERN = FormatDetails(players=2)
    VINTAGE = FormatDetails(players=2)
    LEGACY = FormatDetails(players=2)
    BRAWL_TWO_PLAYER = FormatDetails(players=2)
    BRAWL_MULTIPLAYER = FormatDetails(players=4)
    TWO_HEADED_GIANT = FormatDetails(players=4)
    PAUPER = FormatDetails(players=2)
    PIONEER = FormatDetails(players=2)


class Game(Base):
    __tablename__ = "games"

    id = Column(
        Integer,
        autoincrement=True,
        nullable=False,
        primary_key=True,
        doc="The SpellBot game reference ID",
    )
    created_at = Column(
        DateTime,
        nullable=False,
        default=datetime.utcnow,
        server_default=now,
        doc="UTC timestamp when this games was first created",
    )
    updated_at = Column(
        DateTime,
        nullable=False,
        default=datetime.utcnow,
        server_default=now,
        onupdate=datetime.utcnow,
        doc="UTC timestamp when this games was last updated",
    )
    started_at = Column(
        DateTime,
        nullable=True,
        doc="UTC timestamp when this games was started",
    )
    guild_xid = Column(
        BigInteger,
        ForeignKey("guilds.xid", ondelete="CASCADE"),
        index=True,
        nullable=False,
        doc="The external Discord ID of the associated guild",
    )
    channel_xid = Column(
        BigInteger,
        ForeignKey("channels.xid", ondelete="CASCADE"),
        index=True,
        nullable=False,
        doc="The external Discord ID of the associated channel",
    )
    message_xid = Column(
        BigInteger,
        index=True,
        nullable=True,
        doc="The external Discord ID of the messsage where this game's embed is found",
    )
    voice_xid = Column(
        BigInteger,
        index=True,
        nullable=True,
        doc="The external Discord ID of an associated voice channel",
    )
    seats = Column(
        Integer,
        index=True,
        nullable=False,
        doc="The number of seats (open or occupied) available at this game",
    )
    status = Column(
        Integer(),
        default=GameStatus.PENDING.value,
        server_default=text(str(GameStatus.PENDING.value)),
        index=True,
        nullable=False,
        doc="Pending or started status of this game",
    )
    format = Column(
        Integer(),
        default=GameFormat.COMMANDER.value,
        server_default=text(str(GameFormat.COMMANDER.value)),
        index=True,
        nullable=False,
        doc="The Magic: The Gathering format for this game",
    )
    spelltable_link = Column(
        String(255),
        doc="The generated SpellTable link for this game",
    )
    voice_invite_link = Column(
        String(255),
        doc="The generate voice channel invite link for this game",
    )

    players = relationship(
        "User",
        back_populates="game",
        uselist=True,
        doc="Players in this game",
    )
    guild = relationship(
        "Guild",
        back_populates="games",
        doc="The guild this game was created in",
    )
    channel = relationship(
        "Channel",
        back_populates="games",
        doc="The channel this game was created in",
    )

    @property
    def started_at_timestamp(self) -> int:
        assert self.started_at is not None
        return int(self.started_at.replace(tzinfo=tz.UTC).timestamp())

    def show_links(self, dm: bool = False) -> bool:
        return True if dm else self.guild.show_links

    @property
    def embed_title(self) -> str:
        if self.status == GameStatus.STARTED.value:
            return "**Your game is ready!**"
        remaining = int(self.seats) - len(self.players)
        plural = "s" if remaining > 1 else ""
        return f"**Waiting for {remaining} more player{plural} to join...**"

    def embed_description(self, dm: bool = False) -> str:
        description = ""
        if self.status == GameStatus.PENDING.value:
            description += (
                "_A SpellTable link will be created when all players have joined._"
            )
        else:
            if self.show_links(dm):
                if self.spelltable_link:
                    description += (
                        "[Join your SpellTable game now!]"
                        f"(<{self.spelltable_link}>)"
                        " (or [spectate this game]"
                        f"({self.spelltable_link}?spectate=true))"
                    )
                else:
                    description += (
                        "Sorry but SpellBot was unable to create a SpellTable link"
                        " for this game. Please go to [SpellTable]"
                        "(https://spelltable.wizards.com/) to create one."
                    )
                if self.voice_invite_link:
                    settings = Settings()
                    expire_min = int(settings.VOICE_INVITE_EXPIRE_TIME_S / 60)
                    description += (
                        f"\n\n[Join your voice chat now!]({self.voice_invite_link})"
                        f"  (invite will expire in {expire_min} minutes)"
                    )
            else:
                description += (
                    "Please check your Direct Messages for your SpellTable link."
                )
            if dm:
                description += (
                    "\n\nYou can also [jump to the original game post]"
                    f"({self.jump_link}) in <#{self.channel_xid}>."
                )
            elif self.guild.show_points:
                description += (
                    "\n\nWhen your game is over use the drop down to report your points."
                )
        if self.guild.motd:
            description += f"\n\n{self.guild.motd}"
        return description

    @property
    def embed_players(self) -> str:
        player_strs: list[str] = []
        for player in self.players:
            points = player.points(self.id)
            if points:
                points_str = f" ({points} point{'s' if points > 1 else ''})"
            else:
                points_str = ""
            player_strs.append(f"<@{player.xid}>{points_str}")
        return ", ".join(sorted(player_strs))

    @property
    def embed_footer(self) -> str:
        return f"SpellBot Game ID: #SB{self.id}"

    @property
    def jump_link(self) -> str:
        guild = self.guild_xid
        channel = self.channel_xid
        message = self.message_xid
        return f"https://discordapp.com/channels/{guild}/{channel}/{message}"

    @property
    def format_name(self) -> str:
        return GameFormat(self.format).name.replace("_", " ").title()

    def to_embed(self, dm: bool = False) -> discord.Embed:
        settings = Settings()
        embed = discord.Embed(title=self.embed_title)
        embed.set_thumbnail(url=settings.THUMB_URL)
        embed.description = self.embed_description(dm)
        if self.players:
            embed.add_field(name="Players", value=self.embed_players, inline=False)
        embed.add_field(name="Format", value=self.format_name)
        if self.started_at:
            embed.add_field(name="Started at", value=f"<t:{self.started_at_timestamp}>")
        if self.voice_xid and self.show_links(dm):
            embed.add_field(name="Voice Channel", value=f"<#{self.voice_xid}>")
        embed.set_footer(text=self.embed_footer)
        embed.color = discord.Color(settings.EMBED_COLOR)
        return embed

    def to_dict(self) -> dict:
        return {
            "id": self.id,
            "created_at": self.created_at,
            "updated_at": self.updated_at,
            "started_at": self.started_at,
            "guild_xid": self.guild_xid,
            "channel_xid": self.channel_xid,
            "message_xid": self.message_xid,
            "voice_xid": self.voice_xid,
            "seats": self.seats,
            "status": self.status,
            "format": self.format,
            "spelltable_link": self.spelltable_link,
            "voice_invite_link": self.voice_invite_link,
        }
