"""
Team service — SQLite operations for Team workspace.

Flow: Tạo Team → Ghi Nháp → Comment → Review → Merge vào Chính
Features: User profile resolution, comments, review flow, reactions.
"""

from __future__ import annotations

import logging
import secrets
import string
import json
from datetime import datetime, timezone
from typing import Any

import httpx

from common.mongodb import mongodb_client, utc_now

logger = logging.getLogger(__name__)

# Valid reactions
VALID_REACTIONS = {"👍", "💡", "❓", "✅", "🔥", "❤️"}


class TeamService:
    """Team CRUD + Comments + Reviews + Reactions service."""

    # ========================= USER PROFILE RESOLUTION =========================

    async def resolve_user_profiles(self, user_ids: list[str]) -> dict[str, dict]:
        """
        Resolve user IDs → display names + avatars.
        Dùng local SQLite users table — KHÔNG phụ thuộc Clerk.
        Returns {user_id: {display_name, avatar_url, email}}.
        """
        if not user_ids:
            return {}

        unique_ids = [str(uid) for uid in set(user_ids) if uid]
        result: dict[str, dict] = {}

        # 1. Check cache
        try:
            cached = await mongodb_client.cached_user_profiles.find(
                {"user_id": {"$in": unique_ids}}
            ).to_list(length=200)
            for c in cached:
                result[c["user_id"]] = {
                    "display_name": c.get("display_name", ""),
                    "avatar_url":   c.get("avatar_url", ""),
                    "email":        c.get("email", ""),
                }
        except Exception:
            pass

        uncached = [uid for uid in unique_ids if uid not in result]
        if not uncached:
            return result

        # 2. Fetch từ local SQLite users table (KHÔNG dùng Clerk)
        try:
            from common.sqlite_client import sqlite_client
            qs = ",".join("?" for _ in uncached)
            rows = await sqlite_client.fetch_all(
                f"SELECT id, username, email, nickname, avatar_url FROM users WHERE id IN ({qs})",
                tuple(uncached),
            )
            now = utc_now()
            for row in rows:
                uid = str(row["id"])
                display_name = row.get("nickname") or row.get("username") or uid[:12]
                profile = {
                    "display_name": display_name,
                    "avatar_url":   row.get("avatar_url") or "",
                    "email":        row.get("email") or "",
                }
                result[uid] = profile
                try:
                    await mongodb_client.cached_user_profiles.update_one(
                        {"user_id": uid},
                        {"$set": {"user_id": uid, **profile, "cached_at": now}},
                        upsert=True,
                    )
                except Exception:
                    pass
        except Exception as e:
            logger.error("❌ resolve_user_profiles error: %s", e)

        # 3. Fallback cho ID không có trong DB
        for uid in uncached:
            if uid not in result:
                result[uid] = {"display_name": uid[:12], "avatar_url": "", "email": ""}

        return result


    def _get_profile(self, profiles: dict, user_id: str) -> tuple[str, str]:
        """Helper to get (display_name, avatar_url) from resolved profiles."""
        p = profiles.get(str(user_id) if user_id else "", {})
        return p.get("display_name", str(user_id)[:12] if user_id else ""), p.get("avatar_url", "")



    # ========================= TEAM CRUD =========================

    async def create_team(self, name: str, description: str, owner_id: str) -> dict:
        """Tạo team mới. Owner tự động thành member."""
        now = utc_now()
        # Use only unambiguous chars (no O/0/I/l/1)
        alphabet = string.ascii_uppercase.replace('O', '').replace('I', '') + string.digits.replace('0', '').replace('1', '')
        invite_code = ''.join(secrets.choice(alphabet) for _ in range(8))

        doc = {
            "owner_id": owner_id,
            "name": name,
            "description": description,
            "invite_code": invite_code,
            "created_at": now.isoformat(),
            "updated_at": now.isoformat(),
        }
        result = await mongodb_client.teams.insert_one(doc)
        doc["id"] = result.inserted_id

        # Add owner as member
        await mongodb_client.team_members.insert_one({
            "team_id": result.inserted_id,
            "user_id": owner_id,
            "role": "owner",
            "created_at": now.isoformat(),
        })

        logger.info("✅ Team created: %s by %s", name, owner_id)
        return self._team_to_response(doc, member_count=1)

    async def list_teams(self, user_id: str) -> list[dict]:
        """List all teams user belongs to."""
        # Find team_ids user is member of
        member_docs = await mongodb_client.team_members.find(
            {"user_id": user_id}
        ).to_list(length=100)

        team_ids = [m["team_id"] for m in member_docs]
        if not team_ids:
            return []

        results = []
        for team_id in team_ids:
            team = await mongodb_client.teams.find_one({"id": team_id})
            if not team:
                continue

            # Count members
            member_count = await mongodb_client.team_members.count_documents({"team_id": team_id})
            results.append(self._team_to_response(team, member_count=member_count))

        # Sort by created_at descending
        results.sort(key=lambda x: x.get("created_at") or "", reverse=True)
        return results

    async def get_team(self, team_id: str, user_id: str) -> dict | None:
        """Get team detail with resolved member names. Only members can view."""
        if not await self._is_member(team_id, user_id):
            return None

        team = await mongodb_client.teams.find_one({"id": int(team_id) if team_id.isdigit() else team_id})
        if not team:
            return None

        # Get members
        member_docs = await mongodb_client.team_members.find(
            {"team_id": team["id"]}
        ).to_list(length=100)

        # Resolve all member names
        member_ids = [m["user_id"] for m in member_docs]
        profiles = await self.resolve_user_profiles(member_ids)

        members = []
        for m in member_docs:
            name, avatar = self._get_profile(profiles, m["user_id"])
            members.append({
                "user_id": m["user_id"],
                "role": m["role"],
                "joined_at": m.get("created_at"),
                "display_name": name,
                "avatar_url": avatar,
            })

        response = self._team_to_response(team, member_count=len(members))
        response["members"] = members
        return response

    async def update_team(self, team_id: str, user_id: str, **fields) -> dict | None:
        """Update team. Only owner."""
        if not await self._is_owner(team_id, user_id):
            return None

        team = await mongodb_client.teams.find_one({"id": int(team_id) if team_id.isdigit() else team_id})
        if not team:
            return None

        update = {"updated_at": utc_now().isoformat()}
        for key in ("name", "description"):
            if key in fields and fields[key] is not None:
                update[key] = fields[key]

        await mongodb_client.teams.update_one(
            {"id": team["id"]},
            {"$set": update}
        )

        return await self.get_team(team_id, user_id)

    async def delete_team(self, team_id: str, user_id: str) -> bool:
        """Delete team + all members + all memos + comments + reactions. Only owner."""
        if not await self._is_owner(team_id, user_id):
            return False

        team = await mongodb_client.teams.find_one({"id": int(team_id) if team_id.isdigit() else team_id})
        if not team:
            return False

        await mongodb_client.teams.delete_one({"id": team["id"]})
        await mongodb_client.team_members.delete_many({"team_id": team["id"]})
        await mongodb_client.team_memos.delete_many({"team_id": team["id"]})
        await mongodb_client.team_comments.delete_many({"team_id": team["id"]})
        await mongodb_client.team_reactions.delete_many({"team_id": team["id"]})
        logger.info("🗑️ Team deleted: %s by %s", team_id, user_id)
        return True

    # ========================= MEMBERS =========================

    async def join_team(self, invite_code: str, user_id: str) -> dict | None:
        """Join team bằng invite code."""
        # Get all teams and filter by invite code (case-insensitive)
        all_teams = await mongodb_client.teams.find({}).to_list(length=100)
        team = None
        for t in all_teams:
            if t.get("invite_code", "").lower() == invite_code.lower():
                team = t
                break

        if not team:
            return None

        team_id = team["id"]

        # Check already member
        existing = await mongodb_client.team_members.find_one({
            "team_id": team_id, "user_id": user_id
        })
        if existing:
            return self._team_to_response(team)

        await mongodb_client.team_members.insert_one({
            "team_id": team_id,
            "user_id": user_id,
            "role": "member",
            "created_at": utc_now().isoformat(),
        })

        logger.info("✅ User %s joined team %s", user_id, team["name"])
        return self._team_to_response(team)

    async def remove_member(self, team_id: str, target_user_id: str, requester_id: str) -> bool:
        """Remove member. Owner only (can't remove self)."""
        if not await self._is_owner(team_id, requester_id):
            return False
        if target_user_id == requester_id:
            return False  # Can't remove yourself

        team = await mongodb_client.teams.find_one({"id": int(team_id) if team_id.isdigit() else team_id})
        if not team:
            return False

        result = await mongodb_client.team_members.delete_one({
            "team_id": team["id"], "user_id": target_user_id
        })
        return result.deleted_count > 0

    async def leave_team(self, team_id: str, user_id: str) -> bool:
        """Leave team. Owner can't leave (must delete team)."""
        if await self._is_owner(team_id, user_id):
            return False

        team = await mongodb_client.teams.find_one({"id": int(team_id) if team_id.isdigit() else team_id})
        if not team:
            return False

        result = await mongodb_client.team_members.delete_one({
            "team_id": team["id"], "user_id": user_id
        })
        return result.deleted_count > 0

    # ========================= TEAM MEMOS (Draft / Main) =========================

    async def create_draft(self, team_id: str, user_id: str, content: str, tags: list[str] = None) -> dict | None:
        """Tạo note nháp trong team."""
        if not await self._is_member(team_id, user_id):
            return None

        now = utc_now()
        doc = {
            "team_id": team_id,
            "creator_id": user_id,
            "content": content,
            "tags": json.dumps(tags or []),
            "space": "draft",
            "status": "draft",
            "pinned": False,
            "merged_at": None,
            "merged_by": None,
            "review_reason": None,
            "created_at": now.isoformat(),
            "updated_at": now.isoformat(),
        }
        result = await mongodb_client.team_memos.insert_one(doc)
        doc["id"] = result.inserted_id

        # Resolve creator name
        profiles = await self.resolve_user_profiles([user_id])
        return self._memo_to_response(doc, profiles=profiles)

    async def list_drafts(self, team_id: str, user_id: str) -> list[dict]:
        """List nháp của team with user profiles resolved."""
        if not await self._is_member(team_id, user_id):
            return []

        docs = await mongodb_client.team_memos.find(
            {"team_id": team_id, "space": "draft"}
        ).sort([("created_at", -1)]).to_list(length=200)

        if not docs:
            return []

        # Collect all user IDs to resolve
        user_ids = set()
        for doc in docs:
            user_ids.add(doc.get("creator_id", ""))
            if doc.get("merged_by"):
                user_ids.add(doc["merged_by"])
        user_ids.discard("")

        profiles = await self.resolve_user_profiles(list(user_ids))

        # Get comment counts + reactions in batch
        memo_ids = [str(doc["id"]) for doc in docs]
        comment_counts, reaction_counts, user_reactions = await self._get_memo_metrics(memo_ids, user_id)

        results = []
        for doc in docs:
            mid = str(doc["id"])
            resp = self._memo_to_response(doc, profiles=profiles)
            resp["comment_count"] = comment_counts.get(mid, 0)
            resp["reaction_counts"] = reaction_counts.get(mid, {})
            resp["user_reactions"] = user_reactions.get(mid, [])
            results.append(resp)

        return results

    async def _get_memo_metrics(self, memo_ids: list[str], user_id: str) -> tuple[dict, dict, dict]:
        """Fetch comment counts, reaction counts, and user reactions using SQLite."""
        if not memo_ids:
            return {}, {}, {}
            
        from common.sqlite_client import sqlite_client
        qs = ",".join("?" for _ in memo_ids)
        params = tuple(memo_ids)
        
        # 1. Comment counts
        comment_counts = {}
        rows = await sqlite_client.fetch_all(
            f"SELECT memo_id, count(*) as cnt FROM cuccu_team_comments WHERE memo_id IN ({qs}) GROUP BY memo_id", params
        )
        for r in rows:
            comment_counts[str(r["memo_id"])] = r["cnt"]
            
        # 2. Reaction counts
        reaction_counts = {}
        r_rows = await sqlite_client.fetch_all(
            f"SELECT memo_id, emoji, count(*) as cnt FROM cuccu_team_reactions WHERE memo_id IN ({qs}) GROUP BY memo_id, emoji", params
        )
        for r in r_rows:
            mid = str(r["memo_id"])
            reaction_counts.setdefault(mid, {})[r["emoji"]] = r["cnt"]
            
        # 3. User reactions
        user_reactions = {}
        u_rows = await sqlite_client.fetch_all(
            f"SELECT memo_id, emoji FROM cuccu_team_reactions WHERE memo_id IN ({qs}) AND user_id = ?", params + tuple([user_id])
        )
        for r in u_rows:
            mid = str(r["memo_id"])
            user_reactions.setdefault(mid, []).append(r["emoji"])
            
        return comment_counts, reaction_counts, user_reactions

    async def list_main(self, team_id: str, user_id: str) -> list[dict]:
        """List bản chính của team with user profiles resolved."""
        if not await self._is_member(team_id, user_id):
            return []

        docs = await mongodb_client.team_memos.find(
            {"team_id": team_id, "space": "main"}
        ).sort([("merged_at", -1)]).to_list(length=200)

        if not docs:
            return []

        # Collect user IDs
        user_ids = set()
        for doc in docs:
            user_ids.add(doc.get("creator_id", ""))
            if doc.get("merged_by"):
                user_ids.add(doc["merged_by"])
        user_ids.discard("")

        profiles = await self.resolve_user_profiles(list(user_ids))

        memo_ids = [str(doc["id"]) for doc in docs]
        comment_counts, reaction_counts, user_reactions = await self._get_memo_metrics(memo_ids, user_id)

        results = []
        for doc in docs:
            mid = str(doc["id"])
            resp = self._memo_to_response(doc, profiles=profiles)
            resp["comment_count"] = comment_counts.get(mid, 0)
            resp["reaction_counts"] = reaction_counts.get(mid, {})
            resp["user_reactions"] = user_reactions.get(mid, [])
            results.append(resp)

        return results

    async def merge_to_main(self, team_id: str, memo_id: str, user_id: str) -> dict | None:
        """Merge note nháp → bản chính. Only owner can merge."""
        if not await self._is_owner(team_id, user_id):
            return None

        team = await mongodb_client.teams.find_one({"id": int(team_id) if team_id.isdigit() else team_id})
        if not team:
            return None

        doc = await mongodb_client.team_memos.find_one({
            "id": int(memo_id) if memo_id.isdigit() else memo_id,
            "team_id": team["id"],
            "space": "draft",
        })
        if not doc:
            return None

        now = utc_now()
        await mongodb_client.team_memos.update_one(
            {"id": doc["id"]},
            {"$set": {
                "space": "main",
                "status": "merged",
                "merged_at": now.isoformat(),
                "merged_by": user_id,
                "updated_at": now.isoformat(),
            }}
        )

        updated = await mongodb_client.team_memos.find_one({"id": doc["id"]})
        profiles = await self.resolve_user_profiles([
            updated.get("creator_id", ""), user_id
        ])
        logger.info("✅ Merged draft → main: %s in team %s", memo_id, team_id)
        return self._memo_to_response(updated, profiles=profiles)

    async def unmerge_to_draft(self, team_id: str, memo_id: str, user_id: str) -> dict | None:
        """Đưa note từ chính về nháp. Chỉ owner."""
        if not await self._is_owner(team_id, user_id):
            return None

        team = await mongodb_client.teams.find_one({"id": int(team_id) if team_id.isdigit() else team_id})
        if not team:
            return None

        doc = await mongodb_client.team_memos.find_one({
            "id": int(memo_id) if memo_id.isdigit() else memo_id,
            "team_id": team["id"],
            "space": "main",
        })
        if not doc:
            return None

        await mongodb_client.team_memos.update_one(
            {"id": doc["id"]},
            {"$set": {
                "space": "draft",
                "status": "draft",
                "merged_at": None,
                "merged_by": None,
                "updated_at": utc_now().isoformat(),
            }}
        )

        updated = await mongodb_client.team_memos.find_one({"id": doc["id"]})
        profiles = await self.resolve_user_profiles([updated.get("creator_id", "")])
        return self._memo_to_response(updated, profiles=profiles)

    async def update_memo(self, team_id: str, memo_id: str, user_id: str, content: str = None, tags: list[str] = None) -> dict | None:
        """Update team memo. Creator hoặc owner."""
        team = await mongodb_client.teams.find_one({"id": int(team_id) if team_id.isdigit() else team_id})
        if not team:
            return None

        doc = await mongodb_client.team_memos.find_one({
            "id": int(memo_id) if memo_id.isdigit() else memo_id,
            "team_id": team["id"],
        })
        if not doc:
            return None

        is_owner = await self._is_owner(team_id, user_id)
        if not is_owner and doc.get("creator_id") != user_id:
            return None

        # Can't edit if pending_review (unless owner)
        if doc.get("status") == "pending_review" and not is_owner:
            return None

        update: dict[str, Any] = {"updated_at": utc_now().isoformat()}
        if content is not None:
            update["content"] = content
        if tags is not None:
            update["tags"] = json.dumps(tags)

        await mongodb_client.team_memos.update_one(
            {"id": doc["id"]},
            {"$set": update}
        )

        updated = await mongodb_client.team_memos.find_one({"id": doc["id"]})
        profiles = await self.resolve_user_profiles([updated.get("creator_id", "")])
        return self._memo_to_response(updated, profiles=profiles)

    async def delete_memo(self, team_id: str, memo_id: str, user_id: str) -> bool:
        """Delete team memo. Creator hoặc owner. Must unmerge first if in main."""
        team = await mongodb_client.teams.find_one({"id": int(team_id) if team_id.isdigit() else team_id})
        if not team:
            return False

        doc = await mongodb_client.team_memos.find_one({
            "id": int(memo_id) if memo_id.isdigit() else memo_id,
            "team_id": team["id"],
        })
        if not doc:
            return False

        is_owner = await self._is_owner(team_id, user_id)
        if not is_owner and doc.get("creator_id") != user_id:
            return False

        # Must unmerge before deleting
        if doc.get("space") == "main" and not is_owner:
            return False

        await mongodb_client.team_memos.delete_one({"id": doc["id"]})
        # Clean up comments and reactions
        await mongodb_client.team_comments.delete_many({"memo_id": str(doc["id"])})
        await mongodb_client.team_reactions.delete_many({"memo_id": str(doc["id"])})
        return True

    async def pin_memo(self, team_id: str, memo_id: str, user_id: str) -> dict | None:
        """Pin/unpin team memo. Owner hoặc creator."""
        team = await mongodb_client.teams.find_one({"id": int(team_id) if team_id.isdigit() else team_id})
        if not team:
            return None

        doc = await mongodb_client.team_memos.find_one({
            "id": int(memo_id) if memo_id.isdigit() else memo_id,
            "team_id": team["id"],
        })
        if not doc:
            return None

        is_owner = await self._is_owner(team_id, user_id)
        if not is_owner and doc.get("creator_id") != user_id:
            return None

        new_pinned = not doc.get("pinned", False)
        await mongodb_client.team_memos.update_one(
            {"id": doc["id"]},
            {"$set": {"pinned": new_pinned, "updated_at": utc_now().isoformat()}}
        )

        updated = await mongodb_client.team_memos.find_one({"id": doc["id"]})
        profiles = await self.resolve_user_profiles([updated.get("creator_id", "")])
        return self._memo_to_response(updated, profiles=profiles)

    # ========================= REVIEW FLOW =========================

    async def request_review(self, team_id: str, memo_id: str, user_id: str) -> dict | None:
        """Member requests review for their draft. Status: draft → pending_review."""
        if not await self._is_member(team_id, user_id):
            return None

        team = await mongodb_client.teams.find_one({"id": int(team_id) if team_id.isdigit() else team_id})
        if not team:
            return None

        doc = await mongodb_client.team_memos.find_one({
            "id": int(memo_id) if memo_id.isdigit() else memo_id,
            "team_id": team["id"],
            "space": "draft",
        })
        if not doc:
            return None

        # Only creator or owner can request review
        is_owner = await self._is_owner(team_id, user_id)
        if not is_owner and doc.get("creator_id") != user_id:
            return None

        await mongodb_client.team_memos.update_one(
            {"id": doc["id"]},
            {"$set": {"status": "pending_review", "updated_at": utc_now().isoformat()}}
        )

        updated = await mongodb_client.team_memos.find_one({"id": doc["id"]})
        profiles = await self.resolve_user_profiles([updated.get("creator_id", "")])
        logger.info("📨 Review requested: %s by %s", memo_id, user_id)
        return self._memo_to_response(updated, profiles=profiles)

    async def submit_review(self, team_id: str, memo_id: str, user_id: str, action: str, reason: str = "") -> dict | None:
        """Owner approves or rejects a review. Only owner can review."""
        if not await self._is_owner(team_id, user_id):
            return None

        team = await mongodb_client.teams.find_one({"id": int(team_id) if team_id.isdigit() else team_id})
        if not team:
            return None

        doc = await mongodb_client.team_memos.find_one({
            "id": int(memo_id) if memo_id.isdigit() else memo_id,
            "team_id": team["id"],
        })
        if not doc:
            return None

        if action == "approve":
            new_status = "approved"
        elif action == "reject":
            new_status = "draft"
        else:
            return None

        update = {
            "status": new_status,
            "review_reason": reason,
            "updated_at": utc_now().isoformat(),
        }

        await mongodb_client.team_memos.update_one(
            {"id": doc["id"]},
            {"$set": update}
        )

        updated = await mongodb_client.team_memos.find_one({"id": doc["id"]})
        profiles = await self.resolve_user_profiles([updated.get("creator_id", "")])
        logger.info("📋 Review %s: %s by %s (reason: %s)", action, memo_id, user_id, reason[:50])
        return self._memo_to_response(updated, profiles=profiles)

    # ========================= COMMENTS =========================

    async def create_comment(self, team_id: str, memo_id: str, user_id: str, content: str) -> dict | None:
        """Add comment to a team memo."""
        if not await self._is_member(team_id, user_id):
            return None

        # Verify memo exists in this team
        team = await mongodb_client.teams.find_one({"id": int(team_id) if team_id.isdigit() else team_id})
        if not team:
            return None

        memo = await mongodb_client.team_memos.find_one({
            "id": int(memo_id) if memo_id.isdigit() else memo_id,
            "team_id": team["id"],
        })
        if not memo:
            return None

        now = utc_now()
        doc = {
            "team_id": team["id"],
            "memo_id": memo["id"],
            "user_id": user_id,
            "content": content,
            "created_at": now.isoformat(),
        }
        result = await mongodb_client.team_comments.insert_one(doc)
        doc["id"] = result.inserted_id

        profiles = await self.resolve_user_profiles([user_id])
        name, avatar = self._get_profile(profiles, user_id)

        return {
            "id": str(doc["id"]),
            "memo_id": str(memo["id"]),
            "user_id": user_id,
            "user_name": name,
            "user_avatar": avatar,
            "content": content,
            "created_at": now.isoformat(),
        }

    async def list_comments(self, team_id: str, memo_id: str, user_id: str) -> list[dict]:
        """List comments for a team memo."""
        if not await self._is_member(team_id, user_id):
            return []

        team = await mongodb_client.teams.find_one({"id": int(team_id) if team_id.isdigit() else team_id})
        if not team:
            return []

        docs = await mongodb_client.team_comments.find(
            {"memo_id": memo_id, "team_id": team["id"]}
        ).sort([("created_at", 1)]).to_list(length=100)

        if not docs:
            return []

        # Resolve all commenter names
        user_ids = list(set(d["user_id"] for d in docs))
        profiles = await self.resolve_user_profiles(user_ids)

        results = []
        for doc in docs:
            name, avatar = self._get_profile(profiles, doc["user_id"])
            results.append({
                "id": str(doc["id"]),
                "memo_id": doc["memo_id"],
                "user_id": doc["user_id"],
                "user_name": name,
                "user_avatar": avatar,
                "content": doc["content"],
                "created_at": doc.get("created_at", ""),
            })

        return results

    async def delete_comment(self, team_id: str, comment_id: str, user_id: str) -> bool:
        """Delete a comment. Only comment author or team owner."""
        team = await mongodb_client.teams.find_one({"id": int(team_id) if team_id.isdigit() else team_id})
        if not team:
            return False

        doc = await mongodb_client.team_comments.find_one({
            "id": int(comment_id) if comment_id.isdigit() else comment_id,
        })
        if not doc or doc.get("team_id") != team["id"]:
            return False

        is_owner = await self._is_owner(team_id, user_id)
        if not is_owner and doc.get("user_id") != user_id:
            return False

        await mongodb_client.team_comments.delete_one({"id": doc["id"]})
        return True

    # ========================= REACTIONS =========================

    async def toggle_reaction(self, team_id: str, memo_id: str, user_id: str, emoji: str) -> dict | None:
        """Toggle reaction on a memo. Returns updated reaction counts."""
        if emoji not in VALID_REACTIONS:
            return None

        if not await self._is_member(team_id, user_id):
            return None

        # Check memo exists
        team = await mongodb_client.teams.find_one({"id": int(team_id) if team_id.isdigit() else team_id})
        if not team:
            return None

        memo = await mongodb_client.team_memos.find_one({
            "id": int(memo_id) if memo_id.isdigit() else memo_id,
            "team_id": team["id"],
        })
        if not memo:
            return None

        # Check if reaction already exists
        existing = await mongodb_client.team_reactions.find_one({
            "memo_id": memo["id"],
            "user_id": user_id,
            "emoji": emoji,
        })

        if existing:
            # Remove reaction
            await mongodb_client.team_reactions.delete_one({"id": existing["id"]})
        else:
            # Add reaction
            await mongodb_client.team_reactions.insert_one({
                "team_id": team["id"],
                "memo_id": memo["id"],
                "user_id": user_id,
                "emoji": emoji,
                "created_at": utc_now().isoformat(),
            })

        # Return updated counts
        pipeline = [
            {"$match": {"memo_id": memo["id"]}},
            {"$group": {"_id": "$emoji", "count": {"$sum": 1}}},
        ]
        counts = {}
        async for item in mongodb_client.team_reactions.aggregate(pipeline):
            counts[item["_id"]] = item["count"]

        # User's reactions for this memo
        user_emojis = await mongodb_client.team_reactions.find(
            {"memo_id": memo["id"], "user_id": user_id}
        ).to_list(length=20)
        user_reaction_list = [r["emoji"] for r in user_emojis]

        return {
            "memo_id": str(memo["id"]),
            "reaction_counts": counts,
            "user_reactions": user_reaction_list,
        }

    # ========================= HELPERS =========================

    async def _is_member(self, team_id: str, user_id: str) -> bool:
        team = await mongodb_client.teams.find_one({"id": int(team_id) if team_id.isdigit() else team_id})
        if not team:
            return False
        return await mongodb_client.team_members.find_one({
            "team_id": team["id"], "user_id": user_id
        }) is not None

    async def _is_owner(self, team_id: str, user_id: str) -> bool:
        team = await mongodb_client.teams.find_one({"id": int(team_id) if team_id.isdigit() else team_id})
        if not team:
            return False
        return await mongodb_client.team_members.find_one({
            "team_id": team["id"], "user_id": user_id, "role": "owner"
        }) is not None

    def _team_to_response(self, doc: dict, member_count: int = 0) -> dict:
        created = doc.get("created_at")
        return {
            "id": str(doc["id"]),
            "name": doc.get("name", ""),
            "description": doc.get("description", ""),
            "owner_id": doc.get("owner_id", ""),
            "invite_code": doc.get("invite_code", ""),
            "members": [],
            "member_count": member_count,
            "created_at": str(created) if created else None,
        }

    def _memo_to_response(self, doc: dict, profiles: dict = None) -> dict:
        profiles = profiles or {}
        created = doc.get("created_at")
        updated = doc.get("updated_at")
        merged = doc.get("merged_at")

        creator_id = doc.get("creator_id", "")
        creator_name, creator_avatar = self._get_profile(profiles, creator_id)

        merged_by = doc.get("merged_by")
        merged_by_name = ""
        if merged_by:
            merged_by_name, _ = self._get_profile(profiles, merged_by)

        tags_raw = doc.get("tags", "[]")
        tags_list = []
        if isinstance(tags_raw, str):
            try:
                tags_list = json.loads(tags_raw) if tags_raw else []
            except Exception:
                tags_list = []
        elif isinstance(tags_raw, list):
            tags_list = tags_raw

        return {
            "id": str(doc["id"]),
            "content": doc.get("content", ""),
            "tags": tags_list,
            "creator_id": creator_id,
            "creator_name": creator_name,
            "creator_avatar": creator_avatar,
            "team_id": doc.get("team_id", ""),
            "space": doc.get("space", "draft"),
            "status": doc.get("status", "draft"),
            "pinned": doc.get("pinned", False),
            "merged_at": str(merged) if merged else None,
            "merged_by": merged_by,
            "merged_by_name": merged_by_name,
            "comment_count": 0,
            "reaction_counts": {},
            "user_reactions": [],
            "review_reason": doc.get("review_reason", ""),
            "created_at": str(created) if created else None,
            "updated_at": str(updated) if updated else None,
        }


# Singleton
team_service = TeamService()
