# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========

from datetime import datetime, timedelta, timezone

from mirage.cache.index.config import (IndexConfig, IndexEntry, ListResult,
                                       LookupResult, LookupStatus)
from mirage.cache.index.store import IndexCacheStore
from mirage.cache.lock import KeyLockMixin


class RAMIndexCacheStore(IndexCacheStore, KeyLockMixin):
    """In-memory index cache using plain dicts + asyncio locks."""

    def __init__(self, ttl: float = 600) -> None:
        super().__init__()
        self._ttl = ttl
        self._entries: dict[str, IndexEntry] = {}
        self._children: dict[str, list[str]] = {}
        self._expiry: dict[str, datetime] = {}

    @classmethod
    def from_config(cls, config: IndexConfig) -> "RAMIndexCacheStore":
        return cls(ttl=config.ttl)

    async def get(self, resource_path: str) -> LookupResult:
        entry = self._entries.get(resource_path)
        if entry is None:
            return LookupResult(status=LookupStatus.NOT_FOUND)
        return LookupResult(entry=entry)

    async def put(self, resource_path: str, entry: IndexEntry) -> None:
        async with self._lock_for(resource_path):
            if not entry.index_time:
                entry = entry.model_copy(
                    update={
                        "index_time": datetime.now(timezone.utc).isoformat()
                    })
            self._entries[resource_path] = entry

    async def list_dir(self, resource_path: str) -> ListResult:
        exp = self._expiry.get(resource_path)
        if exp is None:
            return ListResult(status=LookupStatus.NOT_FOUND)
        if datetime.now(timezone.utc) > exp:
            return ListResult(status=LookupStatus.EXPIRED)
        children = self._children.get(resource_path)
        return ListResult(entries=children or [])

    async def set_dir(
        self,
        resource_path: str,
        entries: list[tuple[str, IndexEntry]],
        expired_at: datetime | None = None,
    ) -> None:
        async with self._lock_for(resource_path):
            now = datetime.now(timezone.utc)
            exp = expired_at or (now + timedelta(seconds=self._ttl))
            now_iso = now.isoformat()
            prefix = "/" if resource_path == "/" else resource_path + "/"
            child_keys: list[str] = []
            for name, entry in entries:
                full_path = prefix + name
                if not entry.index_time:
                    entry = entry.model_copy(update={"index_time": now_iso})
                self._entries[full_path] = entry
                child_keys.append(full_path)
            self._children[resource_path] = child_keys
            self._expiry[resource_path] = exp

    async def invalidate_dir(self, resource_path: str) -> None:
        self._expiry.pop(resource_path, None)
        self._children.pop(resource_path, None)

    async def clear(self) -> None:
        self._entries.clear()
        self._children.clear()
        self._expiry.clear()
        self._clear_locks()
