"""
User Profile Router — all endpoints are mounted under /api/v1/users/me/...
"""
from __future__ import annotations

from typing import Optional

from fastapi import APIRouter, Depends, File, Query, Request, UploadFile, status
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
from sqlalchemy.orm import Session

from src.apps.auth.utils.auth import get_current_active_user, get_current_merchant
from src.apps.user_profile import services
from src.apps.user_profile.schemas.profile_schemas import (
    AvatarUrlResponse,
    ChangePasswordRequest,
    ChangePasswordResponse,
    EmailChangeInitiateRequest,
    EmailChangeInitiateResponse,
    NotificationPrefsRequest,
    NotificationPrefsResponse,
    ProfileResponse,
    ProfileUpdateRequest,
    SessionListResponse,
    TwoFAUpdateRequest,
    TwoFAUpdateResponse,
)
from src.core.database import get_session

router = APIRouter()
_bearer = HTTPBearer()


def _get_current_token(
    credentials: HTTPAuthorizationCredentials = Depends(_bearer),
) -> str:
    """Extract the raw bearer token from the request."""
    return credentials.credentials


# ─── Profile ──────────────────────────────────────────────────────────────────

@router.get(
    "/profile",
    response_model=ProfileResponse,
    summary="Get current user profile",
    tags=["User Profile"],
)
async def get_profile(
    current_user=Depends(get_current_active_user),
    db: Session = Depends(get_session),
):
    result = services.get_user_profile(db, current_user)
    user = result["user"]
    avatar_url = result["avatar_url"]

    return ProfileResponse(
        id=user.id,
        email=user.email,
        username=getattr(user, "username", None),
        first_name=user.first_name,
        last_name=user.last_name,
        middle_name=getattr(user, "middle_name", None),
        full_name=user.full_name,
        bio=getattr(user, "bio", None),
        phone_number=getattr(user, "phone_number", None) or getattr(user, "phone", None),
        address_line_1=getattr(user, "address_line_1", None),
        address_line_2=getattr(user, "address_line_2", None),
        city=getattr(user, "city", None),
        state=getattr(user, "state", None),
        zip_code=getattr(user, "zip_code", None),
        country=getattr(user, "country", None),
        avatar_url=avatar_url,
        twofa_enabled=getattr(user, "twofa_enabled", False) or False,
        last_password_changed_at=getattr(user, "last_password_changed_at", None),
        created_at=user.created_at,
        updated_at=user.updated_at,
    )


@router.put(
    "/profile",
    response_model=ProfileResponse,
    summary="Update current user profile",
    tags=["User Profile"],
)
async def update_profile(
    body: ProfileUpdateRequest,
    current_user=Depends(get_current_active_user),
    db: Session = Depends(get_session),
):
    user = services.update_user_profile(db, current_user, body.model_dump(exclude_unset=True))
    db.commit()
    result = services.get_user_profile(db, user)
    avatar_url = result["avatar_url"]

    return ProfileResponse(
        id=user.id,
        email=user.email,
        username=getattr(user, "username", None),
        first_name=user.first_name,
        last_name=user.last_name,
        middle_name=getattr(user, "middle_name", None),
        full_name=user.full_name,
        bio=getattr(user, "bio", None),
        phone_number=getattr(user, "phone_number", None) or getattr(user, "phone", None),
        address_line_1=getattr(user, "address_line_1", None),
        address_line_2=getattr(user, "address_line_2", None),
        city=getattr(user, "city", None),
        state=getattr(user, "state", None),
        zip_code=getattr(user, "zip_code", None),
        country=getattr(user, "country", None),
        avatar_url=avatar_url,
        twofa_enabled=getattr(user, "twofa_enabled", False) or False,
        last_password_changed_at=getattr(user, "last_password_changed_at", None),
        created_at=user.created_at,
        updated_at=user.updated_at,
    )


# ─── Avatar ───────────────────────────────────────────────────────────────────

@router.post(
    "/avatar",
    summary="Upload avatar image",
    tags=["User Profile"],
)
async def upload_avatar(
    file: UploadFile = File(...),
    current_user=Depends(get_current_active_user),
    merchant=Depends(get_current_merchant),
    db: Session = Depends(get_session),
):
    # SECURITY (CRITICAL): Avatar upload relies solely on the client-supplied Content-Type
    # header via upload_single_file → _validate_file_type_and_content. This is trivially
    # bypassed: an attacker can set Content-Type: image/jpeg on a script or binary file.
    # TWO fixes required:
    #   1. Restrict allowed types to image subtypes only at this layer (pre-check below).
    #   2. Validate magic bytes server-side using python-magic or imghdr AFTER reading
    #      file content, before storing. See file_services.py _validate_file_type_and_content.
    # The global ALLOWED_FILE_CONTENT_TYPES also permits pdf/csv/zip/octet-stream which
    # are not valid avatar formats. Until magic-byte validation is added, the pre-check
    # below at least rejects obviously wrong declared MIME types.
    _AVATAR_ALLOWED_MIME_TYPES = {"image/jpeg", "image/png", "image/gif", "image/webp"}
    if file.content_type not in _AVATAR_ALLOWED_MIME_TYPES:
        from fastapi import HTTPException
        raise HTTPException(
            status_code=400,
            detail=f"Avatar must be an image file (jpeg, png, gif, webp). Got: {file.content_type}",
        )
    url = await services.upload_user_avatar(db, current_user, file, merchant)
    db.commit()
    return {"avatar_url": url, "message": "Avatar uploaded successfully."}


@router.get(
    "/avatar-url",
    response_model=AvatarUrlResponse,
    summary="Get fresh presigned avatar URL",
    tags=["User Profile"],
)
async def get_avatar_url(
    current_user=Depends(get_current_active_user),
    db: Session = Depends(get_session),
):
    url = services.refresh_avatar_url(db, current_user)
    if not url:
        from fastapi import HTTPException
        raise HTTPException(status_code=404, detail="No avatar set for this user.")
    return AvatarUrlResponse(avatar_url=url, expires_in=3600)


@router.delete(
    "/avatar",
    status_code=status.HTTP_204_NO_CONTENT,
    summary="Remove avatar",
    tags=["User Profile"],
)
async def remove_avatar(
    current_user=Depends(get_current_active_user),
    db: Session = Depends(get_session),
):
    services.remove_user_avatar(db, current_user)
    db.commit()


# ─── Password ─────────────────────────────────────────────────────────────────

@router.put(
    "/password",
    response_model=ChangePasswordResponse,
    summary="Change password",
    tags=["User Profile"],
    # TODO: Add rate limiting (e.g. 5 attempts per 15 min per user) to prevent
    # brute-force guessing of current_password. No project-wide rate limiter
    # is currently configured.
)
async def change_password(
    body: ChangePasswordRequest,
    request: Request,
    current_user=Depends(get_current_active_user),
    token: str = Depends(_get_current_token),
    db: Session = Depends(get_session),
):
    # Determine merchant_id for policy lookup
    merchant_id = 0
    try:
        from sqlalchemy import select as sa_select
        from src.apps.merchants.models.merchant_users import MerchantUsers
        mu = db.execute(
            sa_select(MerchantUsers).where(MerchantUsers.user_id == current_user.id)
        ).scalar_one_or_none()
        if mu:
            merchant_id = mu.merchant_id
    except Exception:
        pass

    # Optional Redis client
    redis_client = None
    try:
        import redis as redis_lib
        from src.core.config import settings
        url = getattr(settings, "CELERY_RESULT_BACKEND", None)
        if url and "redis" in url:
            redis_client = redis_lib.from_url(url, decode_responses=True)
    except Exception:
        pass

    result = services.change_password(
        db=db,
        user=current_user,
        current_password=body.current_password,
        new_password=body.new_password,
        current_session_token=token,
        merchant_id=merchant_id,
        redis_client=redis_client,
    )
    db.commit()
    return ChangePasswordResponse(**result)


# ─── 2FA ──────────────────────────────────────────────────────────────────────

@router.put(
    "/2fa",
    response_model=TwoFAUpdateResponse,
    summary="Toggle two-factor authentication",
    tags=["User Profile"],
)
async def toggle_2fa(
    body: TwoFAUpdateRequest,
    current_user=Depends(get_current_active_user),
    db: Session = Depends(get_session),
):
    user = services.toggle_twofa(db, current_user, body.twofa_enabled)
    db.commit()
    state = "enabled" if body.twofa_enabled else "disabled"
    return TwoFAUpdateResponse(
        twofa_enabled=body.twofa_enabled,
        message=f"Two-factor authentication {state} successfully.",
    )


# ─── Sessions ─────────────────────────────────────────────────────────────────

@router.get(
    "/sessions",
    response_model=SessionListResponse,
    summary="Get session history",
    tags=["User Profile"],
)
async def get_sessions(
    page: int = Query(1, ge=1),
    per_page: int = Query(20, ge=1, le=100),
    current_user=Depends(get_current_active_user),
    token: str = Depends(_get_current_token),
    db: Session = Depends(get_session),
):
    data = services.get_session_history(
        db=db,
        user_id=current_user.id,
        current_token=token,
        page=page,
        per_page=per_page,
    )
    return SessionListResponse(**data)


@router.delete(
    "/sessions/{session_id}",
    status_code=status.HTTP_204_NO_CONTENT,
    summary="Revoke a specific session",
    tags=["User Profile"],
)
async def revoke_session(
    session_id: int,
    current_user=Depends(get_current_active_user),
    token: str = Depends(_get_current_token),
    db: Session = Depends(get_session),
):
    services.revoke_session(db, current_user.id, session_id, token)
    db.commit()


# ─── Notification Preferences ─────────────────────────────────────────────────

@router.get(
    "/notification-preferences",
    response_model=NotificationPrefsResponse,
    summary="Get notification preferences",
    tags=["User Profile"],
)
async def get_notification_prefs(
    current_user=Depends(get_current_active_user),
    db: Session = Depends(get_session),
):
    prefs = services.get_notification_preferences(db, current_user.id)
    return NotificationPrefsResponse(
        notify_on_new_login=prefs.notify_on_new_login,
        notify_on_password_change=prefs.notify_on_password_change,
        notify_on_new_device=prefs.notify_on_new_device,
        updated_at=getattr(prefs, "updated_at", None),
    )


@router.put(
    "/notification-preferences",
    response_model=NotificationPrefsResponse,
    summary="Update notification preferences",
    tags=["User Profile"],
)
async def update_notification_prefs(
    body: NotificationPrefsRequest,
    current_user=Depends(get_current_active_user),
    db: Session = Depends(get_session),
):
    prefs = services.update_notification_preferences(
        db, current_user.id, body.model_dump(exclude_none=True)
    )
    db.commit()
    return NotificationPrefsResponse(
        notify_on_new_login=prefs.notify_on_new_login,
        notify_on_password_change=prefs.notify_on_password_change,
        notify_on_new_device=prefs.notify_on_new_device,
        updated_at=prefs.updated_at,
    )


# ─── Email Change ─────────────────────────────────────────────────────────────

@router.post(
    "/email-change",
    response_model=EmailChangeInitiateResponse,
    summary="Initiate email change",
    tags=["User Profile"],
    # TODO: Add rate limiting (e.g. 3 requests per 15 min per user) to prevent
    # email-change token spam. No project-wide rate limiter is currently configured.
)
async def initiate_email_change(
    body: EmailChangeInitiateRequest,
    current_user=Depends(get_current_active_user),
    db: Session = Depends(get_session),
):
    services.initiate_email_change(db, current_user, str(body.new_email))
    db.commit()
    return EmailChangeInitiateResponse(
        message=(
            "A verification link has been sent to your new email address. "
            "Please check your inbox and click the link to confirm the change."
        )
    )


@router.get(
    "/email-change/confirm",
    response_model=EmailChangeInitiateResponse,
    summary="Confirm email change via token",
    tags=["User Profile"],
    # SECURITY (MEDIUM): The confirmation token is passed as a URL query parameter, which
    # means it appears in server access logs, browser history, Referrer headers, and any
    # reverse proxy logs. Consider migrating to a POST endpoint that accepts the token in
    # the request body, preventing token leakage into log infrastructure. The token is
    # SHA-256 hashed in the DB so the raw token value is still sensitive until consumed.
)
async def confirm_email_change(
    token: str = Query(..., description="Email change confirmation token"),
    current_user=Depends(get_current_active_user),
    db: Session = Depends(get_session),
):
    services.confirm_email_change(db, current_user, token)
    db.commit()
    return EmailChangeInitiateResponse(
        message=(
            "Email address updated successfully. "
            "All active sessions have been revoked. Please log in again with your new email."
        )
    )
