import datetime

from rollbot import as_command, initialize_data, RollbotFailure
from rollbot.injection import Subcommand, Data, ChannelAdmin, OriginAdmin, Channel, Lazy


@initialize_data
class DnDSession:
    session_time: float | None = None

    @staticmethod
    def fmt(sesh):
        if (
            sesh is None
            or sesh.session_time is None
            or sesh.session_time < datetime.datetime.now().timestamp()
        ):
            return "No upcoming session found."
        return "Next DnD session scheduled for " + datetime.datetime.fromtimestamp(
            sesh.session_time
        ).strftime("%b %d, %Y, at %I:%M %p")


WEEKDAYS = {
    "monday": 0,
    "tuesday": 1,
    "wednesday": 2,
    "thursday": 3,
    "friday": 4,
    "saturday": 5,
    "sunday": 6,
}


def pop_arg(text):
    if text is None:
        return None, None
    text = text.strip()
    parts = text.split(maxsplit=1)
    if len(parts) == 1:
        return parts[0], None
    return parts[0], parts[1]


def parse_datetime(arg_text):
    if arg_text is None:
        return

    day_date, arg_text = pop_arg(arg_text)
    day_date = day_date.lower()
    if day_date in WEEKDAYS:
        today = datetime.date.today()
        day_diff = WEEKDAYS[day_date] - today.weekday()
        next_day = today + datetime.timedelta((day_diff) % 7)
        month = next_day.month
        day = next_day.day
        year = next_day.year
    else:
        date_parts = day_date.split("/")
        if len(date_parts) == 3:
            month = date_parts[0]
            day = date_parts[1]
            year = date_parts[2]
        elif len(date_parts) == 2:
            month = date_parts[0]
            day = date_parts[1]
            year = datetime.date.today().year
        else:
            return

    hour = 19
    minute = 45

    if arg_text is None:
        return datetime.datetime(
            int(year), int(month), int(day), int(hour), int(minute)
        )

    time, arg_text = pop_arg(arg_text)
    time_parts = time.split(":")
    hour = time_parts[0]
    if len(time_parts) > 1:
        minute = time_parts[1]

    ampm, arg_text = pop_arg(arg_text)

    if ampm is None:
        return datetime.datetime(
            int(year), int(month), int(day), int(hour), int(minute)
        )

    if ampm.lower() == "pm":
        hour = int(hour) + 12

    return datetime.datetime(int(year), int(month), int(day), int(hour), int(minute))


SUBCOMMANDS = ("next", "view", "late", "cancel")
VALID_DETAIL = "Valid subcommands are: next, view, late, and cancel"
NEEDS_ADMIN = ("next", "cancel")


@as_command
async def session(
    subc: Subcommand,
    get_sesh: Lazy(Data(DnDSession).For(Channel)),
    store: Data(DnDSession),
    channel_admin: ChannelAdmin,
    origin_admin: OriginAdmin,
    channel: Channel,
):
    if subc is None:
        RollbotFailure.MISSING_SUBCOMMAND.raise_exc(detail=VALID_DETAIL)

    if subc.name not in SUBCOMMANDS:
        RollbotFailure.INVALID_SUBCOMMAND.raise_exc(detail=VALID_DETAIL)

    if not (channel_admin or origin_admin) and subc.name in NEEDS_ADMIN:
        RollbotFailure.PERMISSIONS.raise_exc()

    sesh = await get_sesh()

    if subc.name == "view":
        return DnDSession.fmt(sesh)

    if subc.name == "next":
        dt = parse_datetime(subc.args)
        if dt is None:
            RollbotFailure.INVALID_ARGUMENTS.raise_exc(
                detail="Could not process argument as a date/time."
            )
        if dt < datetime.datetime.now():
            RollbotFailure.INVALID_ARGUMENTS.raise_exc(
                detail="Cannot schedule sessions in the past."
            )

        sesh.session_time = dt.timestamp()
        await store.save(channel, sesh)
        return DnDSession.fmt(sesh)

    if sesh is None or sesh.session_time is None:
        return "No upcoming session found."

    if subc.name == "late":
        now = datetime.datetime.now().timestamp()
        if sesh.session_time >= now:
            return "You're not late yet!"

        late = (now - sesh.session_time) / 60
        return f"You're running {late:.2f} minutes late..."

    if subc.name == "cancel":
        sesh.session_time = None
        await store.save(channel, sesh)
        return "Sigh, session cancelled..."
