"""Channels & messages. Real-time broadcast via Socket.IO."""
from datetime import datetime, timezone
from bson import ObjectId
from bson.errors import InvalidId
from fastapi import APIRouter, Depends, HTTPException

from deps import get_db, get_current_user, require_roles
from models import MessageIn, ChannelCreateIn, CLIENT_CHAT_ROLES
import notifications as notif

router = APIRouter(prefix="/chat", tags=["chat"])

# Roles allowed to see "client" channels (with clients in them)
_client_channel_roles = CLIENT_CHAT_ROLES


def _oid(s: str) -> ObjectId:
    try:
        return ObjectId(s)
    except InvalidId:
        raise HTTPException(status_code=400, detail="ID inválido")


def _ser_channel(c: dict) -> dict:
    return {
        "id": str(c["_id"]),
        "name": c["name"],
        "type": c.get("type", "project"),
        "project_id": c.get("project_id"),
        "department_id": c.get("department_id"),
        "members": c.get("members", []),
        "created_at": c.get("created_at"),
    }


def _ser_msg(m: dict) -> dict:
    return {
        "id": str(m["_id"]),
        "channel_id": m["channel_id"],
        "sender_id": m["sender_id"],
        "sender_name": m.get("sender_name", ""),
        "sender_role": m.get("sender_role", ""),
        "body": m["body"],
        "created_at": m.get("created_at"),
    }


def _can_access(ch: dict, user: dict) -> bool:
    """Return True if `user` may read/write this channel."""
    role = user["role"]
    uid = user["id"]
    members = ch.get("members") or []
    # Client channels — restricted to allowed roles + members
    if ch.get("type") == "client":
        if uid in members:
            return True
        return role in _client_channel_roles
    # CEO/Admin/PMO/PM have broad access (project & department channels)
    if role in {"ceo", "admin", "pmo", "pm"}:
        return True
    return uid in members


@router.get("/channels")
async def list_channels(user: dict = Depends(get_current_user)):
    db = get_db()
    role = user["role"]
    uid = user["id"]
    if role == "client":
        # client only sees client channels they're a member of
        flt = {"type": "client", "members": uid}
    elif role in {"ceo", "admin", "pmo"}:
        flt = {}  # see all
    elif role == "pm":
        # all project + department + client channels (their own DMs)
        flt = {"$or": [{"type": {"$in": ["project", "department", "client", "team"]}}, {"members": uid}]}
    elif role in _client_channel_roles:
        # sales/account_manager — client channels + their own
        flt = {"$or": [{"type": "client"}, {"members": uid}]}
    else:
        # collaborator/marketing/rh — only channels they're a member of
        flt = {"members": uid}
    cursor = db.channels.find(flt)
    return [_ser_channel(c) async for c in cursor]


@router.post("/channels")
async def create_channel(payload: ChannelCreateIn, user: dict = Depends(get_current_user)):
    """Create team or department channels."""
    if user["role"] not in {"ceo", "admin", "pmo", "pm", "rh"}:
        raise HTTPException(status_code=403, detail="Sin permisos")
    db = get_db()
    members = list(set(payload.member_ids + [user["id"]]))
    doc = {
        "name": payload.name,
        "type": payload.type,
        "department_id": payload.department_id,
        "project_id": None,
        "members": members,
        "created_at": datetime.now(timezone.utc).isoformat(),
    }
    r = await db.channels.insert_one(doc)
    doc["_id"] = r.inserted_id
    return _ser_channel(doc)


@router.post("/channels/dm/{other_user_id}")
async def open_dm(other_user_id: str, user: dict = Depends(get_current_user)):
    db = get_db()
    members = sorted([user["id"], other_user_id])
    existing = await db.channels.find_one({"type": "dm", "members": members})
    if existing:
        return _ser_channel(existing)
    other = await db.users.find_one({"_id": _oid(other_user_id)})
    if not other:
        raise HTTPException(status_code=404, detail="Usuario no encontrado")
    # Client cannot DM arbitrary users; only allowed roles
    if user["role"] == "client" and other.get("role") not in _client_channel_roles:
        raise HTTPException(status_code=403, detail="Sin permisos")
    doc = {
        "name": f"DM: {user['name']} ↔ {other.get('name', '')}",
        "type": "dm",
        "project_id": None,
        "department_id": None,
        "members": members,
        "created_at": datetime.now(timezone.utc).isoformat(),
    }
    res = await db.channels.insert_one(doc)
    doc["_id"] = res.inserted_id
    return _ser_channel(doc)


@router.get("/channels/{channel_id}/messages")
async def list_messages(channel_id: str, user: dict = Depends(get_current_user)):
    db = get_db()
    ch = await db.channels.find_one({"_id": _oid(channel_id)})
    if not ch:
        raise HTTPException(status_code=404, detail="Canal no encontrado")
    if not _can_access(ch, user):
        raise HTTPException(status_code=403, detail="Sin acceso")
    cur = db.messages.find({"channel_id": channel_id}).sort("created_at", 1).limit(500)
    return [_ser_msg(m) async for m in cur]


@router.post("/messages")
async def post_message(payload: MessageIn, user: dict = Depends(get_current_user)):
    db = get_db()
    ch = await db.channels.find_one({"_id": _oid(payload.channel_id)})
    if not ch:
        raise HTTPException(status_code=404, detail="Canal no encontrado")
    if not _can_access(ch, user):
        raise HTTPException(status_code=403, detail="Sin acceso")
    doc = {
        "channel_id": payload.channel_id,
        "project_id": ch.get("project_id"),
        "sender_id": user["id"],
        "sender_name": user.get("name", ""),
        "sender_role": user.get("role", ""),
        "body": payload.body,
        "created_at": datetime.now(timezone.utc).isoformat(),
    }
    res = await db.messages.insert_one(doc)
    doc["_id"] = res.inserted_id
    msg = _ser_msg(doc)
    # broadcast via Socket.IO
    try:
        from socket_server import sio
        await sio.emit("message", msg, room=payload.channel_id)
    except Exception:
        pass
    # notifications to other members
    other_members = [m for m in (ch.get("members") or []) if m != user["id"]]
    if other_members:
        ch_name = ch.get("name", "Canal")
        await notif.push(
            other_members,
            "message",
            f"Nuevo mensaje en {ch_name}",
            f"{user.get('name', '')}: {payload.body[:80]}",
            link="/app/chat" if user["role"] != "client" else "/portal",
            meta={"channel_id": payload.channel_id},
        )
    return msg
