"""Follow repository — typed CRUD for polymorphic user follows.

All persistence is delegated to :class:`BaseRepository` methods — no raw
SQL or SQLAlchemy imports.  Timestamps are auto-injected by the base class.
"""
from __future__ import annotations

import logging
from typing import Any

from repo_core import BaseRepository
from repo_core.events.hooks import hook
from repo_core.domains.follow.model import Follow, FollowTargetType
from repo_core.domains.follow.validator import build_follow_pipeline

logger = logging.getLogger(__name__)


class FollowRepository(BaseRepository[Follow]):
    """Repository for persisting and querying user follows.

    Wraps ``BaseRepository`` with typed helpers for the two most common
    access patterns — per-user (``list_following``) and per-target
    (``list_followers``) — plus idempotent ``follow()`` / ``unfollow()``
    that transparently handle the unique constraint.

    Usage::

        repo = FollowRepository(model=Follow)
        async with repo:
            f = await repo.follow(user_id=42, target_type=FollowTargetType.STORY, target_id="s-1")
            assert await repo.is_following(42, FollowTargetType.STORY, "s-1") is True
            await repo.unfollow(user_id=42, target_type=FollowTargetType.STORY, target_id="s-1")
    """

    TABLE = "follows"
    _pipeline = build_follow_pipeline()

    # ------------------------------------------------------------------
    # Lifecycle hooks
    # ------------------------------------------------------------------

    @hook("before_create")
    async def _validate(self, data: dict[str, Any]) -> None:
        """Validate target_type and target_id before persisting."""
        data["target_type"] = await self._pipeline._validators[0].validate(data.get("target_type", ""))
        data["target_id"] = await self._pipeline._validators[1].validate(data.get("target_id", ""))

    @hook("after_create")
    async def _log_created(self, instance: Follow) -> None:
        logger.info(
            "Follow created: user_id=%s %s:%s",
            instance.user_id,
            instance.target_type,
            instance.target_id,
        )

    # ------------------------------------------------------------------
    # Command helpers
    # ------------------------------------------------------------------

    async def follow(
        self, *, user_id: int, target_type: FollowTargetType, target_id: str
    ) -> Follow:
        """Follow a target — idempotent.

        If the user already follows this target the existing row is
        returned; otherwise a new row is inserted.

        Returns:
            The (new or existing) Follow instance.
        """
        existing = await self.first(
            self.TABLE,
            {
                "user_id": user_id,
                "target_type": target_type.value,
                "target_id": target_id,
            },
        )
        if existing is not None:
            logger.debug("Follow already exists: user=%s %s:%s",
                         user_id, target_type.value, target_id)
            return existing

        return await self.insert_one(
            self.TABLE,
            {
                "user_id": user_id,
                "target_type": target_type.value,
                "target_id": target_id,
            },
            returning=["*"],
        )

    async def unfollow(
        self, *, user_id: int, target_type: FollowTargetType, target_id: str
    ) -> bool:
        """Remove a follow.  Returns ``True`` if a row was deleted."""
        deleted = await self.remove(
            self.TABLE,
            {
                "user_id": user_id,
                "target_type": target_type.value,
                "target_id": target_id,
            },
        )
        if deleted:
            logger.info("Follow removed: user=%s %s:%s",
                        user_id, target_type.value, target_id)
        return deleted > 0

    # ------------------------------------------------------------------
    # Query helpers
    # ------------------------------------------------------------------

    async def is_following(
        self, user_id: int, target_type: FollowTargetType, target_id: str
    ) -> bool:
        """Return ``True`` if *user_id* currently follows the given target."""
        row = await self.first(
            self.TABLE,
            {
                "user_id": user_id,
                "target_type": target_type.value,
                "target_id": target_id,
            },
        )
        return row is not None

    async def list_following(
        self,
        user_id: int,
        *,
        target_type: FollowTargetType | None = None,
        limit: int | None = None,
        offset: int | None = None,
    ) -> list[Follow]:
        """Return everything a user follows, optionally filtered by type.

        Usage::

            stories = await repo.list_following(42, target_type=FollowTargetType.STORY)
            all = await repo.list_following(42)
        """
        conditions: dict[str, Any] = {"user_id": user_id}
        if target_type is not None:
            conditions["target_type"] = target_type.value
        return await self.find(
            self.TABLE,
            conditions,
            limit=limit,
            offset=offset,
            order_by=["-created_at"],
        )

    async def list_followers(
        self,
        target_type: FollowTargetType,
        target_id: str,
        *,
        limit: int | None = None,
        offset: int | None = None,
    ) -> list[Follow]:
        """Return all users following a specific target.

        Usage::

            followers = await repo.list_followers(FollowTargetType.STORY, "story-abc")
        """
        return await self.find(
            self.TABLE,
            {"target_type": target_type.value, "target_id": target_id},
            limit=limit,
            offset=offset,
            order_by=["-created_at"],
        )

    async def count_followers(self, target_type: FollowTargetType, target_id: str) -> int:
        """Return the number of users following a specific target.

        Usage::

            count = await repo.count_followers(FollowTargetType.STORY, "story-abc")
        """
        return await self.count(
            self.TABLE,
            {"target_type": target_type.value, "target_id": target_id},
        )
