"""Admin-only user-management endpoints for the CMS.

Mounted at ``/api/v1/admin/users``. Every route requires an admin-role JWT
via :func:`dependencies.auth.get_admin_user`. Works against both local and
platform/OIDC users, but password-related actions are only meaningful on
local accounts.

Local-auth-only fields (``password_hash``, ``auth_provider``, ``is_active``)
live on :class:`LocalAuthProfile` keyed by ``user_id``; the shared ``users``
table only stores identity fields.
"""

from __future__ import annotations

import logging
from typing import Optional

from core.database import get_db
from dependencies.auth import get_admin_user
from fastapi import APIRouter, Depends, HTTPException, Query, Request, status
from models.auth import LocalAuthProfile, User
from schemas.admin_users import (
    AdminResetPasswordRequest,
    AdminUpdateActiveRequest,
    AdminUpdateRoleRequest,
    AdminUserItem,
    AdminUserListResponse,
    SimpleMessageResponse,
)
from schemas.auth import UserResponse
from services.audit_log import (
    ACTION_ACTIVE_CHANGED,
    ACTION_ADMIN_PASSWORD_RESET,
    ACTION_ROLE_CHANGED,
    record_admin_event,
)
from services.local_auth import LocalAuthService, get_or_create_profile, get_profile
from sqlalchemy import func, or_, select
from sqlalchemy.ext.asyncio import AsyncSession

logger = logging.getLogger(__name__)

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


async def _get_user_or_404(db: AsyncSession, user_id: str) -> User:
    result = await db.execute(select(User).where(User.id == user_id))
    user = result.scalar_one_or_none()
    if user is None:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found")
    return user


def _to_item(user: User, profile: Optional[LocalAuthProfile]) -> AdminUserItem:
    """Combine a User row and its (optional) LocalAuthProfile into the CMS DTO."""
    return AdminUserItem(
        id=user.id,
        email=user.email,
        name=user.name,
        role=user.role,
        auth_provider=(profile.auth_provider if profile is not None else "platform"),
        is_active=(profile.is_active if profile is not None else True),
        last_login=user.last_login,
        created_at=user.created_at,
    )


@router.get("", response_model=AdminUserListResponse)
async def list_users(
    search: Optional[str] = Query(default=None, description="Filter by email or name (case-insensitive)"),
    role: Optional[str] = Query(default=None, pattern="^(admin|user)$"),
    provider: Optional[str] = Query(default=None, pattern="^(local|platform)$"),
    page: int = Query(default=1, ge=1),
    page_size: int = Query(default=50, ge=1, le=200),
    db: AsyncSession = Depends(get_db),
    _admin: UserResponse = Depends(get_admin_user),
) -> AdminUserListResponse:
    """List local + platform users for the admin CMS, with search/filter/pagination."""
    # Outer join so users without a LocalAuthProfile still appear.
    base_stmt = select(User, LocalAuthProfile).outerjoin(
        LocalAuthProfile, LocalAuthProfile.user_id == User.id
    )
    count_stmt = select(func.count()).select_from(
        User.__table__.outerjoin(
            LocalAuthProfile.__table__,
            LocalAuthProfile.user_id == User.id,
        )
    )

    filters = []
    if search:
        pattern = f"%{search.strip().lower()}%"
        filters.append(
            or_(func.lower(User.email).like(pattern), func.lower(User.name).like(pattern))
        )
    if role:
        filters.append(User.role == role)
    if provider:
        # Treat missing profile as 'platform' to stay backward compatible.
        if provider == "platform":
            filters.append(
                or_(
                    LocalAuthProfile.auth_provider == "platform",
                    LocalAuthProfile.auth_provider.is_(None),
                )
            )
        else:
            filters.append(LocalAuthProfile.auth_provider == provider)

    for f in filters:
        base_stmt = base_stmt.where(f)
        count_stmt = count_stmt.where(f)

    total_result = await db.execute(count_stmt)
    total = int(total_result.scalar() or 0)

    base_stmt = (
        base_stmt.order_by(User.created_at.desc().nullslast())
        .offset((page - 1) * page_size)
        .limit(page_size)
    )
    result = await db.execute(base_stmt)
    rows = result.all()

    return AdminUserListResponse(
        items=[_to_item(user, profile) for user, profile in rows],
        total=total,
        page=page,
        page_size=page_size,
    )


@router.patch("/{user_id}/role", response_model=AdminUserItem)
async def update_user_role(
    user_id: str,
    payload: AdminUpdateRoleRequest,
    request: Request,
    db: AsyncSession = Depends(get_db),
    admin: UserResponse = Depends(get_admin_user),
) -> AdminUserItem:
    """Promote to admin or demote back to user."""
    user = await _get_user_or_404(db, user_id)
    if admin.id == user.id and payload.role != "admin":
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="You cannot demote yourself. Ask another admin to do this.",
        )
    previous_role = user.role
    user.role = payload.role
    await db.commit()
    await db.refresh(user)
    profile = await get_profile(db, user.id)
    logger.info("[admin_users] %s changed role of %s to %s", admin.email, user.email, payload.role)
    await record_admin_event(
        db,
        action=ACTION_ROLE_CHANGED,
        admin_user_id=admin.id,
        admin_email=admin.email,
        target_user_id=user.id,
        target_email=user.email,
        details={"previous_role": previous_role, "new_role": payload.role},
        request=request,
    )
    return _to_item(user, profile)


@router.patch("/{user_id}/active", response_model=AdminUserItem)
async def update_user_active(
    user_id: str,
    payload: AdminUpdateActiveRequest,
    request: Request,
    db: AsyncSession = Depends(get_db),
    admin: UserResponse = Depends(get_admin_user),
) -> AdminUserItem:
    """Enable or disable a user account. Disabled accounts cannot log in."""
    user = await _get_user_or_404(db, user_id)
    if admin.id == user.id and payload.is_active is False:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="You cannot disable your own account.",
        )
    profile = await get_or_create_profile(db, user.id)
    previous_active = bool(profile.is_active)
    profile.is_active = payload.is_active
    await db.commit()
    await db.refresh(user)
    profile = await get_profile(db, user.id)
    logger.info(
        "[admin_users] %s set is_active=%s on %s", admin.email, payload.is_active, user.email
    )
    await record_admin_event(
        db,
        action=ACTION_ACTIVE_CHANGED,
        admin_user_id=admin.id,
        admin_email=admin.email,
        target_user_id=user.id,
        target_email=user.email,
        details={"previous_active": previous_active, "new_active": bool(payload.is_active)},
        request=request,
    )
    return _to_item(user, profile)


@router.post("/{user_id}/reset-password", response_model=SimpleMessageResponse)
async def admin_reset_password(
    user_id: str,
    payload: AdminResetPasswordRequest,
    request: Request,
    db: AsyncSession = Depends(get_db),
    admin: UserResponse = Depends(get_admin_user),
) -> SimpleMessageResponse:
    """Directly set a new password on a local account (admin override).

    Platform/OIDC users cannot have passwords reset this way — their auth
    lives on the identity provider.
    """
    user = await _get_user_or_404(db, user_id)
    profile = await get_profile(db, user.id)
    if profile is None or profile.auth_provider != "local":
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Only local accounts can have their password reset here.",
        )
    service = LocalAuthService(db)
    try:
        await service.set_password(user, payload.new_password)
    except ValueError as exc:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(exc))
    logger.info("[admin_users] %s reset password for %s", admin.email, user.email)
    await record_admin_event(
        db,
        action=ACTION_ADMIN_PASSWORD_RESET,
        admin_user_id=admin.id,
        admin_email=admin.email,
        target_user_id=user.id,
        target_email=user.email,
        details={"auth_provider": profile.auth_provider},
        request=request,
    )
    return SimpleMessageResponse(detail="Password updated")