"""Projects, milestones and pipeline. Notifies on assignment changes."""
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 ProjectIn, ProjectUpdate, MilestoneIn
import notifications as notif

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

_mgr = require_roles("ceo", "admin", "pm", "pmo")


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


def _ser(p: dict) -> dict:
    return {
        "id": str(p["_id"]),
        "name": p["name"],
        "description": p.get("description", ""),
        "client_id": p.get("client_id"),
        "pm_id": p.get("pm_id"),
        "collaborator_ids": p.get("collaborator_ids", []),
        "status": p.get("status", "discovery"),
        "milestones": p.get("milestones", []),
        "created_at": p.get("created_at"),
        "updated_at": p.get("updated_at"),
    }


async def _visible_filter(user: dict) -> dict:
    role = user["role"]
    uid = user["id"]
    if role in {"ceo", "admin", "pm", "pmo"}:
        return {}
    if role == "client":
        return {"client_id": uid}
    if role in {"sales", "account_manager"}:
        # they can see all to support client conversations
        return {}
    # collaborator + marketing + rh
    return {"$or": [{"collaborator_ids": uid}, {"pm_id": uid}]}


@router.get("")
async def list_projects(user: dict = Depends(get_current_user)):
    db = get_db()
    flt = await _visible_filter(user)
    cursor = db.projects.find(flt).sort("created_at", -1)
    return [_ser(p) async for p in cursor]


@router.post("")
async def create_project(payload: ProjectIn, current: dict = Depends(_mgr)):
    db = get_db()
    now = datetime.now(timezone.utc).isoformat()
    doc = {
        **payload.model_dump(),
        "milestones": [
            {"label": "Descubrimiento", "completed": True},
            {"label": "Diseño aprobado", "completed": False},
            {"label": "En desarrollo", "completed": False},
            {"label": "QA", "completed": False},
            {"label": "Entrega", "completed": False},
        ],
        "created_at": now,
        "updated_at": now,
    }
    res = await db.projects.insert_one(doc)
    doc["_id"] = res.inserted_id
    # Auto-create internal channel
    internal_members = list(set([m for m in [payload.pm_id, *(payload.collaborator_ids or [])] if m]))
    await db.channels.insert_one({
        "name": payload.name,
        "type": "project",
        "project_id": str(res.inserted_id),
        "members": internal_members,
        "created_at": now,
    })
    # Client channel
    if payload.client_id and payload.pm_id:
        await db.channels.insert_one({
            "name": f"Cliente: {payload.name}",
            "type": "client",
            "project_id": str(res.inserted_id),
            "members": [payload.client_id, payload.pm_id],
            "created_at": now,
        })
    # Notify assigned members
    notify_ids = [m for m in internal_members if m != current["id"]]
    if notify_ids:
        await notif.push(
            notify_ids, "project_assigned",
            f"Asignado al proyecto: {payload.name}",
            f"{current.get('name', '')} te agregó al proyecto.",
            link=f"/app/proyectos/{res.inserted_id}",
            meta={"project_id": str(res.inserted_id)},
        )
    return _ser(doc)


@router.get("/{project_id}")
async def get_project(project_id: str, user: dict = Depends(get_current_user)):
    db = get_db()
    oid = _oid(project_id)
    p = await db.projects.find_one({"_id": oid})
    if not p:
        raise HTTPException(status_code=404, detail="Proyecto no encontrado")
    role = user["role"]
    if role == "client" and p.get("client_id") != user["id"]:
        raise HTTPException(status_code=403, detail="Sin acceso")
    if role in {"collaborator", "marketing", "rh"} and user["id"] not in (p.get("collaborator_ids") or []) and p.get("pm_id") != user["id"]:
        raise HTTPException(status_code=403, detail="Sin acceso")
    return _ser(p)


@router.patch("/{project_id}")
async def update_project(project_id: str, payload: ProjectUpdate, current: dict = Depends(_mgr)):
    db = get_db()
    oid = _oid(project_id)
    existing = await db.projects.find_one({"_id": oid})
    if not existing:
        raise HTTPException(status_code=404, detail="Proyecto no encontrado")
    update = {k: v for k, v in payload.model_dump(exclude_unset=True).items()}
    if not update:
        raise HTTPException(status_code=400, detail="Nada para actualizar")
    update["updated_at"] = datetime.now(timezone.utc).isoformat()
    res = await db.projects.find_one_and_update({"_id": oid}, {"$set": update}, return_document=True)

    # Notify added/removed collaborators
    old_members = set(existing.get("collaborator_ids") or []) | ({existing.get("pm_id")} if existing.get("pm_id") else set())
    new_members = set(res.get("collaborator_ids") or []) | ({res.get("pm_id")} if res.get("pm_id") else set())
    added = list(new_members - old_members - {current["id"], None})
    removed = list(old_members - new_members - {current["id"], None})
    if added:
        await notif.push(
            added, "project_assigned",
            f"Asignado a proyecto: {res['name']}",
            f"{current.get('name', '')} te agregó al equipo.",
            link=f"/app/proyectos/{project_id}",
            meta={"project_id": project_id},
        )
        # also add to project channel
        await db.channels.update_one(
            {"project_id": project_id, "type": "project"},
            {"$addToSet": {"members": {"$each": added}}},
        )
    if removed:
        await notif.push(
            removed, "project_unassigned",
            f"Removido de proyecto: {res['name']}",
            f"{current.get('name', '')} te quitó del equipo.",
            link="/app",
            meta={"project_id": project_id},
        )
        await db.channels.update_one(
            {"project_id": project_id, "type": "project"},
            {"$pull": {"members": {"$in": removed}}},
        )
    return _ser(res)


@router.delete("/{project_id}")
async def delete_project(project_id: str, _: dict = Depends(require_roles("ceo", "admin"))):
    db = get_db()
    oid = _oid(project_id)
    await db.projects.delete_one({"_id": oid})
    await db.tasks.delete_many({"project_id": project_id})
    await db.channels.delete_many({"project_id": project_id})
    await db.messages.delete_many({"project_id": project_id})
    return {"ok": True}


@router.put("/{project_id}/milestones")
async def set_milestones(project_id: str, milestones: list[MilestoneIn], current: dict = Depends(_mgr)):
    db = get_db()
    oid = _oid(project_id)
    existing = await db.projects.find_one({"_id": oid})
    if not existing:
        raise HTTPException(status_code=404, detail="Proyecto no encontrado")
    items = [m.model_dump() for m in milestones]
    res = await db.projects.find_one_and_update(
        {"_id": oid},
        {"$set": {"milestones": items, "updated_at": datetime.now(timezone.utc).isoformat()}},
        return_document=True,
    )
    # Notify client of milestone changes
    if res.get("client_id"):
        old = {m["label"]: m.get("completed", False) for m in existing.get("milestones", [])}
        for m in items:
            if not old.get(m["label"], False) and m.get("completed"):
                await notif.push(
                    [res["client_id"]], "milestone_updated",
                    f"Hito completado: {m['label']}",
                    f"Proyecto: {res['name']}",
                    link="/portal",
                    meta={"project_id": project_id, "milestone": m["label"]},
                )
                break
    return _ser(res)
