"""LRU cache wrapper with per-ticker invalidation.

Provides an in-memory cache (max 1000 entries) keyed on
(ticker, start_date, end_date, indicator_type). Supports selective
invalidation of all entries for a given ticker — needed when new
OHLCV data is ingested.
"""

from __future__ import annotations

from collections import OrderedDict
from typing import Any, Hashable

CacheKey = tuple[str, Any, Any, str]  # (ticker, start_date, end_date, indicator_type)

_DEFAULT_MAX_SIZE = 1000


class LRUCache:
    """Simple LRU cache backed by an ``OrderedDict``.

    Parameters
    ----------
    max_size:
        Maximum number of entries. When exceeded the least-recently-used
        entry is evicted. Defaults to 1000.
    """

    def __init__(self, max_size: int = _DEFAULT_MAX_SIZE) -> None:
        self._max_size = max_size
        self._store: OrderedDict[Hashable, Any] = OrderedDict()

    # ------------------------------------------------------------------
    # Public API
    # ------------------------------------------------------------------

    def get(self, key: CacheKey) -> Any | None:
        """Return cached value or ``None`` on miss.

        Accessing an entry promotes it to most-recently-used.
        """
        if key not in self._store:
            return None
        self._store.move_to_end(key)
        return self._store[key]

    def set(self, key: CacheKey, value: Any) -> None:
        """Insert or update *key* with *value*.

        If the cache is full the least-recently-used entry is evicted.
        """
        if key in self._store:
            self._store.move_to_end(key)
            self._store[key] = value
            return
        if len(self._store) >= self._max_size:
            self._store.popitem(last=False)  # evict LRU
        self._store[key] = value

    def invalidate_ticker(self, ticker: str) -> int:
        """Remove all entries whose first key element matches *ticker*.

        Returns the number of evicted entries.
        """
        keys_to_remove = [k for k in self._store if k[0] == ticker]
        for k in keys_to_remove:
            del self._store[k]
        return len(keys_to_remove)

    def clear(self) -> None:
        """Remove all entries."""
        self._store.clear()

    @property
    def size(self) -> int:
        """Current number of cached entries."""
        return len(self._store)

    @property
    def max_size(self) -> int:
        """Maximum capacity."""
        return self._max_size


# Module-level singleton used by the indicator service.
indicator_cache = LRUCache()
