"""
Deep coverage tests for local_deep_research/news/core/storage.py

The module contains only abstract base classes (ABCs).  We test them by:
1. Verifying that instantiating them directly raises TypeError.
2. Building minimal concrete subclasses (backed by in-memory dicts) to test
   the only concrete method: BaseStorage.generate_id().
3. Exercising every abstract-method slot via the concrete implementations so
   that the dispatcher logic inside the ABC framework is traversed.

No SQLite database is used – all storage is in-memory Python dicts.
"""

import uuid
from datetime import datetime, timezone
from typing import Dict, List

import pytest

from local_deep_research.news.core.storage import (
    BaseStorage,
    CardStorage,
    PreferenceStorage,
    RatingStorage,
    SubscriptionStorage,
)


# ---------------------------------------------------------------------------
# In-memory concrete helpers
# ---------------------------------------------------------------------------


class _DictStore(BaseStorage):
    """Minimal in-memory BaseStorage."""

    def __init__(self):
        self._data: Dict[str, Dict] = {}

    def create(self, data):
        rid = self.generate_id()
        self._data[rid] = dict(data)
        return rid

    def get(self, id):
        return self._data.get(id)

    def update(self, id, data):
        if id not in self._data:
            return False
        self._data[id].update(data)
        return True

    def delete(self, id):
        if id not in self._data:
            return False
        del self._data[id]
        return True

    def list(self, filters=None, limit=100, offset=0):
        items = list(self._data.values())
        return items[offset : offset + limit]


class _InMemoryCardStorage(CardStorage):
    """Concrete CardStorage backed by in-memory dicts."""

    def __init__(self):
        self._data: Dict[str, Dict] = {}
        self._versions: Dict[str, List] = {}

    def create(self, data):
        rid = self.generate_id()
        self._data[rid] = dict(data)
        self._versions[rid] = []
        return rid

    def get(self, id):
        return self._data.get(id)

    def update(self, id, data):
        if id not in self._data:
            return False
        self._data[id].update(data)
        return True

    def delete(self, id):
        if id not in self._data:
            return False
        del self._data[id]
        del self._versions[id]
        return True

    def list(self, filters=None, limit=100, offset=0):
        return list(self._data.values())[offset : offset + limit]

    def get_by_user(self, user_id, limit=50, offset=0):
        return [v for v in self._data.values() if v.get("user_id") == user_id][
            offset : offset + limit
        ]

    def get_latest_version(self, card_id):
        versions = self._versions.get(card_id, [])
        return versions[-1] if versions else None

    def add_version(self, card_id, version_data):
        vid = self.generate_id()
        self._versions.setdefault(card_id, []).append(
            {"id": vid, **version_data}
        )
        return vid

    def update_latest_info(self, card_id, version_data):
        if card_id not in self._data:
            return False
        self._data[card_id]["latest"] = version_data
        return True

    def archive_card(self, card_id):
        if card_id not in self._data:
            return False
        self._data[card_id]["archived"] = True
        return True

    def pin_card(self, card_id, pinned=True):
        if card_id not in self._data:
            return False
        self._data[card_id]["pinned"] = pinned
        return True


class _InMemorySubscriptionStorage(SubscriptionStorage):
    def __init__(self):
        self._data: Dict[str, Dict] = {}

    def create(self, data):
        rid = self.generate_id()
        self._data[rid] = dict(data)
        return rid

    def get(self, id):
        return self._data.get(id)

    def update(self, id, data):
        if id not in self._data:
            return False
        self._data[id].update(data)
        return True

    def delete(self, id):
        if id not in self._data:
            return False
        del self._data[id]
        return True

    def list(self, filters=None, limit=100, offset=0):
        return list(self._data.values())[offset : offset + limit]

    def get_active_subscriptions(self, user_id=None):
        return [
            v
            for v in self._data.values()
            if v.get("status") == "active"
            and (user_id is None or v.get("user_id") == user_id)
        ]

    def get_due_subscriptions(self, limit=100):
        return [v for v in self._data.values() if v.get("due", False)][:limit]

    def update_refresh_time(self, subscription_id, last_refresh, next_refresh):
        if subscription_id not in self._data:
            return False
        self._data[subscription_id].update(
            {"last_refresh": last_refresh, "next_refresh": next_refresh}
        )
        return True

    def increment_stats(self, subscription_id, results_count):
        if subscription_id not in self._data:
            return False
        rec = self._data[subscription_id]
        rec["refresh_count"] = rec.get("refresh_count", 0) + 1
        rec["results_count"] = rec.get("results_count", 0) + results_count
        return True

    def pause_subscription(self, subscription_id):
        if subscription_id not in self._data:
            return False
        self._data[subscription_id]["status"] = "paused"
        return True

    def resume_subscription(self, subscription_id):
        if subscription_id not in self._data:
            return False
        self._data[subscription_id]["status"] = "active"
        return True

    def expire_subscription(self, subscription_id):
        if subscription_id not in self._data:
            return False
        self._data[subscription_id]["status"] = "expired"
        return True


class _InMemoryRatingStorage(RatingStorage):
    def __init__(self):
        self._data: Dict[str, Dict] = {}

    def create(self, data):
        rid = self.generate_id()
        self._data[rid] = dict(data)
        return rid

    def get(self, id):
        return self._data.get(id)

    def update(self, id, data):
        if id not in self._data:
            return False
        self._data[id].update(data)
        return True

    def delete(self, id):
        if id not in self._data:
            return False
        del self._data[id]
        return True

    def list(self, filters=None, limit=100, offset=0):
        return list(self._data.values())[offset : offset + limit]

    def get_user_rating(self, user_id, item_id, rating_type):
        for v in self._data.values():
            if (
                v.get("user_id") == user_id
                and v.get("item_id") == item_id
                and v.get("rating_type") == rating_type
            ):
                return v
        return None

    def upsert_rating(
        self, user_id, item_id, rating_type, rating_value, item_type="card"
    ):
        existing = self.get_user_rating(user_id, item_id, rating_type)
        if existing:
            existing["rating_value"] = rating_value
            return existing.get("id", "existing")
        rid = self.generate_id()
        self._data[rid] = {
            "id": rid,
            "user_id": user_id,
            "item_id": item_id,
            "rating_type": rating_type,
            "rating_value": rating_value,
            "item_type": item_type,
        }
        return rid

    def get_ratings_summary(self, item_id, item_type="card"):
        votes = {"up": 0, "down": 0}
        for v in self._data.values():
            if v.get("item_id") == item_id and v.get("item_type") == item_type:
                votes[v.get("rating_value", "up")] = (
                    votes.get(v.get("rating_value", "up"), 0) + 1
                )
        return votes

    def get_user_ratings(self, user_id, rating_type=None, limit=100):
        results = [
            v
            for v in self._data.values()
            if v.get("user_id") == user_id
            and (rating_type is None or v.get("rating_type") == rating_type)
        ]
        return results[:limit]


# ---------------------------------------------------------------------------
# Tests: abstract classes cannot be instantiated
# ---------------------------------------------------------------------------


class TestAbstractInstantiation:
    def test_base_storage_cannot_be_instantiated(self):
        with pytest.raises(TypeError):
            BaseStorage()

    def test_card_storage_cannot_be_instantiated(self):
        with pytest.raises(TypeError):
            CardStorage()

    def test_subscription_storage_cannot_be_instantiated(self):
        with pytest.raises(TypeError):
            SubscriptionStorage()

    def test_rating_storage_cannot_be_instantiated(self):
        with pytest.raises(TypeError):
            RatingStorage()

    def test_preference_storage_cannot_be_instantiated(self):
        with pytest.raises(TypeError):
            PreferenceStorage()


# ---------------------------------------------------------------------------
# Tests: BaseStorage.generate_id
# ---------------------------------------------------------------------------


class TestGenerateId:
    def test_generate_id_returns_valid_uuid(self):
        store = _DictStore()
        id_str = store.generate_id()
        parsed = uuid.UUID(id_str)  # raises ValueError if invalid
        assert str(parsed) == id_str

    def test_generate_id_is_unique_per_call(self):
        store = _DictStore()
        ids = {store.generate_id() for _ in range(50)}
        assert len(ids) == 50


# ---------------------------------------------------------------------------
# Tests: in-memory CardStorage
# ---------------------------------------------------------------------------


class TestInMemoryCardStorage:
    def setup_method(self):
        self.storage = _InMemoryCardStorage()

    def test_create_and_get(self):
        card_id = self.storage.create({"user_id": "u1", "topic": "AI"})
        card = self.storage.get(card_id)
        assert card["topic"] == "AI"

    def test_get_by_user(self):
        self.storage.create({"user_id": "u1", "topic": "T1"})
        self.storage.create({"user_id": "u2", "topic": "T2"})
        cards = self.storage.get_by_user("u1")
        assert len(cards) == 1
        assert cards[0]["topic"] == "T1"

    def test_add_version_and_get_latest(self):
        cid = self.storage.create({"user_id": "u1", "topic": "T"})
        self.storage.add_version(cid, {"content": "v1"})
        self.storage.add_version(cid, {"content": "v2"})
        latest = self.storage.get_latest_version(cid)
        assert latest["content"] == "v2"

    def test_archive_card(self):
        cid = self.storage.create({"user_id": "u1", "topic": "T"})
        result = self.storage.archive_card(cid)
        assert result is True
        assert self.storage.get(cid)["archived"] is True

    def test_pin_card(self):
        cid = self.storage.create({"user_id": "u1", "topic": "T"})
        assert self.storage.pin_card(cid, pinned=True) is True
        assert self.storage.get(cid)["pinned"] is True
        assert self.storage.pin_card(cid, pinned=False) is True
        assert self.storage.get(cid)["pinned"] is False

    def test_delete_card(self):
        cid = self.storage.create({"user_id": "u1", "topic": "T"})
        assert self.storage.delete(cid) is True
        assert self.storage.get(cid) is None

    def test_delete_nonexistent_returns_false(self):
        assert self.storage.delete("does-not-exist") is False


# ---------------------------------------------------------------------------
# Tests: in-memory SubscriptionStorage
# ---------------------------------------------------------------------------


class TestInMemorySubscriptionStorage:
    def setup_method(self):
        self.storage = _InMemorySubscriptionStorage()

    def test_get_active_subscriptions(self):
        self.storage.create({"user_id": "u1", "status": "active"})
        self.storage.create({"user_id": "u1", "status": "paused"})
        active = self.storage.get_active_subscriptions("u1")
        assert len(active) == 1
        assert active[0]["status"] == "active"

    def test_pause_and_resume(self):
        sid = self.storage.create({"user_id": "u1", "status": "active"})
        self.storage.pause_subscription(sid)
        assert self.storage.get(sid)["status"] == "paused"
        self.storage.resume_subscription(sid)
        assert self.storage.get(sid)["status"] == "active"

    def test_expire_subscription(self):
        sid = self.storage.create({"user_id": "u1", "status": "active"})
        self.storage.expire_subscription(sid)
        assert self.storage.get(sid)["status"] == "expired"

    def test_increment_stats(self):
        sid = self.storage.create({"user_id": "u1", "status": "active"})
        self.storage.increment_stats(sid, 10)
        self.storage.increment_stats(sid, 5)
        rec = self.storage.get(sid)
        assert rec["refresh_count"] == 2
        assert rec["results_count"] == 15

    def test_update_refresh_time(self):
        sid = self.storage.create({"user_id": "u1", "status": "active"})
        now = datetime.now(timezone.utc)
        later = datetime.now(timezone.utc)
        result = self.storage.update_refresh_time(sid, now, later)
        assert result is True
        rec = self.storage.get(sid)
        assert rec["last_refresh"] == now

    def test_get_due_subscriptions(self):
        self.storage.create({"due": True})
        self.storage.create({"due": False})
        due = self.storage.get_due_subscriptions()
        assert len(due) == 1


# ---------------------------------------------------------------------------
# Tests: in-memory RatingStorage
# ---------------------------------------------------------------------------


class TestInMemoryRatingStorage:
    def setup_method(self):
        self.storage = _InMemoryRatingStorage()

    def test_upsert_creates_new_rating(self):
        rid = self.storage.upsert_rating("u1", "card1", "vote", "up")
        assert self.storage.get(rid) is not None

    def test_upsert_updates_existing_rating(self):
        self.storage.upsert_rating("u1", "card1", "vote", "up")
        self.storage.upsert_rating("u1", "card1", "vote", "down")
        rating = self.storage.get_user_rating("u1", "card1", "vote")
        assert rating["rating_value"] == "down"

    def test_get_ratings_summary(self):
        self.storage.upsert_rating("u1", "card1", "vote", "up")
        self.storage.upsert_rating("u2", "card1", "vote", "up")
        summary = self.storage.get_ratings_summary("card1")
        assert summary.get("up", 0) == 2

    def test_get_user_ratings_with_type_filter(self):
        self.storage.upsert_rating("u1", "card1", "vote", "up")
        self.storage.upsert_rating("u1", "card2", "bookmark", "saved")
        votes = self.storage.get_user_ratings("u1", rating_type="vote")
        assert len(votes) == 1
