"""In-memory cache for blocklists to avoid repeated database queries.

This module provides a thread-safe cache for blocked users and hashtags,
with functions to load, query, and invalidate the cache.
"""

from __future__ import annotations

import threading
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from sqlalchemy.orm import Session

from sqlalchemy import select

from fenliu.models import BlockedHashtag
from fenliu.models import BlockedUser

# Global cache state
_cache_lock = threading.Lock()
_blocked_users_cache: set[str] | None = None
_blocked_hashtags_cache: set[str] | None = None


def _load_cache(db: Session) -> tuple[set[str], set[str]]:
    """Load blocklists from database.

    Args:
        db: Open SQLAlchemy session.

    Returns:
        Tuple of (blocked_users_set, blocked_hashtags_set), both normalized to lowercase.

    """
    users = db.execute(select(BlockedUser)).scalars().all()
    blocked_users = {u.account_identifier.lower() for u in users}

    tags = db.execute(select(BlockedHashtag)).scalars().all()
    blocked_hashtags = {t.hashtag.lower() for t in tags}

    return blocked_users, blocked_hashtags


def initialize_cache(db: Session) -> None:
    """Eagerly initialize the blocklist cache from database.

    Call this during application startup to pre-load the cache.

    Args:
        db: Open SQLAlchemy session.

    """
    global _blocked_users_cache  # noqa: PLW0603
    global _blocked_hashtags_cache  # noqa: PLW0603

    with _cache_lock:
        users, tags = _load_cache(db)
        _blocked_users_cache = users
        _blocked_hashtags_cache = tags


def get_blocked_users(db: Session) -> set[str]:
    """Get the set of blocked user identifiers (all lowercase).

    Lazily loads from cache; loads from DB if cache is empty.

    Args:
        db: Open SQLAlchemy session.

    Returns:
        Set of blocked user account identifiers (normalized to lowercase).

    """
    global _blocked_users_cache  # noqa: PLW0603

    with _cache_lock:
        if _blocked_users_cache is None:
            users, _ = _load_cache(db)
            _blocked_users_cache = users
        return _blocked_users_cache.copy()


def get_blocked_hashtags(db: Session) -> set[str]:
    """Get the set of blocked hashtags (all lowercase).

    Lazily loads from cache; loads from DB if cache is empty.

    Args:
        db: Open SQLAlchemy session.

    Returns:
        Set of blocked hashtags (normalized to lowercase, no leading #).

    """
    global _blocked_hashtags_cache  # noqa: PLW0603

    with _cache_lock:
        if _blocked_hashtags_cache is None:
            _, tags = _load_cache(db)
            _blocked_hashtags_cache = tags
        return _blocked_hashtags_cache.copy()


def add_blocked_user(identifier: str) -> None:
    """Add a user to the blocklist and update cache.

    Args:
        identifier: User account identifier to block (will be normalized to lowercase).

    """
    global _blocked_users_cache  # noqa: PLW0602

    normalized = identifier.lower()

    with _cache_lock:
        if _blocked_users_cache is not None:
            _blocked_users_cache.add(normalized)


def add_blocked_hashtag(hashtag: str) -> None:
    """Add a hashtag to the blocklist and update cache.

    Args:
        hashtag: Hashtag to block (will be normalized to lowercase, # stripped).

    """
    global _blocked_hashtags_cache  # noqa: PLW0602

    normalized = hashtag.lower().lstrip("#")

    with _cache_lock:
        if _blocked_hashtags_cache is not None:
            _blocked_hashtags_cache.add(normalized)


def remove_blocked_user(identifier: str) -> None:
    """Remove a user from the blocklist cache.

    Args:
        identifier: User account identifier to unblock.

    """
    global _blocked_users_cache  # noqa: PLW0602

    normalized = identifier.lower()

    with _cache_lock:
        if _blocked_users_cache is not None:
            _blocked_users_cache.discard(normalized)


def remove_blocked_hashtag(hashtag: str) -> None:
    """Remove a hashtag from the blocklist cache.

    Args:
        hashtag: Hashtag to unblock (will be normalized to lowercase, # stripped).

    """
    global _blocked_hashtags_cache  # noqa: PLW0602

    normalized = hashtag.lower().lstrip("#")

    with _cache_lock:
        if _blocked_hashtags_cache is not None:
            _blocked_hashtags_cache.discard(normalized)


def invalidate_cache() -> None:
    """Invalidate the entire blocklist cache.

    Call this when blocklists are modified externally (e.g., via API endpoints).
    The cache will be reloaded on next access.

    """
    global _blocked_users_cache  # noqa: PLW0603
    global _blocked_hashtags_cache  # noqa: PLW0603

    with _cache_lock:
        _blocked_users_cache = None
        _blocked_hashtags_cache = None


def is_user_blocked(identifier: str, db: Session) -> bool:
    """Check if a user identifier is in the blocklist.

    Args:
        identifier: User account identifier to check.
        db: Open SQLAlchemy session.

    Returns:
        True if the identifier is blocked, False otherwise.

    """
    blocked = get_blocked_users(db)
    return identifier.lower() in blocked


def is_hashtag_blocked(hashtag: str, db: Session) -> bool:
    """Check if a hashtag is in the blocklist.

    Args:
        hashtag: Hashtag to check (leading # is optional).
        db: Open SQLAlchemy session.

    Returns:
        True if the hashtag is blocked, False otherwise.

    """
    blocked = get_blocked_hashtags(db)
    normalized = hashtag.lower().lstrip("#")
    return normalized in blocked
