"""
Client / Team / Project management.

Access rules:
  - Client CRUD              → Admin only
  - PM assignment on client  → Admin only
  - Team CRUD + membership   → Admin or PM of that client
  - Project CRUD             → Admin or PM of that client
  - List projects (read)     → Admin, PM, or any team member of the client
"""

from datetime import UTC, datetime

from bson import ObjectId
from fastapi import APIRouter, Depends, HTTPException, Request
from motor.motor_asyncio import AsyncIOMotorDatabase
from pydantic import BaseModel

from ...core.database import get_database
from ...core.dependencies import get_current_user, require_roles
from ...models.audit_log import AuditAction
from ...models.client import (
    Client,
    ClientCreate,
    ClientUpdate,
    Project,
    ProjectCreate,
    ProjectUpdate,
    Team,
    TeamCreate,
    TeamUpdate,
)
from ...models.user import User, UserRole
from ...services.audit_logger import audit_logger

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


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

def _now() -> datetime:
    return datetime.now(UTC)


async def _get_client_or_404(client_id: str, db: AsyncIOMotorDatabase) -> dict:
    doc = await db.clients.find_one({"_id": client_id})
    if not doc:
        raise HTTPException(status_code=404, detail="Client not found")
    return doc


async def _get_team_or_404(team_id: str, client_id: str, db: AsyncIOMotorDatabase) -> dict:
    doc = await db.teams.find_one({"_id": team_id, "client_id": client_id})
    if not doc:
        raise HTTPException(status_code=404, detail="Team not found")
    return doc


async def _get_project_or_404(project_id: str, client_id: str, db: AsyncIOMotorDatabase) -> dict:
    doc = await db.projects.find_one({"_id": project_id, "client_id": client_id})
    if not doc:
        raise HTTPException(status_code=404, detail="Project not found")
    return doc


def _client_from_doc(doc: dict) -> Client:
    return Client(
        id=str(doc["_id"]),
        name=doc["name"],
        slug=doc["slug"],
        is_active=doc.get("is_active", True),
        created_at=doc.get("created_at"),
        updated_at=doc.get("updated_at"),
    )


def _team_from_doc(doc: dict) -> Team:
    return Team(
        id=str(doc["_id"]),
        name=doc["name"],
        client_id=doc["client_id"],
        member_user_ids=doc.get("member_user_ids", []),
        created_at=doc.get("created_at"),
        updated_at=doc.get("updated_at"),
    )


def _project_from_doc(doc: dict) -> Project:
    return Project(
        id=str(doc["_id"]),
        name=doc["name"],
        client_id=doc["client_id"],
        is_active=doc.get("is_active", True),
        default_languages=doc.get("default_languages", []),
        default_linguist_id=doc.get("default_linguist_id"),
        default_reviewer_id=doc.get("default_reviewer_id"),
        created_at=doc.get("created_at"),
        updated_at=doc.get("updated_at"),
    )


# ---------------------------------------------------------------------------
# Client endpoints (Admin only)
# ---------------------------------------------------------------------------

@router.get("", response_model=list[Client])
async def list_clients(
    current_user: User = Depends(get_current_user),
    db: AsyncIOMotorDatabase = Depends(get_database),
):
    if current_user.role == UserRole.ADMIN:
        docs = await db.clients.find().to_list(None)
    elif current_user.role == UserRole.PROJECT_MANAGER:
        client_ids = current_user.pm_client_ids or []
        docs = await db.clients.find({"_id": {"$in": client_ids}}).to_list(None)
    else:
        raise HTTPException(status_code=403, detail="Insufficient permissions")
    return [_client_from_doc(d) for d in docs]


@router.post("", response_model=Client)
async def create_client(
    body: ClientCreate,
    request: Request,
    current_user: User = Depends(require_roles(UserRole.ADMIN)),
    db: AsyncIOMotorDatabase = Depends(get_database),
):
    if await db.clients.find_one({"slug": body.slug}):
        raise HTTPException(status_code=409, detail="Slug already exists")
    now = _now()
    client_id = str(ObjectId())
    await db.clients.insert_one({
        "_id": client_id,
        "name": body.name,
        "slug": body.slug,
        "is_active": True,
        "created_at": now,
        "updated_at": now,
    })
    doc = await db.clients.find_one({"_id": client_id})
    client = _client_from_doc(doc)
    await audit_logger.log_action(
        action=AuditAction.CLIENT_CREATE,
        description=f"Client '{client.name}' created",
        user=current_user,
        request=request,
        resource_type="client",
        resource_id=str(client.id),
        resource_name=client.name,
        details={"slug": client.slug},
    )
    return client


@router.get("/{client_id}", response_model=Client)
async def get_client(
    client_id: str,
    current_user: User = Depends(get_current_user),
    db: AsyncIOMotorDatabase = Depends(get_database),
):
    if current_user.role not in (UserRole.ADMIN, UserRole.PROJECT_MANAGER):
        raise HTTPException(status_code=403, detail="Insufficient permissions")
    if current_user.role == UserRole.PROJECT_MANAGER and client_id not in (current_user.pm_client_ids or []):
        raise HTTPException(status_code=403, detail="Not a manager for this client")
    doc = await _get_client_or_404(client_id, db)
    return _client_from_doc(doc)


@router.patch("/{client_id}", response_model=Client)
async def update_client(
    client_id: str,
    body: ClientUpdate,
    request: Request,
    current_user: User = Depends(require_roles(UserRole.ADMIN)),
    db: AsyncIOMotorDatabase = Depends(get_database),
):
    await _get_client_or_404(client_id, db)
    update: dict = dict(body.model_dump(exclude_none=True).items())
    if not update:
        raise HTTPException(status_code=422, detail="No fields to update")
    if "slug" in update and await db.clients.find_one({"slug": update["slug"], "_id": {"$ne": client_id}}):
        raise HTTPException(status_code=409, detail="Slug already exists")
    update["updated_at"] = _now()
    await db.clients.update_one({"_id": client_id}, {"$set": update})
    doc = await db.clients.find_one({"_id": client_id})
    client = _client_from_doc(doc)
    await audit_logger.log_action(
        action=AuditAction.CLIENT_UPDATE,
        description=f"Client '{client.name}' updated",
        user=current_user,
        request=request,
        resource_type="client",
        resource_id=client_id,
        resource_name=client.name,
        details={"fields_updated": list(body.model_dump(exclude_none=True).keys())},
    )
    return client


@router.delete("/{client_id}", status_code=204)
async def deactivate_client(
    client_id: str,
    request: Request,
    current_user: User = Depends(require_roles(UserRole.ADMIN)),
    db: AsyncIOMotorDatabase = Depends(get_database),
):
    doc = await _get_client_or_404(client_id, db)
    await db.clients.update_one({"_id": client_id}, {"$set": {"is_active": False, "updated_at": _now()}})
    await audit_logger.log_action(
        action=AuditAction.CLIENT_DEACTIVATE,
        description=f"Client '{doc['name']}' deactivated",
        user=current_user,
        request=request,
        resource_type="client",
        resource_id=client_id,
        resource_name=doc["name"],
        details={"was_active": doc.get("is_active", True)},
    )


# ---------------------------------------------------------------------------
# PM assignment (Admin only)
# ---------------------------------------------------------------------------

class AssignPMRequest(BaseModel):
    user_id: str


@router.post("/{client_id}/pm", status_code=204)
async def assign_pm(
    client_id: str,
    body: AssignPMRequest,
    request: Request,
    current_user: User = Depends(require_roles(UserRole.ADMIN)),
    db: AsyncIOMotorDatabase = Depends(get_database),
):
    client_doc = await _get_client_or_404(client_id, db)
    user_doc = await db.users.find_one({"_id": body.user_id})
    if not user_doc:
        raise HTTPException(status_code=404, detail="User not found")
    await db.users.update_one(
        {"_id": body.user_id},
        {
            "$addToSet": {"pm_client_ids": client_id},
            "$set": {"role": UserRole.PROJECT_MANAGER.value, "updated_at": _now()},
        },
    )
    await audit_logger.log_action(
        action=AuditAction.CLIENT_PM_ASSIGN,
        description=f"PM '{user_doc.get('email', body.user_id)}' assigned to client '{client_doc['name']}'",
        user=current_user,
        request=request,
        resource_type="client",
        resource_id=client_id,
        resource_name=client_doc["name"],
        details={"pm_user_id": body.user_id, "pm_email": user_doc.get("email")},
    )


@router.delete("/{client_id}/pm/{user_id}", status_code=204)
async def remove_pm(
    client_id: str,
    user_id: str,
    request: Request,
    current_user: User = Depends(require_roles(UserRole.ADMIN)),
    db: AsyncIOMotorDatabase = Depends(get_database),
):
    client_doc = await _get_client_or_404(client_id, db)
    pm_doc = await db.users.find_one({"_id": user_id})
    await db.users.update_one(
        {"_id": user_id},
        {"$pull": {"pm_client_ids": client_id}, "$set": {"updated_at": _now()}},
    )
    # Downgrade role if no more PM assignments
    user_doc = await db.users.find_one({"_id": user_id})
    if user_doc and not user_doc.get("pm_client_ids"):
        await db.users.update_one(
            {"_id": user_id},
            {"$set": {"role": UserRole.CLIENT.value, "updated_at": _now()}},
        )
    await audit_logger.log_action(
        action=AuditAction.CLIENT_PM_REMOVE,
        description=f"PM '{pm_doc.get('email', user_id) if pm_doc else user_id}' removed from client '{client_doc['name']}'",
        user=current_user,
        request=request,
        resource_type="client",
        resource_id=client_id,
        resource_name=client_doc["name"],
        details={"pm_user_id": user_id, "pm_email": pm_doc.get("email") if pm_doc else None},
    )


@router.get("/{client_id}/pm", response_model=list[dict])
async def list_pms(
    client_id: str,
    current_user: User = Depends(require_roles(UserRole.ADMIN)),
    db: AsyncIOMotorDatabase = Depends(get_database),
):
    await _get_client_or_404(client_id, db)
    users = await db.users.find(
        {"pm_client_ids": client_id},
        {"_id": 1, "email": 1, "full_name": 1, "role": 1},
    ).to_list(None)
    return [{"id": str(u["_id"]), "email": u["email"], "full_name": u["full_name"], "role": u["role"]} for u in users]


# ---------------------------------------------------------------------------
# Team endpoints
# ---------------------------------------------------------------------------

@router.get("/{client_id}/teams", response_model=list[Team])
async def list_teams(
    client_id: str,
    current_user: User = Depends(get_current_user),
    db: AsyncIOMotorDatabase = Depends(get_database),
):
    await _get_client_or_404(client_id, db)
    await _assert_client_access(current_user, client_id, db)
    docs = await db.teams.find({"client_id": client_id}).to_list(None)
    return [_team_from_doc(d) for d in docs]


@router.post("/{client_id}/teams", response_model=Team)
async def create_team(
    client_id: str,
    body: TeamCreate,
    request: Request,
    current_user: User = Depends(get_current_user),
    db: AsyncIOMotorDatabase = Depends(get_database),
):
    client_doc = await _get_client_or_404(client_id, db)
    await _assert_pm_or_admin(current_user, client_id, db)
    now = _now()
    team_id = str(ObjectId())
    await db.teams.insert_one({
        "_id": team_id,
        "name": body.name,
        "client_id": client_id,
        "member_user_ids": [],
        "created_at": now,
        "updated_at": now,
    })
    doc = await db.teams.find_one({"_id": team_id})
    team = _team_from_doc(doc)
    await audit_logger.log_action(
        action=AuditAction.CLIENT_TEAM_CREATE,
        description=f"Team '{team.name}' created for client '{client_doc['name']}'",
        user=current_user,
        request=request,
        resource_type="client",
        resource_id=client_id,
        resource_name=client_doc["name"],
        details={"team_id": team_id, "team_name": team.name},
    )
    return team


@router.patch("/{client_id}/teams/{team_id}", response_model=Team)
async def update_team(
    client_id: str,
    team_id: str,
    body: TeamUpdate,
    request: Request,
    current_user: User = Depends(get_current_user),
    db: AsyncIOMotorDatabase = Depends(get_database),
):
    client_doc = await _get_client_or_404(client_id, db)
    await _assert_pm_or_admin(current_user, client_id, db)
    await _get_team_or_404(team_id, client_id, db)
    update = dict(body.model_dump(exclude_none=True).items())
    if not update:
        raise HTTPException(status_code=422, detail="No fields to update")
    update["updated_at"] = _now()
    await db.teams.update_one({"_id": team_id}, {"$set": update})
    doc = await db.teams.find_one({"_id": team_id})
    team = _team_from_doc(doc)
    await audit_logger.log_action(
        action=AuditAction.CLIENT_TEAM_UPDATE,
        description=f"Team '{team.name}' updated for client '{client_doc['name']}'",
        user=current_user,
        request=request,
        resource_type="client",
        resource_id=client_id,
        resource_name=client_doc["name"],
        details={"team_id": team_id, "team_name": team.name, "fields_updated": list(body.model_dump(exclude_none=True).keys())},
    )
    return team


@router.delete("/{client_id}/teams/{team_id}", status_code=204)
async def delete_team(
    client_id: str,
    team_id: str,
    request: Request,
    current_user: User = Depends(get_current_user),
    db: AsyncIOMotorDatabase = Depends(get_database),
):
    client_doc = await _get_client_or_404(client_id, db)
    await _assert_pm_or_admin(current_user, client_id, db)
    team_doc = await _get_team_or_404(team_id, client_id, db)
    await db.teams.delete_one({"_id": team_id})
    await audit_logger.log_action(
        action=AuditAction.CLIENT_TEAM_DELETE,
        description=f"Team '{team_doc['name']}' deleted from client '{client_doc['name']}'",
        user=current_user,
        request=request,
        resource_type="client",
        resource_id=client_id,
        resource_name=client_doc["name"],
        details={"team_id": team_id, "team_name": team_doc["name"]},
    )


# Team membership

class AddMemberRequest(BaseModel):
    user_id: str


@router.post("/{client_id}/teams/{team_id}/members", status_code=204)
async def add_team_member(
    client_id: str,
    team_id: str,
    body: AddMemberRequest,
    request: Request,
    current_user: User = Depends(get_current_user),
    db: AsyncIOMotorDatabase = Depends(get_database),
):
    client_doc = await _get_client_or_404(client_id, db)
    await _assert_pm_or_admin(current_user, client_id, db)
    team_doc = await _get_team_or_404(team_id, client_id, db)
    member_doc = await db.users.find_one({"_id": body.user_id})
    if not member_doc:
        raise HTTPException(status_code=404, detail="User not found")
    # Write to both Team.member_user_ids (legacy) and Membership.team_ids (MT-17)
    await db.teams.update_one(
        {"_id": team_id},
        {"$addToSet": {"member_user_ids": body.user_id}, "$set": {"updated_at": _now()}},
    )
    await db.memberships.update_one(
        {"user_id": body.user_id, "organization_id": client_id},
        {"$addToSet": {"team_ids": team_id}},
    )
    await audit_logger.log_action(
        action=AuditAction.CLIENT_TEAM_MEMBER_ADD,
        description=f"User '{member_doc.get('email', body.user_id)}' added to team '{team_doc['name']}' of client '{client_doc['name']}'",
        user=current_user,
        request=request,
        resource_type="client",
        resource_id=client_id,
        resource_name=client_doc["name"],
        details={"team_id": team_id, "team_name": team_doc["name"], "member_user_id": body.user_id, "member_email": member_doc.get("email")},
    )


@router.delete("/{client_id}/teams/{team_id}/members/{user_id}", status_code=204)
async def remove_team_member(
    client_id: str,
    team_id: str,
    user_id: str,
    request: Request,
    current_user: User = Depends(get_current_user),
    db: AsyncIOMotorDatabase = Depends(get_database),
):
    client_doc = await _get_client_or_404(client_id, db)
    await _assert_pm_or_admin(current_user, client_id, db)
    team_doc = await _get_team_or_404(team_id, client_id, db)
    member_doc = await db.users.find_one({"_id": user_id})
    await db.teams.update_one(
        {"_id": team_id},
        {"$pull": {"member_user_ids": user_id}, "$set": {"updated_at": _now()}},
    )
    await db.memberships.update_one(
        {"user_id": user_id, "organization_id": client_id},
        {"$pull": {"team_ids": team_id}},
    )
    await audit_logger.log_action(
        action=AuditAction.CLIENT_TEAM_MEMBER_REMOVE,
        description=f"User '{member_doc.get('email', user_id) if member_doc else user_id}' removed from team '{team_doc['name']}' of client '{client_doc['name']}'",
        user=current_user,
        request=request,
        resource_type="client",
        resource_id=client_id,
        resource_name=client_doc["name"],
        details={"team_id": team_id, "team_name": team_doc["name"], "member_user_id": user_id, "member_email": member_doc.get("email") if member_doc else None},
    )


# ---------------------------------------------------------------------------
# Project endpoints
# ---------------------------------------------------------------------------

@router.get("/all-projects", response_model=list[Project])
async def list_all_projects(
    current_user: User = Depends(get_current_user),
    db: AsyncIOMotorDatabase = Depends(get_database),
):
    """Return all active projects accessible to the current user (across all clients)."""
    if current_user.role in (UserRole.ADMIN, UserRole.PRODUCTION, UserRole.PROJECT_MANAGER):
        docs = await db.projects.find({"is_active": True}).to_list(None)
    else:
        accessible_client_ids = await _get_accessible_client_ids(current_user, db)
        if not accessible_client_ids:
            return []
        docs = await db.projects.find(
            {"client_id": {"$in": accessible_client_ids}, "is_active": True}
        ).to_list(None)
    return [_project_from_doc(d) for d in docs]


@router.get("/{client_id}/projects", response_model=list[Project])
async def list_projects(
    client_id: str,
    current_user: User = Depends(get_current_user),
    db: AsyncIOMotorDatabase = Depends(get_database),
):
    await _get_client_or_404(client_id, db)
    await _assert_client_access(current_user, client_id, db)
    docs = await db.projects.find({"client_id": client_id}).to_list(None)
    return [_project_from_doc(d) for d in docs]


@router.post("/{client_id}/projects", response_model=Project)
async def create_project(
    client_id: str,
    body: ProjectCreate,
    request: Request,
    current_user: User = Depends(get_current_user),
    db: AsyncIOMotorDatabase = Depends(get_database),
):
    client_doc = await _get_client_or_404(client_id, db)
    await _assert_pm_or_client_member(current_user, client_id, db)
    now = _now()
    project_id = str(ObjectId())
    await db.projects.insert_one({
        "_id": project_id,
        "name": body.name,
        "client_id": client_id,
        "is_active": True,
        "default_languages": body.default_languages,
        "default_linguist_id": body.default_linguist_id,
        "default_reviewer_id": body.default_reviewer_id,
        "created_at": now,
        "updated_at": now,
    })
    doc = await db.projects.find_one({"_id": project_id})
    project = _project_from_doc(doc)
    await audit_logger.log_action(
        action=AuditAction.CLIENT_PROJECT_CREATE,
        description=f"Project '{project.name}' created for client '{client_doc['name']}'",
        user=current_user,
        request=request,
        resource_type="client",
        resource_id=client_id,
        resource_name=client_doc["name"],
        details={"project_id": project_id, "project_name": project.name, "default_languages": body.default_languages},
    )
    return project


@router.patch("/{client_id}/projects/{project_id}", response_model=Project)
async def update_project(
    client_id: str,
    project_id: str,
    body: ProjectUpdate,
    request: Request,
    current_user: User = Depends(get_current_user),
    db: AsyncIOMotorDatabase = Depends(get_database),
):
    client_doc = await _get_client_or_404(client_id, db)
    await _assert_pm_or_admin(current_user, client_id, db)
    await _get_project_or_404(project_id, client_id, db)
    update = dict(body.model_dump(exclude_none=True).items())
    if not update:
        raise HTTPException(status_code=422, detail="No fields to update")
    update["updated_at"] = _now()
    await db.projects.update_one({"_id": project_id}, {"$set": update})
    doc = await db.projects.find_one({"_id": project_id})
    project = _project_from_doc(doc)
    await audit_logger.log_action(
        action=AuditAction.CLIENT_PROJECT_UPDATE,
        description=f"Project '{project.name}' updated for client '{client_doc['name']}'",
        user=current_user,
        request=request,
        resource_type="client",
        resource_id=client_id,
        resource_name=client_doc["name"],
        details={"project_id": project_id, "project_name": project.name, "fields_updated": list(body.model_dump(exclude_none=True).keys())},
    )
    return project


@router.delete("/{client_id}/projects/{project_id}", status_code=204)
async def archive_project(
    client_id: str,
    project_id: str,
    request: Request,
    current_user: User = Depends(get_current_user),
    db: AsyncIOMotorDatabase = Depends(get_database),
):
    client_doc = await _get_client_or_404(client_id, db)
    await _assert_pm_or_admin(current_user, client_id, db)
    project_doc = await _get_project_or_404(project_id, client_id, db)
    await db.projects.update_one(
        {"_id": project_id},
        {"$set": {"is_active": False, "updated_at": _now()}},
    )
    await audit_logger.log_action(
        action=AuditAction.CLIENT_PROJECT_ARCHIVE,
        description=f"Project '{project_doc['name']}' archived for client '{client_doc['name']}'",
        user=current_user,
        request=request,
        resource_type="client",
        resource_id=client_id,
        resource_name=client_doc["name"],
        details={"project_id": project_id, "project_name": project_doc["name"]},
    )


# ---------------------------------------------------------------------------
# Internal access-check helpers
# ---------------------------------------------------------------------------

async def _assert_pm_or_admin(user: User, client_id: str, db: AsyncIOMotorDatabase) -> None:
    if user.role == UserRole.ADMIN:
        return
    # Check memberships collection (Phase 3)
    mem = await db.memberships.find_one({"user_id": str(user.id), "organization_id": client_id})
    if mem and mem.get("role_in_org") in ("owner", "admin", "manager"):
        return
    # Legacy fallback
    if user.role == UserRole.PROJECT_MANAGER and client_id in (user.pm_client_ids or []):
        return
    raise HTTPException(status_code=403, detail="Not a manager for this client")


async def _assert_pm_or_client_member(user: User, client_id: str, db: AsyncIOMotorDatabase) -> None:
    """Allow PM/ADMIN/PROD or any org member (CLIENT role) with membership in this client's org."""
    if user.role in (UserRole.ADMIN, UserRole.PRODUCTION):
        return
    if user.role == UserRole.PROJECT_MANAGER:
        if client_id in (user.pm_client_ids or []):
            return
        mem = await db.memberships.find_one({"user_id": str(user.id), "organization_id": client_id})
        if mem and mem.get("role_in_org") in ("owner", "admin", "manager"):
            return
    # Allow CLIENT users who are members of the org
    if user.role == UserRole.CLIENT:
        mem = await db.memberships.find_one({"user_id": str(user.id), "organization_id": client_id})
        if mem:
            return
    raise HTTPException(status_code=403, detail="Not authorized to create projects for this client")


async def _get_accessible_client_ids(user: User, db: AsyncIOMotorDatabase) -> list[str]:
    """Return list of client_ids the user can access."""
    ids: set[str] = set()
    # PM assignments (legacy)
    if user.pm_client_ids:
        ids.update(user.pm_client_ids)
    # Org memberships
    mems = await db.memberships.find({"user_id": str(user.id)}).to_list(None)
    for m in mems:
        ids.add(m["organization_id"])
    return list(ids)


async def _assert_client_access(user: User, client_id: str, db: AsyncIOMotorDatabase) -> None:
    """Allow platform staff, org members (any role), or PM of the client."""
    if user.role in (UserRole.ADMIN, UserRole.REVIEWER, UserRole.PRODUCTION, UserRole.LINGUIST):
        return
    # Any org membership grants read access
    mem = await db.memberships.find_one({"user_id": str(user.id), "organization_id": client_id})
    if mem:
        return
    # Legacy fallback for pre-migration users
    if user.role == UserRole.PROJECT_MANAGER and client_id in (user.pm_client_ids or []):
        return
    raise HTTPException(status_code=403, detail="Insufficient permissions")
