# retoor <retoor@molodetz.nl>

from .core import TTLCache, datetime, db, timedelta, timezone


def record_activity(user_uid: str, action: str) -> int:
    if not user_uid or not action or "user_activity" not in db.tables:
        return 0
    now = datetime.now(timezone.utc).isoformat()
    with db:
        db.query(
            "INSERT INTO user_activity (user_uid, action, count, first_at, last_at) "
            "VALUES (:u, :a, 1, :now, :now) "
            "ON CONFLICT(user_uid, action) DO UPDATE SET "
            "count = count + 1, last_at = excluded.last_at",
            u=user_uid,
            a=action,
            now=now,
        )
    rows = list(
        db.query(
            "SELECT count FROM user_activity WHERE user_uid = :u AND action = :a",
            u=user_uid,
            a=action,
        )
    )
    return int(rows[0]["count"]) if rows else 0


def record_unique_activity(user_uid: str, action: str, target: str) -> int | None:
    if not user_uid or not action or "user_activity_seen" not in db.tables:
        return None
    now = datetime.now(timezone.utc).isoformat()
    with db:
        db.query(
            "INSERT OR IGNORE INTO user_activity_seen "
            "(user_uid, action, target, created_at) VALUES (:u, :a, :t, :now)",
            u=user_uid,
            a=action,
            t=str(target),
            now=now,
        )
        changed = list(db.query("SELECT changes() AS c"))
    if not changed or not changed[0]["c"]:
        return None
    rows = list(
        db.query(
            "SELECT COUNT(*) AS c FROM user_activity_seen "
            "WHERE user_uid = :u AND action = :a",
            u=user_uid,
            a=action,
        )
    )
    return int(rows[0]["c"]) if rows else 0


def get_user_activity(user_uid: str) -> dict:
    if not user_uid or "user_activity" not in db.tables:
        return {}
    rows = db.query(
        "SELECT action, count FROM user_activity WHERE user_uid = :u",
        u=user_uid,
    )
    return {row["action"]: int(row["count"]) for row in rows}


_activity_cache = TTLCache(ttl=300, max_size=1000)


_ACTIVITY_TABLES = ("posts", "comments", "gists", "projects")


def get_activity_calendar(user_uid: str) -> dict:
    cached = _activity_cache.get(user_uid)
    if cached is not None:
        return cached
    sources = [table for table in _ACTIVITY_TABLES if table in db.tables]
    calendar: dict[str, int] = {}
    if sources:
        cutoff = (datetime.now(timezone.utc) - timedelta(days=364)).date().isoformat()
        union = " UNION ALL ".join(
            f"SELECT created_at FROM {table} WHERE user_uid = :u AND deleted_at IS NULL"
            for table in sources
        )
        rows = db.query(
            f"SELECT date(created_at) AS day, COUNT(*) AS c FROM ({union}) WHERE date(created_at) >= :cutoff GROUP BY day",
            u=user_uid,
            cutoff=cutoff,
        )
        for row in rows:
            if row["day"]:
                calendar[row["day"]] = row["c"]
    _activity_cache.set(user_uid, calendar)
    return calendar


def _activity_level(count: int) -> int:
    if count <= 0:
        return 0
    if count == 1:
        return 1
    if count <= 3:
        return 2
    if count <= 6:
        return 3
    return 4


def get_first_activity_date(user_uid: str):
    sources = [table for table in _ACTIVITY_TABLES if table in db.tables]
    if not sources:
        return None
    union = " UNION ALL ".join(
        f"SELECT MIN(created_at) AS m FROM {table} WHERE user_uid = :u AND deleted_at IS NULL"
        for table in sources
    )
    for row in db.query(f"SELECT MIN(m) AS first FROM ({union})", u=user_uid):
        if row["first"]:
            return datetime.fromisoformat(row["first"]).date()
    return None


HEATMAP_WEEKS = 53


def get_activity_heatmap(user_uid: str) -> list:
    calendar = get_activity_calendar(user_uid)
    today = datetime.now(timezone.utc).date()
    week_start = today - timedelta(days=today.weekday())
    start = week_start - timedelta(weeks=HEATMAP_WEEKS - 1)
    first = get_first_activity_date(user_uid)
    if first:
        first_week = first - timedelta(days=first.weekday())
        if first_week > start:
            start = first_week
    weeks = []
    for w in range(HEATMAP_WEEKS):
        week = []
        for d in range(7):
            day = start + timedelta(days=w * 7 + d)
            iso = day.isoformat()
            count = calendar.get(iso, 0)
            week.append({"date": iso, "count": count, "level": _activity_level(count)})
        weeks.append(week)
    return weeks


def get_activity_months(weeks: list) -> list:
    if not weeks:
        return []
    last = len(weeks) - 1
    labels = []
    for i in range(6):
        column = round(i * last / 5)
        iso = weeks[column][0]["date"]
        labels.append(datetime.fromisoformat(iso).strftime("%b"))
    return labels


def get_streaks(user_uid: str) -> dict:
    calendar = get_activity_calendar(user_uid)
    if not calendar:
        return {"current": 0, "longest": 0}
    dates = sorted(datetime.fromisoformat(day).date() for day in calendar)
    date_set = set(dates)
    longest = 1
    run = 1
    for index in range(1, len(dates)):
        if (dates[index] - dates[index - 1]).days == 1:
            run += 1
        else:
            run = 1
        longest = max(longest, run)
    today = datetime.now(timezone.utc).date()
    cursor = today
    if today not in date_set and (today - timedelta(days=1)) in date_set:
        cursor = today - timedelta(days=1)
    current = 0
    while cursor in date_set:
        current += 1
        cursor = cursor - timedelta(days=1)
    return {"current": current, "longest": longest}
