"""User management routes."""

from __future__ import annotations

import uuid
from typing import Any

from fastapi import APIRouter, Depends, HTTPException, Query, Response, status
from sqlalchemy.ext.asyncio import AsyncSession

from app.core.audit import log_audit
from app.core.auth import get_redis
from app.core.db import get_db
from app.core.notifications import create_notification
from app.core.permissions import invalidate_permission_cache
from app.deps import require_permission
from app.schemas.user import UserCreate, UserUpdate, UserResponse, PaginatedUsers
from app.services.user_service import user_service, _UNSET

router = APIRouter(prefix="/api/v1/users", tags=["users"])


def _parse_role_id(raw: str | None) -> uuid.UUID | None:
    """Convert a string body value into a UUID or None.

    Empty string and None are both treated as "clear role_id".
    """
    if raw is None or raw == "":
        return None
    try:
        return uuid.UUID(raw)
    except (ValueError, AttributeError):
        raise HTTPException(
            400,
            detail={"detail": "Invalid role_id", "code": "invalid_role_id"},
        ) from None


@router.get("", response_model=PaginatedUsers)
async def list_users(
    page: int = Query(1, ge=1),
    page_size: int = Query(25, ge=1, le=100),
    search: str | None = Query(None),
    db: AsyncSession = Depends(get_db),
    current_user: dict = Depends(require_permission("users:read")),
):
    """List users (admin only, paginated)."""
    tenant_id = uuid.UUID(current_user["tenant_id"])
    return await user_service.list_users(db, tenant_id, page, page_size, search)


@router.post("", status_code=status.HTTP_201_CREATED, response_model=UserResponse)
async def create_user(
    body: UserCreate,
    db: AsyncSession = Depends(get_db),
    current_user: dict = Depends(require_permission("users:write")),
):
    """Create a new user (admin only)."""
    tenant_id = uuid.UUID(current_user["tenant_id"])
    user_id = uuid.UUID(current_user["user_id"])
    role_id = _parse_role_id(body.role_id)

    user = await user_service.create_user(
        db,
        tenant_id,
        body.email,
        body.name,
        body.password,
        body.role,
        role_id,
        body.is_active,
    )

    # Audit log
    await log_audit(
        db,
        tenant_id,
        user_id,
        "create",
        "user",
        user.id,
        changes={"email": body.email, "name": body.name, "role": body.role, "role_id": body.role_id},
    )

    # Notification
    await create_notification(
        db,
        tenant_id,
        user.id,
        "info",
        "Account created",
        f"Your account has been created by {current_user['name']}.",
    )

    return {
        "id": str(user.id),
        "email": user.email,
        "name": user.name,
        "role": user.role,
        "role_id": str(user.role_id) if user.role_id else None,
        "is_active": user.is_active,
        "tenant_id": str(user.tenant_id),
    }


@router.get("/{user_id}", response_model=UserResponse)
async def get_user(
    user_id: str,
    db: AsyncSession = Depends(get_db),
    current_user: dict = Depends(require_permission("users:read")),
):
    """Get a single user."""
    tenant_id = uuid.UUID(current_user["tenant_id"])
    try:
        uid = uuid.UUID(user_id)
    except ValueError:
        raise HTTPException(
            400, detail={"detail": "Invalid user_id", "code": "invalid_id"}
        ) from None

    user = await user_service.get_user(db, tenant_id, uid)
    if user is None:
        raise HTTPException(404, detail={"detail": "User not found", "code": "not_found"})

    return {
        "id": str(user.id),
        "email": user.email,
        "name": user.name,
        "role": user.role,
        "role_id": str(user.role_id) if user.role_id else None,
        "is_active": user.is_active,
        "tenant_id": str(user.tenant_id),
    }


@router.patch("/{user_id}")
async def update_user(
    user_id: str,
    body: UserUpdate,
    db: AsyncSession = Depends(get_db),
    current_user: dict = Depends(require_permission("users:write")),
):
    """Update a user (admin only).

    Uses ``model_fields_set`` to detect whether ``role_id`` was explicitly
    present in the request body (even if sent as ``null``).  This allows
    the caller to clear the FK by sending ``role_id: null``.

    Self-modification prevention: a user cannot change their own role,
    is_active status, or system_admin flag.
    """
    tenant_id = uuid.UUID(current_user["tenant_id"])
    acting_user_id = uuid.UUID(current_user["user_id"])
    try:
        uid = uuid.UUID(user_id)
    except ValueError:
        raise HTTPException(
            400, detail={"detail": "Invalid user_id", "code": "invalid_id"}
        ) from None

    # Self-modification prevention: cannot change own role or active status
    if uid == acting_user_id:
        if body.role is not None or body.is_active is not None or "role_id" in body.model_fields_set:
            raise HTTPException(
                status_code=status.HTTP_403_FORBIDDEN,
                detail={"detail": "Cannot modify your own role or active status", "code": "self_modification_forbidden"},
            )

    # Determine if role_id was explicitly sent (Pydantic v2)
    role_id_sent = "role_id" in body.model_fields_set

    changes: dict[str, Any] = {}
    if body.name is not None:
        changes["name"] = body.name
    if body.role is not None:
        changes["role"] = body.role
    if role_id_sent:
        changes["role_id"] = body.role_id
    if body.is_active is not None:
        changes["is_active"] = body.is_active

    # Pass _UNSET sentinel when role_id was not in the request body
    # so the service leaves the existing value untouched.
    role_id: uuid.UUID | None | Any
    if role_id_sent:
        role_id = _parse_role_id(body.role_id)
    else:
        role_id = _UNSET

    # Handle profile fields
    if body.first_name is not None:
        changes["first_name"] = body.first_name
    if body.last_name is not None:
        changes["last_name"] = body.last_name
    if body.email is not None:
        changes["email"] = body.email
    if body.avatar_url is not None:
        changes["avatar_url"] = body.avatar_url
    if body.new_password is not None:
        changes["password_changed"] = True

    try:
        user = await user_service.update_user(
            db,
            tenant_id,
            uid,
            body.name,
            body.role,
            role_id,
            body.is_active,
            body.first_name,
            body.last_name,
            body.avatar_url,
            body.email,
            body.current_password,
            body.new_password,
        )
    except ValueError as exc:
        raise HTTPException(400, detail={"detail": str(exc), "code": "invalid_password"}) from None
    if user is None:
        raise HTTPException(404, detail={"detail": "User not found", "code": "not_found"})

    await log_audit(db, tenant_id, acting_user_id, "update", "user", uid, changes=changes)

    # Invalidate permission cache for the updated user
    redis = get_redis()
    await invalidate_permission_cache(redis, uid, tenant_id)

    return {
        "id": str(user.id),
        "email": user.email,
        "name": user.name,
        "first_name": user.first_name,
        "last_name": user.last_name,
        "avatar_url": user.avatar_url,
        "role": user.role,
        "role_id": str(user.role_id) if user.role_id else None,
        "is_active": user.is_active,
        "tenant_id": str(user.tenant_id),
    }


@router.delete("/{user_id}")
async def delete_user(
    user_id: str,
    db: AsyncSession = Depends(get_db),
    current_user: dict = Depends(require_permission("users:write")),
):
    """Delete a user (admin only)."""
    tenant_id = uuid.UUID(current_user["tenant_id"])
    acting_user_id = uuid.UUID(current_user["user_id"])
    try:
        uid = uuid.UUID(user_id)
    except ValueError:
        raise HTTPException(
            400, detail={"detail": "Invalid user_id", "code": "invalid_id"}
        ) from None

    # Get user snapshot for audit before deletion
    user = await user_service.get_user(db, tenant_id, uid)
    if user is None:
        raise HTTPException(404, detail={"detail": "User not found", "code": "not_found"})

    success = await user_service.delete_user(db, tenant_id, uid)
    if not success:
        raise HTTPException(404, detail={"detail": "User not found", "code": "not_found"})

    await log_audit(
        db,
        tenant_id,
        acting_user_id,
        "delete",
        "user",
        uid,
        changes={"email": user.email, "name": user.name},
    )

    return Response(status_code=status.HTTP_204_NO_CONTENT)
