from typing import Dict, Optional

from backend.config import (
    get_enterprise_agents,
)
from backend.database.store import Store


class UserStore:
    """
    Wraps any Store implementation and scopes all reads/writes
    to the current user (by user_id).
    """

    def __init__(self, base_store: Store, user_id: str, groups: list[str]):
        self._base = base_store
        self._user = user_id
        self._groups = groups or []

    def _build_published_agent(self, agent: dict) -> Optional[dict]:
        pv = agent.get("published_version")
        if not pv or not isinstance(pv, dict):
            return None
        out = {
            "id": agent["id"],
            "owner": agent["owner"],
            "createdAt": agent.get("createdAt"),
            "lastModified": agent.get("lastModified"),
            "published_at": agent.get("published_at"),
            "publishing_status": agent.get("publishing_status"),
            "shareCount": agent.get("shareCount", 0),
        }
        out.update(pv)
        out["published_version"] = pv
        return out

    def _user_groups(self) -> list[str]:
        return self._groups

    def get_agents_by_owner(self, owner: str):
        # Only allow querying your own agents
        if owner != self._user:
            return []
        return self._base.get_agents_by_owner(owner)

    # --- User-scoped Agent methods ---
    def get_agent(self, agent_id: str):
        """Get agent if user owns it or it's shared with them"""
        agent = self._base.get_agent(agent_id)
        if not agent:
            return None

        # Owner can see everything
        if agent.get("owner") == self._user:
            return agent

        # Check if shared with user
        shares = self._base.get_agent_shares(agent_id)

        shared_with_user = any(s["type"] == "user" and s["principal"] == self._user for s in shares)
        shared_with_group = any(s["type"] == "group" and s["principal"] in self._groups for s in shares)
        if shared_with_user or shared_with_group:
            return self._build_published_agent(agent)

        # Not authorized
        return None

    def get_all_agents(self):
        """
        Union of:
          • agents I own
          • agents shared with me (filtering out drafts)
        """
        owned = self._base.get_agents_by_owner(self._user)

        shared = self._base.get_agents_shared_with(self._user, self._user_groups())

        # For shared agents, show only the published version data
        processed_shared = []
        for agent in shared:
            pa = self._build_published_agent(agent)
            if pa is not None:
                processed_shared.append(pa)

        return owned + processed_shared

    def create_agent(self, agent_dict):
        agent_dict = dict(agent_dict)
        agent_dict["owner"] = self._user
        return self._base.create_agent(agent_dict)

    def update_agent(self, agent_id, update_data):
        # Check if user owns the agent
        agent = self._base.get_agent(agent_id)
        if not agent or agent.get("owner") != self._user:
            return None

        update_data = {k: v for k, v in update_data.items() if k != "owner"}
        return self._base.update_agent(agent_id, update_data)

    def delete_agent(self, agent_id):
        # Check if user owns the agent
        agent = self._base.get_agent(agent_id)
        if not agent or agent.get("owner") != self._user:
            return False
        return self._base.delete_agent(agent_id)

    # ---------------- sharing ---------------------------------------
    def replace_agent_shares(self, agent_id: str, shares: list[dict]):
        # Only the owner may modify sharing
        agent = self._base.get_agent(agent_id)
        if not agent or agent.get("owner") != self._user:
            raise PermissionError("Only the owner can share this agent")
        # Validate shares payload
        for s in shares:
            if "type" not in s or "principal" not in s:
                raise ValueError("Invalid share entry; expected keys: 'type', 'principal'")
            if s["type"] not in ("user", "group"):
                raise ValueError(f"Invalid share type: {s['type']}")
        self._base.replace_agent_shares(agent_id, shares)

    def get_agent_shares(self, agent_id: str) -> list[dict]:
        return self._base.get_agent_shares(agent_id)

    def get_agents_shared_with(self) -> list[dict]:
        """
        Forward to the base-store,
        """
        return self._base.get_agents_shared_with(self._user, self._user_groups())

    def get_share_counts(self, agent_ids: list[str]) -> dict[str, int]:
        # notice: count shares for any agent, not just those I own
        counts = self._base.get_share_counts(agent_ids)
        return {aid: counts.get(aid, 0) for aid in agent_ids}

    # --- User-scoped Conversation methods ---

    def get_conversations(self):
        return self._base.get_conversations_by_user(self._user)

    def get_conversations_ids(self):
        return self._base.get_conversations_ids_by_user(self._user)

    def get_conversations_metadata(self) -> list[dict]:
        return self._base.get_conversations_metadata(self._user)

    def update_conversation(self, conversation_id, conversation_obj):
        conversation_obj = dict(conversation_obj)
        conversation_obj["userId"] = self._user
        return self._base.update_conversation(conversation_id, conversation_obj)

    def delete_conversation(self, conversation_id, permanent_delete: bool = False):
        convs = self._base.get_conversations_ids_by_user(self._user)
        if conversation_id not in convs:
            return False
        return self._base.delete_conversation(conversation_id, permanent_delete)

    def get_conversation(self, conversation_id: str) -> Optional[dict]:
        return self._base.get_conversation(conversation_id, self._user)

    # -------- incremental helpers -------------------------------------
    def ensure_conversation_exists(self, conv_id: str, agent_ids: list[str]):
        self._base.ensure_conversation_exists(conv_id, self._user, agent_ids)

    def append_message(self, conversation_id: str, message: dict):
        self._base.append_message(conversation_id, message)

    def get_message(self, message_id):
        return self._base.get_message(message_id, self._user)

    def get_message_artifacts_meta(self, message_id):
        return self._base.get_message_artifacts_meta(message_id, self._user)

    def update_message(self, message_id: str, updates: dict):
        self._base.update_message(message_id=message_id, updates=updates)

    def update_message_feedback(self, message_id: str, rating: int | None, text: str | None):
        self._base.update_message_feedback(message_id, rating=rating, text=text, by=self._user)

    def clear_message_feedback(self, message_id: str):
        self._base.clear_message_feedback(message_id)

    def append_messages(self, conversation_id: str, messages: list[dict]) -> None:
        self._base.append_messages(conversation_id, messages)

    def update_conversation_meta(self, conversation_id: str, *, title=None, agent_ids=None):
        self._base.update_conversation_meta(conversation_id, title=title, agent_ids=agent_ids)

    # --- Shared across users: message events ---

    def get_message_events(self, message_id: str):
        return self._base.get_message_events(message_id, self._user)

    # --- Preferences (scoped to me) ---

    def get_preferences(self):
        return self._base.get_preferences(self._user)

    def update_preferences(self, prefs: dict):
        return self._base.update_preferences(self._user, prefs)

    # --- Draft-chat helpers ---

    def get_draft_conversation(self, agent_id: str):
        return self._base.get_draft_conversation(agent_id, self._user)

    def upsert_draft_conversation(self, agent_id: str, convo: dict):
        return self._base.upsert_draft_conversation(agent_id, convo, self._user)

    def delete_draft_conversation(self, agent_id: str):
        return self._base.delete_draft_conversation(agent_id, self._user)

    # --- Helper method to check existence without permission ---
    def agent_exists(self, agent_id: str) -> bool:
        """Check if agent exists in database (no permission check)"""
        return bool(self.get_agent(agent_id))

    # --- Dashboard analytics ---
    def dashboard_tiles(
        self,
        start: Optional[str] = None,
        end: Optional[str] = None,
        *,
        is_project_admin: bool = False,
        agent_type: Optional[str] = None,  # "all" | "user" | "enterprise"
        owner_id: Optional[str] = None,
    ) -> dict:
        if not is_project_admin:
            counts = self._base.analytics_counts(self._user)
            return {
                **counts,
                "sharedUsersDistinct": self._base.analytics_shared_users(self._user),
                "shared": self._base.analytics_shared_agents(self._user),
            }

        # Effective agentType: if ownerId is provided, we force user-scope to “see like this owner”
        effective_type = "user" if owner_id else (agent_type or "all").lower()
        ent = get_enterprise_agents(self._user) or []
        ent_ids = [a.get("id") for a in ent if a.get("id")]

        if effective_type == "user":
            if owner_id:
                u_counts = self._base.analytics_counts(owner_id)
                return {
                    **u_counts,
                    "sharedUsersDistinct": self._base.analytics_shared_users(owner_id),
                    "shared": self._base.analytics_shared_agents(owner_id),
                    "typeBreakdown": {
                        "user": {
                            "count": u_counts["myAgents"],
                            "live": u_counts["live"],
                            "inProgress": u_counts["inProgress"],
                        },
                        "enterprise": {"count": 0},
                        "total": u_counts["myAgents"],
                    },
                }
            else:
                u_counts = self._base.analytics_counts_all_user_agents()
                return {
                    **u_counts,
                    "sharedUsersDistinct": self._base.analytics_shared_users_for_all_user_agents(),
                    "shared": self._base.analytics_shared_agents_for_all_user_agents(),
                    "typeBreakdown": {
                        "user": {
                            "count": u_counts["myAgents"],
                            "live": u_counts["live"],
                            "inProgress": u_counts["inProgress"],
                        },
                        "enterprise": {"count": 0},
                        "total": u_counts["myAgents"],
                    },
                }

        if effective_type == "enterprise":
            e_count = len(ent_ids)
            return {
                "myAgents": e_count,
                "live": e_count,  # treat configured enterprise agents as live
                "inProgress": 0,
                "sharedUsersDistinct": 0,  # shares only apply to user agents
                "shared": 0,
                "typeBreakdown": {
                    "user": {"count": 0, "live": 0, "inProgress": 0},
                    "enterprise": {"count": e_count},
                    "total": e_count,
                },
            }

        # effective_type == "all"
        u_counts = self._base.analytics_counts_all_user_agents()
        e_count = len(ent_ids)
        return {
            "myAgents": u_counts["myAgents"] + e_count,
            "live": u_counts["live"] + e_count,
            "inProgress": u_counts["inProgress"],
            "sharedUsersDistinct": self._base.analytics_shared_users_for_all_user_agents(),
            "shared": self._base.analytics_shared_agents_for_all_user_agents(),
            "typeBreakdown": {
                "user": {"count": u_counts["myAgents"], "live": u_counts["live"], "inProgress": u_counts["inProgress"]},
                "enterprise": {"count": e_count},
                "total": u_counts["myAgents"] + e_count,
            },
        }

    def dashboard_usage(
        self,
        start: Optional[str],
        end: Optional[str],
        bucket: str,
        agent_id: Optional[str] = None,
        *,
        is_project_admin: bool = False,
        agent_type: Optional[str] = None,
        owner_id: Optional[str] = None,
    ):
        if not is_project_admin:
            if agent_id:
                a = self._base.get_agent(agent_id)
                if not a or a.get("owner") != self._user:
                    return []
            return self._base.analytics_usage_buckets(self._user, start, end, bucket or "week", agent_id)
        ent = get_enterprise_agents(self._user) or []
        ent_ids = [a.get("id") for a in ent if a.get("id")]
        # If ownerId is set, we “see like this owner” (user-only scope inside store via EXISTS).
        eff_type = "user" if owner_id else (agent_type or "all")
        return self._base.analytics_usage_buckets(
            self._user,
            start,
            end,
            bucket or "week",
            agent_id,
            is_project_admin=True,
            agent_type=eff_type,
            owner_id=owner_id,
            enterprise_ids=ent_ids,
        )

    def dashboard_feedback(
        self,
        start: Optional[str],
        end: Optional[str],
        agent_id: Optional[str] = None,
        *,
        is_project_admin: bool = False,
        agent_type: Optional[str] = None,
        owner_id: Optional[str] = None,
    ):
        if not is_project_admin:
            if agent_id:
                a = self._base.get_agent(agent_id)
                if not a or a.get("owner") != self._user:
                    return {"positive": 0, "negative": 0, "none": 0}
            return self._base.analytics_feedback_counts(self._user, start, end, agent_id)
        ent = get_enterprise_agents(self._user) or []
        ent_ids = [a.get("id") for a in ent if a.get("id")]
        eff_type = "user" if owner_id else (agent_type or "all")
        return self._base.analytics_feedback_counts(
            self._user,
            start,
            end,
            agent_id,
            is_project_admin=True,
            agent_type=eff_type,
            owner_id=owner_id,
            enterprise_ids=ent_ids,
        )

    def dashboard_active_users(
        self,
        start: Optional[str],
        end: Optional[str],
        bucket: str,
        agent_id: Optional[str] = None,
        *,
        is_project_admin: bool = False,
        agent_type: Optional[str] = None,
        owner_id: Optional[str] = None,
    ):
        if not is_project_admin:
            if agent_id:
                a = self._base.get_agent(agent_id)
                if not a or a.get("owner") != self._user:
                    return []
            return self._base.analytics_active_users_buckets(self._user, start, end, bucket or "week", agent_id)
        ent = get_enterprise_agents(self._user) or []
        ent_ids = [a.get("id") for a in ent if a.get("id")]
        eff_type = "user" if owner_id else (agent_type or "all")
        return self._base.analytics_active_users_buckets(
            self._user,
            start,
            end,
            bucket or "week",
            agent_id,
            is_project_admin=True,
            agent_type=eff_type,
            owner_id=owner_id,
            enterprise_ids=ent_ids,
        )

    def dashboard_activity(
        self,
        start: Optional[str],
        end: Optional[str],
        agent_id: Optional[str] = None,
        limit: int = 50,
        offset: int = 0,
        *,
        q: Optional[str] = None,
        sort_by: Optional[str] = None,
        sort_dir: Optional[str] = None,
        group_by: Optional[str] = None,
        is_project_admin: bool = False,
        agent_type: Optional[str] = None,
        owner_id: Optional[str] = None,
    ):
        if not is_project_admin:
            if agent_id:
                a = self._base.get_agent(agent_id)
                if not a or a.get("owner") != self._user:
                    return {"rows": [], "total": 0}
            rows, total = self._base.analytics_activity(
                self._user,
                start,
                end,
                agent_id,
                limit,
                offset,
                q=q,
                sort_by=sort_by,
                sort_dir=sort_dir,
                group_by=group_by,
            )
            return {"rows": rows, "total": total}

        ent = get_enterprise_agents(self._user) or []
        ent_ids = [a.get("id") for a in ent if a.get("id")]
        eff_type = "user" if owner_id else (agent_type or "all")
        rows, total = self._base.analytics_activity(
            self._user,
            start,
            end,
            agent_id,
            limit,
            offset,
            is_project_admin=True,
            agent_type=eff_type,
            owner_id=owner_id,
            enterprise_ids=ent_ids,
            q=q,
            sort_by=sort_by,
            sort_dir=sort_dir,
            group_by=group_by,
        )
        id_to_name: Dict[str, str] = {a["id"]: a.get("name", a["id"]) for a in (self._base.get_all_agents() or [])}
        id_to_name.update({e["id"]: e.get("name", e["id"]) for e in ent if e.get("id")})
        for r in rows:
            aid = r.get("agentId")
            if aid and id_to_name.get(aid) and (r.get("agentName") in (None, "", aid)):
                r["agentName"] = id_to_name[aid]
        return {"rows": rows, "total": total}

    def my_agents_minimal(self, *, is_project_admin: bool = False, owner_id: Optional[str] = None):
        """
        Return owner’s agents for the agent-picker.
        Project owner may pass owner_id to list that owner’s agents.
        """
        target = self._user
        if is_project_admin and owner_id:
            target = owner_id
        return [{"id": a["id"], "name": a["name"]} for a in self._base.get_agents_by_owner(target)]

    def delete_message(self, message_id: str) -> bool:
        """Delete a message (with user permission check)"""
        # First check if user has permission to delete this message
        message = self.get_message(message_id)
        if not message:
            return False

        # User can only delete messages from their own conversations
        return self._base.delete_message(message_id)

    def list_user_agent_owners(self) -> list[dict]:
        return self._base.list_user_agent_owners()

    def get_message_trace(self, message_id: str) -> str:
        """
        Retrieve only the trace data for the given message using the provided user id.

        Returns:
            The trace data if available and the message belongs to the user, None otherwise.
        """
        message = self._base.get_message_trace(message_id, self._user)
        if message:
            return message
        return ""
