from __future__ import annotations

import calendar
import re
from datetime import datetime, timedelta
from typing import Any

NUMBER_WORDS = {
    "a": 1,
    "an": 1,
    "one": 1,
    "two": 2,
    "three": 3,
    "four": 4,
    "five": 5,
    "six": 6,
    "seven": 7,
    "eight": 8,
    "nine": 9,
    "ten": 10,
    "eleven": 11,
    "twelve": 12,
}


def _parse_dt(scene: dict[str, Any]) -> datetime:
    date_text = str(scene.get("date") or scene.get("start_date") or "2026-01-01")
    time_text = str(scene.get("time") or scene.get("start_time") or "09:00")
    try:
        return datetime.strptime(f"{date_text} {time_text}", "%Y-%m-%d %H:%M")
    except ValueError:
        return datetime(2026, 1, 1, 9, 0)


def _number(value: str) -> int | None:
    value = value.strip().lower()
    if value.isdigit():
        return int(value)
    return NUMBER_WORDS.get(value)


def _add_months(dt: datetime, months: int) -> datetime:
    total = (dt.year * 12 + (dt.month - 1)) + months
    year, month0 = divmod(total, 12)
    month = month0 + 1
    day = min(dt.day, calendar.monthrange(year, month)[1])
    return dt.replace(year=year, month=month, day=day)


def _add_years(dt: datetime, years: int) -> datetime:
    year = dt.year + years
    day = min(dt.day, calendar.monthrange(year, dt.month)[1])
    return dt.replace(year=year, day=day)


def _set_daypart(dt: datetime, hour: int, always_next_day: bool = False) -> datetime:
    candidate = dt.replace(hour=hour, minute=0, second=0, microsecond=0)
    if always_next_day or candidate <= dt:
        candidate += timedelta(days=1)
    return candidate


def ensure_story_clock(scene: dict[str, Any]) -> dict[str, Any]:
    scene = dict(scene or {})
    start_date = str(scene.get("start_date") or scene.get("date") or "2026-08-13")
    start_time = str(scene.get("start_time") or scene.get("time") or "19:00")
    scene.setdefault("start_date", start_date)
    scene.setdefault("start_time", start_time)
    scene.setdefault("date", start_date)
    scene.setdefault("time", start_time)
    scene.setdefault("story_started", False)
    scene.setdefault("turn_index", 0)
    scene.setdefault("last_advance_reason", "Story has not started yet.")
    scene.setdefault("time_policy", "Conservative story clock: explicit time skips win; ordinary turns advance a little.")
    return scene


def advance_for_user_turn(scene: dict[str, Any], user_text: str) -> tuple[dict[str, Any], str]:
    """Advance story time before a canonical user turn.

    First turn anchors exactly to the configured story start. Later turns use explicit
    user time cues where possible; otherwise they advance conservatively by two minutes.
    Rerolls should never call this function.
    """
    scene = ensure_story_clock(scene)
    text = re.sub(r"\s+", " ", (user_text or "").strip().lower())

    if not scene.get("story_started"):
        scene["story_started"] = True
        scene["date"] = scene["start_date"]
        scene["time"] = scene["start_time"]
        scene["turn_index"] = 1
        reason = "First sent message anchored to the chosen story start."
        scene["last_advance_reason"] = reason
        return scene, reason

    dt = _parse_dt(scene)
    reason = "Ordinary scene progression (+2 minutes)."
    advanced = False

    # Highly explicit daypart phrases first.
    dayparts = [
        (r"\b(?:the\s+)?next morning\b", 8, True, "User time skip: next morning."),
        (r"\b(?:the\s+)?next afternoon\b", 15, True, "User time skip: next afternoon."),
        (r"\b(?:the\s+)?next evening\b", 19, True, "User time skip: next evening."),
        (r"\b(?:the\s+)?next night\b", 22, True, "User time skip: next night."),
        (r"\blater that morning\b", 10, False, "User time skip: later that morning."),
        (r"\blater that afternoon\b", 15, False, "User time skip: later that afternoon."),
        (r"\blater that evening\b", 19, False, "User time skip: later that evening."),
        (r"\blater that night\b", 22, False, "User time skip: later that night."),
    ]
    for pattern, hour, next_day, why in dayparts:
        if re.search(pattern, text):
            dt = _set_daypart(dt, hour, always_next_day=next_day)
            reason = why
            advanced = True
            break

    if not advanced and re.search(r"\b(?:the\s+)?next day\b", text):
        dt += timedelta(days=1)
        reason = "User time skip: next day."
        advanced = True

    if not advanced and re.search(r"\bhalf an hour later\b", text):
        dt += timedelta(minutes=30)
        reason = "User time skip: half an hour later."
        advanced = True

    # "three years later", "2 hours later", "an hour later", etc.
    if not advanced:
        m = re.search(
            r"\b(\d+|a|an|one|two|three|four|five|six|seven|eight|nine|ten|eleven|twelve)\s+"
            r"(minute|minutes|hour|hours|day|days|week|weeks|month|months|year|years)\s+later\b",
            text,
        )
        if m:
            amount = _number(m.group(1)) or 1
            unit = m.group(2)
            if unit.startswith("minute"):
                dt += timedelta(minutes=amount)
            elif unit.startswith("hour"):
                dt += timedelta(hours=amount)
            elif unit.startswith("day"):
                dt += timedelta(days=amount)
            elif unit.startswith("week"):
                dt += timedelta(weeks=amount)
            elif unit.startswith("month"):
                dt = _add_months(dt, amount)
            elif unit.startswith("year"):
                dt = _add_years(dt, amount)
            reason = f"User time skip: {m.group(0)}."
            advanced = True

    # "after three hours" / "after 2 days".
    if not advanced:
        m = re.search(
            r"\bafter\s+(\d+|a|an|one|two|three|four|five|six|seven|eight|nine|ten|eleven|twelve)\s+"
            r"(minute|minutes|hour|hours|day|days|week|weeks|month|months|year|years)\b",
            text,
        )
        if m:
            amount = _number(m.group(1)) or 1
            unit = m.group(2)
            if unit.startswith("minute"):
                dt += timedelta(minutes=amount)
            elif unit.startswith("hour"):
                dt += timedelta(hours=amount)
            elif unit.startswith("day"):
                dt += timedelta(days=amount)
            elif unit.startswith("week"):
                dt += timedelta(weeks=amount)
            elif unit.startswith("month"):
                dt = _add_months(dt, amount)
            elif unit.startswith("year"):
                dt = _add_years(dt, amount)
            reason = f"User time skip: {m.group(0)}."
            advanced = True

    if not advanced:
        dt += timedelta(minutes=2)

    scene["date"] = dt.strftime("%Y-%m-%d")
    scene["time"] = dt.strftime("%H:%M")
    scene["turn_index"] = int(scene.get("turn_index") or 0) + 1
    scene["last_advance_reason"] = reason
    return scene, reason
