"""
User service routes for Memos-style backend.
"""

from typing import List

from fastapi import APIRouter, Body, Depends, HTTPException, Query
from starlette.requests import Request

from common.encryption import mask_api_key, validate_openai_key_format
from common.memos_core.schemas import (
    UserCreate,
    UserUpdate,
    UserResponse,
    PersonalAccessTokenCreate,
    PersonalAccessTokenResponse,
    WebhookCreate,
    WebhookUpdate,
    WebhookResponse,
)
from common.memos_core.services import get_user_service, get_user_settings_service, get_personal_access_token_service, get_webhook_service, get_memo_service
from common.mongodb import mongodb_client


router = APIRouter(prefix="/users")


@router.get("", summary="List users", response_model=List[UserResponse])
async def list_users(user_service=Depends(get_user_service)):
    try:
        return await user_service.list_users()
    except Exception as exc:  # pragma: no cover
        raise HTTPException(status_code=500, detail=str(exc)) from exc


@router.post("", summary="Create user", response_model=UserResponse)
async def create_user(
    payload: dict = Body(default_factory=dict),
    user_service=Depends(get_user_service),
):
    try:
        # Accept both REST payload and Connect/proto-like envelope.
        # REST: { "email": "...", "password": "..." }
        # Possible envelope: { "user": { ... } } or other shapes.
        raw = payload.get("user") if isinstance(payload, dict) else None
        raw = raw if isinstance(raw, dict) else payload
        user_create = UserCreate(**(raw or {}))
        return await user_service.create_user(user_create)
    except Exception as exc:  # pragma: no cover
        raise HTTPException(status_code=400, detail=str(exc)) from exc


@router.post("/settings", summary="List user settings (Connect compatibility)")
async def list_user_settings_connect_compat(payload: dict = Body(default_factory=dict)):  # noqa: B008
    """
    Connect RPC UserService/ListUserSettings is proxied as:
      POST /api/v1/users/settings

    The real Memos API returns a ListUserSettingsResponse with `settings`, `next_page_token`, `total_size`.
    For stub backend, return empty list to let frontend proceed.
    """
    _ = payload
    return {
        "settings": [],
        "next_page_token": "",
        "total_size": 0,
    }


@router.get("/{user_id}/stats", summary="Get user stats (tags, activity)")
async def get_user_stats(
    user_id: str,
    request: Request,
    workspace_id: str | None = Query(default=None, description="Optional workspace filter (PERSONAL, AI_SALES_CRM)"),
    user_service=Depends(get_user_service),
):
    """
    Get user statistics including:
    - tagCount: aggregated tag counts from all user's memos (optionally filtered by workspace)
    - memoDisplayTimestamps: list of memo created_at timestamps for activity calendar
    """
    try:
        stats = await user_service.get_user_stats(user_id, workspace_id=workspace_id)
        return {
            "tagCount": stats.tag_count,
            "memoDisplayTimestamps": stats.memo_display_timestamps,
        }
    except Exception as exc:  # pragma: no cover
        raise HTTPException(status_code=500, detail=str(exc)) from exc


@router.get("/{user_id}", summary="Get user by ID")
async def get_user(user_id: str, request: Request, user_service=Depends(get_user_service)):
    """
    Get user by ID. Returns a User object matching the proto format.
    For the current user (user_id==1 or me), returns data from auth session.
    """
    try:
        # Get user data from auth middleware - request.state.user contains Clerk JWT payload
        user_payload = getattr(request.state, "user", None)
        
        # Default values for when no auth is available (DISABLE_AUTH mode)
        username = "user"
        display_name = ""
        avatar_url = ""
        email = ""
        
        if user_payload and isinstance(user_payload, dict):
            # Extract from Clerk JWT claims
            username = user_payload.get("username") or user_payload.get("preferred_username") or ""
            first_name = user_payload.get("first_name") or user_payload.get("given_name") or ""
            last_name = user_payload.get("last_name") or user_payload.get("family_name") or ""
            avatar_url = user_payload.get("image_url") or user_payload.get("picture") or ""
            email = user_payload.get("email") or ""
            
            # Build display name
            display_name = f"{first_name} {last_name}".strip()
            
            # Fallback username from email if not set
            if not username and email:
                username = email.split("@")[0]
        
        # Ensure we always have valid strings (never 'undefined')
        username = username if username else "user"
        display_name = display_name if display_name else username
        
        # Return proto-compatible User format
        return {
            "name": f"users/{user_id}",
            "role": 1,  # HOST
            "username": username,
            "email": email,
            "displayName": display_name,
            "avatarUrl": avatar_url,
            "description": "",
            "password": "",
            "state": 1,  # ACTIVE
        }
    except Exception as exc:  # pragma: no cover
        raise HTTPException(status_code=404, detail=str(exc)) from exc


@router.patch("/{user_id}", summary="Update user", response_model=UserResponse)
async def update_user(
    user_id: str,
    payload: UserUpdate,
    user_service=Depends(get_user_service),
):
    try:
        return await user_service.update_user(user_id, payload)
    except Exception as exc:  # pragma: no cover
        raise HTTPException(status_code=400, detail=str(exc)) from exc


# =============================================================================
# OpenAI API Key Management Endpoints
# =============================================================================

@router.get("/{user_id}/openai-key", summary="Get user's OpenAI API key (masked)")
async def get_user_openai_key(
    user_id: str,
    request: Request,
    settings_service=Depends(get_user_settings_service),
):
    """
    Get user's OpenAI API key (masked for security).
    User can only access their own key.
    """
    # Verify user can only access their own key
    authenticated_user_id = getattr(request.state, "user_id", None)
    if not authenticated_user_id or authenticated_user_id != user_id:
        raise HTTPException(status_code=403, detail="You can only access your own API key")
    
    try:
        has_key = await settings_service.has_openai_api_key(user_id)
        if not has_key:
            return {"has_key": False, "masked_key": None}
        
        # Get the key to mask it
        key = await settings_service.get_openai_api_key(user_id)
        if key:
            return {
                "has_key": True,
                "masked_key": mask_api_key(key),
            }
        return {"has_key": False, "masked_key": None}
    except Exception as exc:
        raise HTTPException(status_code=500, detail=str(exc)) from exc


@router.put("/{user_id}/openai-key", summary="Update user's OpenAI API key")
async def update_user_openai_key(
    user_id: str,
    request: Request,
    payload: dict = Body(...),
    settings_service=Depends(get_user_settings_service),
):
    """
    Update user's OpenAI API key.
    Key will be encrypted before storage.
    User can only update their own key.
    """
    # Verify user can only update their own key
    authenticated_user_id = getattr(request.state, "user_id", None)
    if not authenticated_user_id or authenticated_user_id != user_id:
        raise HTTPException(status_code=403, detail="You can only update your own API key")
    
    api_key = payload.get("api_key") or payload.get("key")
    if not api_key:
        raise HTTPException(status_code=400, detail="api_key is required")
    
    # Validate key format
    if not validate_openai_key_format(api_key):
        raise HTTPException(
            status_code=400,
            detail="Invalid OpenAI API key format. Key must start with 'sk-' and be 20-100 characters."
        )
    
    try:
        await settings_service.set_openai_api_key(user_id, api_key)
        return {
            "success": True,
            "message": "OpenAI API key updated successfully",
            "masked_key": mask_api_key(api_key),
        }
    except ValueError as exc:
        raise HTTPException(status_code=400, detail=str(exc)) from exc
    except Exception as exc:
        raise HTTPException(status_code=500, detail=str(exc)) from exc


@router.delete("/{user_id}/openai-key", summary="Delete user's OpenAI API key")
async def delete_user_openai_key(
    user_id: str,
    request: Request,
    settings_service=Depends(get_user_settings_service),
):
    """
    Delete user's OpenAI API key.
    User can only delete their own key.
    """
    # Verify user can only delete their own key
    authenticated_user_id = getattr(request.state, "user_id", None)
    if not authenticated_user_id or authenticated_user_id != user_id:
        raise HTTPException(status_code=403, detail="You can only delete your own API key")
    
    try:
        await settings_service.delete_openai_api_key(user_id)
        return {"success": True, "message": "OpenAI API key deleted successfully"}
    except Exception as exc:
        raise HTTPException(status_code=500, detail=str(exc)) from exc


# ============================================================================
# User Notifications (Inbox) Routes
# ============================================================================

from common.memos_core.services import get_notification_service
from common.memos_core.schemas import NotificationResponse


@router.post("/notifications:list", summary="List user notifications (Connect RPC style)")
async def list_user_notifications(
    request: Request,
    notification_service=Depends(get_notification_service),
):
    """
    List all notifications for the current user.
    Connect RPC style: POST /users/notifications:list
    Returns notifications for the Inbox page.
    """
    try:
        # Get current user ID from auth middleware  
        user_id = getattr(request.state, "user_id", None) or "1"
        
        notifications = await notification_service.list_notifications(user_id)
        
        # Return in Connect RPC format matching frontend expectations
        return {
            "notifications": [n.model_dump() for n in notifications]
        }
    except Exception as exc:  # pragma: no cover
        raise HTTPException(status_code=500, detail=str(exc)) from exc


@router.patch("/notifications/{name:path}", summary="Update notification status")
async def update_user_notification(
    name: str,
    payload: dict = Body(default_factory=dict),
    notification_service=Depends(get_notification_service),
):
    """
    Update a notification (e.g., mark as READ or ARCHIVED).
    Name format: notifications/{id} or just the ID number.
    """
    import re
    try:
        # Extract notification ID from name
        if name.startswith("notifications/"):
            name = name[len("notifications/"):]
        
        match = re.match(r"^(\d+)$", name)
        if not match:
            raise HTTPException(status_code=400, detail=f"Invalid notification name: {name}")
        
        notification_id = int(match.group(1))
        
        # Get status from payload (Connect RPC format: { notification: { status: "..." } })
        notification_data = payload.get("notification", payload)
        new_status = notification_data.get("status", "READ")
        
        notification = await notification_service.update_notification(notification_id, new_status)
        
        if not notification:
            raise HTTPException(status_code=404, detail=f"Notification {notification_id} not found")
        
        return notification.model_dump()
    except HTTPException:
        raise
    except Exception as exc:  # pragma: no cover
        raise HTTPException(status_code=500, detail=str(exc)) from exc


@router.delete("/notifications/{name:path}", summary="Delete notification")
async def delete_user_notification(
    name: str,
    notification_service=Depends(get_notification_service),
):
    """
    Delete a notification.
    Name format: notifications/{id} or just the ID number.
    """
    import re
    try:
        # Extract notification ID from name
        if name.startswith("notifications/"):
            name = name[len("notifications/"):]
        
        match = re.match(r"^(\d+)$", name)
        if not match:
            raise HTTPException(status_code=400, detail=f"Invalid notification name: {name}")
        
        notification_id = int(match.group(1))
        
        deleted = await notification_service.delete_notification(notification_id)
        
        if not deleted:
            raise HTTPException(status_code=404, detail=f"Notification {notification_id} not found")
        
        return {"success": True}
    except HTTPException:
        raise
    except Exception as exc:  # pragma: no cover
        raise HTTPException(status_code=500, detail=str(exc)) from exc


# ============================================================================
# Personal Access Tokens (PAT) Routes
# ============================================================================

@router.post("/{user_id}/personalAccessTokens", summary="Create personal access token")
async def create_personal_access_token(
    user_id: str,
    request: Request,
    payload: PersonalAccessTokenCreate,
    pat_service=Depends(get_personal_access_token_service),
):
    authenticated_user_id = getattr(request.state, "user_id", None)
    if not authenticated_user_id or authenticated_user_id != user_id:
        raise HTTPException(status_code=403, detail="You can only create tokens for yourself")
    try:
        token = await pat_service.create_token(user_id, payload.description, payload.expires_at)
        return token
    except Exception as exc:
        raise HTTPException(status_code=400, detail=str(exc)) from exc


@router.get("/{user_id}/personalAccessTokens", summary="List personal access tokens")
async def list_personal_access_tokens(
    user_id: str,
    request: Request,
    pat_service=Depends(get_personal_access_token_service),
):
    authenticated_user_id = getattr(request.state, "user_id", None)
    if not authenticated_user_id or authenticated_user_id != user_id:
        raise HTTPException(status_code=403, detail="You can only list your own tokens")
    try:
        tokens = await pat_service.list_tokens(user_id)
        return tokens
    except Exception as exc:
        raise HTTPException(status_code=500, detail=str(exc)) from exc


@router.delete("/{user_id}/personalAccessTokens/{token_id}", summary="Delete personal access token")
async def delete_personal_access_token(
    user_id: str,
    token_id: str,
    request: Request,
    pat_service=Depends(get_personal_access_token_service),
):
    authenticated_user_id = getattr(request.state, "user_id", None)
    if not authenticated_user_id or authenticated_user_id != user_id:
        raise HTTPException(status_code=403, detail="You can only delete your own tokens")
    try:
        deleted = await pat_service.delete_token(user_id, token_id)
        if not deleted:
            raise HTTPException(status_code=404, detail=f"Token {token_id} not found")
        return {"success": True}
    except HTTPException:
        raise
    except Exception as exc:
        raise HTTPException(status_code=500, detail=str(exc)) from exc


# ============================================================================
# Webhook Routes
# ============================================================================

@router.get("/{user_id}/webhooks", summary="List user webhooks")
async def list_webhooks(
    user_id: str,
    request: Request,
    webhook_service=Depends(get_webhook_service),
):
    authenticated_user_id = getattr(request.state, "user_id", None)
    if not authenticated_user_id or authenticated_user_id != user_id:
        raise HTTPException(status_code=403, detail="You can only list your own webhooks")
    try:
        webhooks = await webhook_service.list_webhooks(user_id)
        return webhooks
    except Exception as exc:
        raise HTTPException(status_code=500, detail=str(exc)) from exc


@router.post("/{user_id}/webhooks", summary="Create webhook")
async def create_webhook(
    user_id: str,
    request: Request,
    payload: WebhookCreate,
    webhook_service=Depends(get_webhook_service),
):
    authenticated_user_id = getattr(request.state, "user_id", None)
    if not authenticated_user_id or authenticated_user_id != user_id:
        raise HTTPException(status_code=403, detail="You can only create webhooks for yourself")
    try:
        webhook = await webhook_service.create_webhook(user_id, payload)
        return webhook
    except Exception as exc:
        raise HTTPException(status_code=400, detail=str(exc)) from exc


@router.patch("/{user_id}/webhooks/{webhook_id}", summary="Update webhook")
async def update_webhook(
    user_id: str,
    webhook_id: str,
    request: Request,
    payload: WebhookUpdate,
    webhook_service=Depends(get_webhook_service),
):
    authenticated_user_id = getattr(request.state, "user_id", None)
    if not authenticated_user_id or authenticated_user_id != user_id:
        raise HTTPException(status_code=403, detail="You can only update your own webhooks")
    try:
        webhook = await webhook_service.update_webhook(user_id, webhook_id, payload)
        return webhook
    except ValueError as exc:
        raise HTTPException(status_code=404, detail=str(exc)) from exc
    except Exception as exc:
        raise HTTPException(status_code=400, detail=str(exc)) from exc


@router.delete("/{user_id}/webhooks/{webhook_id}", summary="Delete webhook")
async def delete_webhook(
    user_id: str,
    webhook_id: str,
    request: Request,
    webhook_service=Depends(get_webhook_service),
):
    authenticated_user_id = getattr(request.state, "user_id", None)
    if not authenticated_user_id or authenticated_user_id != user_id:
        raise HTTPException(status_code=403, detail="You can only delete your own webhooks")
    try:
        deleted = await webhook_service.delete_webhook(user_id, webhook_id)
        if not deleted:
            raise HTTPException(status_code=404, detail=f"Webhook {webhook_id} not found")
        return {"success": True}
    except HTTPException:
        raise
    except Exception as exc:
        raise HTTPException(status_code=500, detail=str(exc)) from exc



# ============================================================================
# Inbox (CRM) Routes - Unread Memos Management
# ============================================================================

@router.get("/{user_id}/inbox/unread_count", summary="Get count of unread memos")
async def get_inbox_unread_count(
    user_id: str,
    request: Request,
    workspace_id: str | None = Query(default=None, description="Optional workspace filter (PERSONAL, AI_SALES_CRM)"),
):
    """
    Get the number of unread memos for the user.
    Used for badge count in the Inbox tab.
    """
    try:
        # Verify user can only access their own inbox
        authenticated_user_id = getattr(request.state, "user_id", None)
        if not authenticated_user_id or authenticated_user_id != user_id:
            # For simplicity in dev mode, allow if DISABLE_AUTH
            from config import DISABLE_AUTH
            if not DISABLE_AUTH:
                raise HTTPException(status_code=403, detail="You can only access your own inbox")

        query = "SELECT COUNT(*) as count FROM inbox WHERE user_id = ? AND is_read = ?"
        params = [user_id, 0]
        if workspace_id:
            query = "SELECT COUNT(*) as count FROM inbox WHERE user_id = ? AND is_read = ? AND message_type = ?"
            params.append(workspace_id)

        row = await mongodb_client.fetch_one(query, tuple(params))
        count = row["count"] if row else 0
        return {"unread_count": count}
    except Exception as exc:  # pragma: no cover
        raise HTTPException(status_code=500, detail=str(exc)) from exc


@router.get("/{user_id}/inbox/memos", summary="List unread memos (Inbox)")
async def list_inbox_unread_memos(
    user_id: str,
    request: Request,
    workspace_id: str | None = Query(default=None, description="Optional workspace filter"),
):
    """
    List unread memos for the user.
    These are memos with is_read=False.
    """
    try:
        # Verify user access
        authenticated_user_id = getattr(request.state, "user_id", None)
        if not authenticated_user_id or authenticated_user_id != user_id:
            from config import DISABLE_AUTH
            if not DISABLE_AUTH:
                raise HTTPException(status_code=403, detail="You can only access your own inbox")

        memos = await get_memo_service().list_memos(
            user_id=user_id,
            workspace_id=workspace_id,
            is_read=False,
        )
        return memos
    except Exception as exc:  # pragma: no cover
        raise HTTPException(status_code=500, detail=str(exc)) from exc
