"""Admin routes for managing users in cabinet."""

import math
from datetime import UTC, datetime, timedelta

import structlog
from fastapi import APIRouter, Depends, HTTPException, Query, status
from sqlalchemy import Integer, and_, delete as sa_delete, func, literal, or_, select
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload

from app.config import settings
from app.database.crud.campaign import get_campaign_registration_by_user
from app.database.crud.subscription import (
    extend_subscription,
)
from app.database.crud.tariff import get_tariff_by_id
from app.database.crud.user import (
    add_user_balance,
    delete_user as soft_delete_user,
    get_referrals,
    get_user_by_id,
    get_user_by_telegram_id,
    get_users_count,
    get_users_list,
    get_users_spending_stats,
    get_users_statistics,
    subtract_user_balance,
)
from app.database.crud.user_promo_group import sync_user_primary_promo_group
from app.database.models import (
    GuestPurchase,
    PaymentMethod,
    PromoGroup,
    ReferralEarning,
    Subscription,
    SubscriptionServer,
    SubscriptionStatus,
    TrafficPurchase,
    Transaction,
    TransactionType,
    User,
    UserPromoGroup,
    UserStatus,
)
from app.services.permission_service import PermissionService
from app.utils.timezone import panel_datetime_to_utc

from ..dependencies import get_cabinet_db, require_permission
from ..schemas.users import (
    AdminUserGiftItem,
    AdminUserGiftsResponse,
    AssignReferrerRequest,
    AssignReferrerResponse,
    DeleteDeviceResponse,
    DeleteUserRequest,
    DeleteUserResponse,
    DeviceInfo,
    DisableUserRequest,
    DisableUserResponse,
    FullDeleteUserRequest,
    FullDeleteUserResponse,
    PanelSyncStatusResponse,
    PanelUserInfo,
    PeriodPriceInfo,
    RemoveReferralResponse,
    RemoveReferrerResponse,
    ResetDevicesResponse,
    ResetSubscriptionRequest,
    ResetSubscriptionResponse,
    ResetTrialRequest,
    ResetTrialResponse,
    SortByEnum,
    SubscriptionListItem,
    SyncFromPanelRequest,
    SyncFromPanelResponse,
    SyncToPanelRequest,
    SyncToPanelResponse,
    TrafficPurchaseItem,
    UpdateBalanceRequest,
    UpdateBalanceResponse,
    UpdatePromoGroupRequest,
    UpdatePromoGroupResponse,
    UpdateReferralCommissionRequest,
    UpdateReferralCommissionResponse,
    UpdateRestrictionsRequest,
    UpdateRestrictionsResponse,
    UpdateSubscriptionRequest,
    UpdateSubscriptionResponse,
    UpdateUserStatusRequest,
    UpdateUserStatusResponse,
    UserAvailableTariffItem,
    UserAvailableTariffsResponse,
    UserDetailResponse,
    UserDevicesResponse,
    UserListItem,
    UserNodeUsageItem,
    UserNodeUsageResponse,
    UserPanelInfoResponse,
    UserPromoGroupInfo,
    UserReferralInfo,
    UsersListResponse,
    UsersStatsResponse,
    UserStatusEnum,
    UserSubscriptionInfo,
    UserTransactionItem,
)


logger = structlog.get_logger(__name__)

router = APIRouter(prefix='/admin/users', tags=['Cabinet Admin Users'])


def _build_user_list_item(user: User, spending_stats: dict = None) -> UserListItem:
    """Build UserListItem from User model."""
    stats = spending_stats or {}
    user_stats = stats.get(user.id, {'total_spent': 0, 'purchase_count': 0})

    subscription_status = None
    subscription_is_trial = False
    subscription_end_date = None
    has_subscription = False
    tariff_id = None
    tariff_name = None
    traffic_used_gb = 0.0
    traffic_limit_gb = 0
    device_limit = 0
    days_remaining = 0

    subs = getattr(user, 'subscriptions', None) or []
    subscription = next((s for s in subs if s.is_active), subs[0] if subs else None)
    if subscription:
        has_subscription = True
        subscription_status = subscription.status
        subscription_is_trial = subscription.is_trial
        subscription_end_date = subscription.end_date
        tariff_id = subscription.tariff_id
        tariff_name = subscription.tariff.name if subscription.tariff else None
        traffic_used_gb = subscription.traffic_used_gb or 0.0
        traffic_limit_gb = subscription.traffic_limit_gb or 0
        device_limit = subscription.device_limit or 0
        if subscription.end_date:
            delta = subscription.end_date - datetime.now(UTC)
            days_remaining = max(0, delta.days)

    # Build per-subscription list (always — bulk actions need it for any mode)
    sub_list: list[SubscriptionListItem] = []
    if subs:
        for s in subs:
            s_days = 0
            if s.end_date:
                s_delta = s.end_date - datetime.now(UTC)
                s_days = max(0, s_delta.days)
            sub_list.append(
                SubscriptionListItem(
                    id=s.id,
                    tariff_id=s.tariff_id,
                    tariff_name=s.tariff.name if s.tariff else None,
                    status=s.status,
                    is_trial=bool(s.is_trial),
                    end_date=s.end_date,
                    days_remaining=s_days,
                    traffic_used_gb=s.traffic_used_gb or 0.0,
                    traffic_limit_gb=s.traffic_limit_gb or 0,
                    device_limit=s.device_limit or 0,
                )
            )

    return UserListItem(
        id=user.id,
        telegram_id=user.telegram_id,
        username=user.username,
        first_name=user.first_name,
        last_name=user.last_name,
        full_name=user.full_name,
        status=user.status,
        balance_kopeks=user.balance_kopeks,
        balance_rubles=user.balance_rubles,
        created_at=user.created_at,
        last_activity=user.last_activity,
        has_subscription=has_subscription,
        subscription_status=subscription_status,
        subscription_is_trial=subscription_is_trial,
        subscription_end_date=subscription_end_date,
        tariff_id=tariff_id,
        tariff_name=tariff_name,
        traffic_used_gb=traffic_used_gb,
        traffic_limit_gb=traffic_limit_gb,
        device_limit=device_limit,
        days_remaining=days_remaining,
        subscriptions=sub_list,
        promo_group_id=user.promo_group_id,
        promo_group_name=user.promo_group.name if user.promo_group else None,
        total_spent_kopeks=user_stats.get('total_spent', 0),
        purchase_count=user_stats.get('purchase_count', 0),
        has_restrictions=user.has_restrictions,
        restriction_topup=user.restriction_topup,
        restriction_subscription=user.restriction_subscription,
    )


def _build_subscription_info(subscription: Subscription, tariff_name: str | None = None) -> UserSubscriptionInfo:
    """Build UserSubscriptionInfo from Subscription model."""
    days_remaining = 0
    is_active = False

    if subscription.end_date:
        delta = subscription.end_date - datetime.now(UTC)
        days_remaining = max(0, delta.days)
        is_active = subscription.status == SubscriptionStatus.ACTIVE.value and subscription.end_date > datetime.now(UTC)

    return UserSubscriptionInfo(
        id=subscription.id,
        status=subscription.status,
        is_trial=subscription.is_trial,
        start_date=subscription.start_date,
        end_date=subscription.end_date,
        traffic_limit_gb=subscription.traffic_limit_gb,
        traffic_used_gb=subscription.traffic_used_gb or 0.0,
        device_limit=subscription.device_limit,
        tariff_id=subscription.tariff_id,
        tariff_name=tariff_name,
        autopay_enabled=subscription.autopay_enabled,
        is_active=is_active,
        days_remaining=days_remaining,
    )


async def _build_subscription_info_async(db: AsyncSession, subscription: Subscription) -> UserSubscriptionInfo:
    """Build UserSubscriptionInfo from Subscription model, fetching tariff name and traffic purchases."""
    tariff_name = None
    if subscription.tariff_id:
        tariff = await get_tariff_by_id(db, subscription.tariff_id)
        if tariff:
            tariff_name = tariff.name

    # Fetch traffic purchases
    now = datetime.now(UTC)
    tp_query = (
        select(TrafficPurchase)
        .where(TrafficPurchase.subscription_id == subscription.id)
        .order_by(TrafficPurchase.created_at.desc())
    )
    tp_result = await db.execute(tp_query)
    purchases = tp_result.scalars().all()

    traffic_purchase_items = []
    for p in purchases:
        delta = p.expires_at - now
        days_remaining = max(0, delta.days)
        is_expired = now >= p.expires_at
        traffic_purchase_items.append(
            TrafficPurchaseItem(
                id=p.id,
                traffic_gb=p.traffic_gb,
                expires_at=p.expires_at,
                created_at=p.created_at,
                days_remaining=days_remaining,
                is_expired=is_expired,
            )
        )

    info = _build_subscription_info(subscription, tariff_name=tariff_name)
    info.purchased_traffic_gb = getattr(subscription, 'purchased_traffic_gb', 0) or 0
    info.traffic_purchases = traffic_purchase_items
    return info


async def _sync_subscription_to_panel(
    db: AsyncSession,
    user: User,
    subscription: Subscription,
    reset_traffic: bool = False,
    reset_traffic_reason: str | None = None,
) -> dict:
    """
    Sync user subscription to Remnawave panel.
    Creates user if not exists, updates if exists.
    Optionally resets traffic after sync.
    Returns dict with changes/errors.
    """
    try:
        from app.config import settings
        from app.external.remnawave_api import UserStatus as PanelUserStatus
        from app.services.remnawave_service import RemnaWaveService
        from app.services.subscription_service import get_traffic_reset_strategy
        from app.utils.subscription_utils import resolve_hwid_device_limit_for_payload

        service = RemnaWaveService()
        if not service.is_configured:
            logger.warning('Remnawave not configured, skipping panel sync for user', user_id=user.id)
            return {'skipped': True, 'reason': 'Remnawave not configured'}

        is_active = (
            subscription.status in (SubscriptionStatus.ACTIVE.value, SubscriptionStatus.TRIAL.value)
            and subscription.end_date
            and subscription.end_date > datetime.now(UTC)
        )
        panel_status = PanelUserStatus.ACTIVE if is_active else PanelUserStatus.DISABLED

        expire_at = subscription.end_date
        if expire_at and expire_at <= datetime.now(UTC):
            expire_at = datetime.now(UTC) + timedelta(minutes=1)

        username = settings.format_remnawave_username(
            full_name=user.full_name,
            username=user.username,
            telegram_id=user.telegram_id,
            email=user.email,
            user_id=user.id,
        )

        description = settings.format_remnawave_user_description(
            full_name=user.full_name,
            username=user.username,
            telegram_id=user.telegram_id,
            email=user.email,
            user_id=user.id,
        )

        hwid_limit = resolve_hwid_device_limit_for_payload(subscription)
        traffic_limit_bytes = subscription.traffic_limit_gb * (1024**3) if subscription.traffic_limit_gb > 0 else 0

        # Загружаем tariff для определения внешнего сквада
        try:
            await db.refresh(subscription, ['tariff'])
        except Exception:
            pass
        ext_squad_uuid = subscription.tariff.external_squad_uuid if subscription.tariff else None

        changes = {}
        async with service.get_api_client() as api:
            # Multi-tariff: each subscription has its own panel user
            if settings.is_multi_tariff_enabled():
                panel_uuid = subscription.remnawave_uuid
            else:
                panel_uuid = user.remnawave_uuid

            # Try to find existing user by UUID first
            if panel_uuid:
                existing_user = await api.get_user_by_uuid(panel_uuid)
                if not existing_user:
                    logger.warning('Stale remnawave_uuid, clearing', user_id=user.id, panel_uuid=panel_uuid)
                    panel_uuid = None
                    if settings.is_multi_tariff_enabled():
                        subscription.remnawave_uuid = None
                    else:
                        user.remnawave_uuid = None

            # Fallback: search by telegram_id (single-tariff only)
            if not panel_uuid and not settings.is_multi_tariff_enabled() and user.telegram_id:
                existing_users = await api.get_user_by_telegram_id(user.telegram_id)
                if existing_users:
                    panel_uuid = existing_users[0].uuid
                    user.remnawave_uuid = panel_uuid
                    changes['remnawave_uuid_discovered'] = panel_uuid

            # Fallback: search by email (single-tariff, OAuth users)
            if not panel_uuid and not settings.is_multi_tariff_enabled() and user.email:
                existing_users = await api.get_user_by_email(user.email)
                if existing_users:
                    panel_uuid = existing_users[0].uuid
                    user.remnawave_uuid = panel_uuid
                    changes['remnawave_uuid_discovered'] = panel_uuid

            if panel_uuid:
                # Update existing user
                update_kwargs = {
                    'uuid': panel_uuid,
                    'status': panel_status,
                    'traffic_limit_bytes': traffic_limit_bytes,
                    'traffic_limit_strategy': get_traffic_reset_strategy(subscription.tariff),
                    'description': description,
                }
                if expire_at:
                    update_kwargs['expire_at'] = expire_at
                if subscription.connected_squads:
                    update_kwargs['active_internal_squads'] = subscription.connected_squads
                if hwid_limit is not None:
                    update_kwargs['hwid_device_limit'] = hwid_limit

                # Внешний сквад: синхронизируем из тарифа (если задан)
                # Не отправляем null — RemnaWave API не принимает null для externalSquadUuid (A039)
                if ext_squad_uuid is not None:
                    update_kwargs['external_squad_uuid'] = ext_squad_uuid

                try:
                    updated_panel_user = await api.update_user(**update_kwargs)
                    subscription.subscription_url = updated_panel_user.subscription_url
                    subscription.subscription_crypto_link = updated_panel_user.happ_crypto_link
                    subscription.remnawave_short_uuid = updated_panel_user.short_uuid
                    changes['action'] = 'updated'
                    logger.info('Updated user in Remnawave panel', user_id=user.id)
                except Exception as update_error:
                    error_code = (getattr(update_error, 'response_data', None) or {}).get('errorCode', '')
                    if (
                        hasattr(update_error, 'status_code') and update_error.status_code == 404
                    ) or error_code == 'A018':
                        panel_uuid = None  # Will create new
                    else:
                        raise

            if not panel_uuid:
                # Create new user
                create_kwargs = {
                    'username': username,
                    'expire_at': expire_at or (datetime.now(UTC) + timedelta(days=30)),
                    'status': panel_status,
                    'traffic_limit_bytes': traffic_limit_bytes,
                    'traffic_limit_strategy': get_traffic_reset_strategy(subscription.tariff),
                    'telegram_id': user.telegram_id,
                    'email': user.email,
                    'description': description,
                    'active_internal_squads': subscription.connected_squads or [],
                }
                if hwid_limit is not None:
                    create_kwargs['hwid_device_limit'] = hwid_limit
                if ext_squad_uuid is not None:
                    create_kwargs['external_squad_uuid'] = ext_squad_uuid

                # Multi-tariff: use subscription-specific username
                if settings.is_multi_tariff_enabled() and subscription.remnawave_short_id:
                    create_kwargs['username'] = f'{username}_{subscription.remnawave_short_id}'

                new_panel_user = await api.create_user(**create_kwargs)
                subscription.remnawave_uuid = new_panel_user.uuid
                subscription.remnawave_short_uuid = new_panel_user.short_uuid
                subscription.subscription_url = new_panel_user.subscription_url
                subscription.subscription_crypto_link = new_panel_user.happ_crypto_link
                # Legacy: also set user-level UUID in single mode
                if not settings.is_multi_tariff_enabled():
                    user.remnawave_uuid = new_panel_user.uuid
                changes['action'] = 'created'
                changes['panel_uuid'] = new_panel_user.uuid
                logger.info('Created user in Remnawave panel', user_id=user.id, uuid=new_panel_user.uuid)

            # Reset traffic on panel if requested
            _reset_uuid = subscription.remnawave_uuid if settings.is_multi_tariff_enabled() else user.remnawave_uuid
            if reset_traffic and _reset_uuid:
                try:
                    await api.reset_user_traffic(_reset_uuid)
                    changes['traffic_reset'] = True
                    reason_text = f' ({reset_traffic_reason})' if reset_traffic_reason else ''
                    logger.info('Reset RemnaWave traffic for user', user_id=user.id, reason=reason_text)
                except Exception as reset_exc:
                    logger.warning('Failed to reset RemnaWave traffic', user_id=user.id, error=reset_exc)

            user.last_remnawave_sync = datetime.now(UTC)
            await db.commit()

        return changes

    except Exception as e:
        logger.error('Error syncing user to panel', user_id=user.id, error=e)
        return {'error': 'Ошибка синхронизации пользователя с панелью'}


# === List & Search ===


@router.get('', response_model=UsersListResponse)
async def list_users(
    offset: int = Query(0, ge=0),
    limit: int = Query(50, ge=1, le=200),
    search: str | None = Query(None, max_length=255),
    email: str | None = Query(None, max_length=255),
    status: UserStatusEnum | None = Query(None),
    subscription_status: str | None = Query(None, max_length=20),
    tariff_id: str | None = Query(None, max_length=255),
    promo_group_id: int | None = Query(None),
    campaign_id: int | None = Query(None),
    partner_id: int | None = Query(None),
    sort_by: SortByEnum = Query(SortByEnum.CREATED_AT),
    admin: User = Depends(require_permission('users:read')),
    db: AsyncSession = Depends(get_cabinet_db),
):
    """
    Get paginated list of users with filtering and sorting.

    - **offset**: Pagination offset
    - **limit**: Number of users per page (max 200)
    - **search**: Search by telegram_id, username, first_name, last_name
    - **email**: Search by email
    - **status**: Filter by user status (active, blocked, deleted)
    - **sort_by**: Sort field (created_at, balance, traffic, last_activity, total_spent, purchase_count)
    """
    # Convert status enum to model enum
    user_status = None
    if status:
        user_status = UserStatus(status.value)

    # Map sort options
    order_by_balance = sort_by == SortByEnum.BALANCE
    order_by_traffic = sort_by == SortByEnum.TRAFFIC
    order_by_last_activity = sort_by == SortByEnum.LAST_ACTIVITY
    order_by_total_spent = sort_by == SortByEnum.TOTAL_SPENT
    order_by_purchase_count = sort_by == SortByEnum.PURCHASE_COUNT

    # Parse comma-separated tariff_ids
    tariff_ids: list[int] | None = None
    if tariff_id:
        try:
            tariff_ids = [int(x.strip()) for x in tariff_id.split(',') if x.strip()]
        except ValueError:
            tariff_ids = None

    users = await get_users_list(
        db=db,
        offset=offset,
        limit=limit,
        search=search,
        email=email,
        status=user_status,
        subscription_status=subscription_status,
        tariff_ids=tariff_ids,
        promo_group_id=promo_group_id,
        campaign_id=campaign_id,
        partner_id=partner_id,
        order_by_balance=order_by_balance,
        order_by_traffic=order_by_traffic,
        order_by_last_activity=order_by_last_activity,
        order_by_total_spent=order_by_total_spent,
        order_by_purchase_count=order_by_purchase_count,
    )

    total = await get_users_count(
        db=db,
        status=user_status,
        search=search,
        email=email,
        subscription_status=subscription_status,
        tariff_ids=tariff_ids,
        promo_group_id=promo_group_id,
        campaign_id=campaign_id,
        partner_id=partner_id,
    )

    # Get spending stats for all users
    user_ids = [u.id for u in users]
    spending_stats = await get_users_spending_stats(db, user_ids) if user_ids else {}

    items = [_build_user_list_item(u, spending_stats) for u in users]

    return UsersListResponse(
        users=items,
        total=total,
        offset=offset,
        limit=limit,
    )


@router.get('/stats', response_model=UsersStatsResponse)
async def get_users_stats(
    admin: User = Depends(require_permission('users:read')),
    db: AsyncSession = Depends(get_cabinet_db),
):
    """Get overall users statistics."""
    stats = await get_users_statistics(db)

    # Get subscription stats
    sub_stats_query = select(
        func.count(Subscription.id).label('total'),
        func.sum(
            func.cast(
                and_(
                    Subscription.status == SubscriptionStatus.ACTIVE.value,
                    Subscription.end_date > datetime.now(UTC),
                ),
                Integer,
            )
        ).label('active'),
        func.sum(func.cast(Subscription.is_trial == True, Integer)).label('trial'),
        func.sum(
            func.cast(
                or_(
                    Subscription.status == SubscriptionStatus.EXPIRED.value,
                    Subscription.end_date <= datetime.now(UTC),
                ),
                Integer,
            )
        ).label('expired'),
    )
    sub_result = await db.execute(sub_stats_query)
    sub_row = sub_result.one_or_none()

    users_with_subscription = sub_row.total or 0 if sub_row else 0
    users_with_active = sub_row.active or 0 if sub_row else 0
    users_with_trial = sub_row.trial or 0 if sub_row else 0
    users_with_expired = sub_row.expired or 0 if sub_row else 0

    # Get balance stats
    balance_query = select(
        func.sum(User.balance_kopeks).label('total'),
        func.avg(User.balance_kopeks).label('avg'),
    ).where(User.status == UserStatus.ACTIVE.value)
    balance_result = await db.execute(balance_query)
    balance_row = balance_result.one_or_none()
    total_balance = balance_row.total or 0 if balance_row else 0
    avg_balance = int(balance_row.avg or 0) if balance_row else 0

    # Get activity stats
    now = datetime.now(UTC)
    today_start = now.replace(hour=0, minute=0, second=0, microsecond=0)
    week_ago = now - timedelta(days=7)
    month_ago = now - timedelta(days=30)

    active_today_q = select(func.count(User.id)).where(
        User.last_activity >= today_start,
        User.status == UserStatus.ACTIVE.value,
    )
    active_week_q = select(func.count(User.id)).where(
        User.last_activity >= week_ago,
        User.status == UserStatus.ACTIVE.value,
    )
    active_month_q = select(func.count(User.id)).where(
        User.last_activity >= month_ago,
        User.status == UserStatus.ACTIVE.value,
    )

    active_today = (await db.execute(active_today_q)).scalar() or 0
    active_week = (await db.execute(active_week_q)).scalar() or 0
    active_month = (await db.execute(active_month_q)).scalar() or 0

    # Count deleted users
    deleted_q = select(func.count(User.id)).where(User.status == UserStatus.DELETED.value)
    deleted_count = (await db.execute(deleted_q)).scalar() or 0

    return UsersStatsResponse(
        total_users=stats['total_users'],
        active_users=stats['active_users'],
        blocked_users=stats['blocked_users'],
        deleted_users=deleted_count,
        new_today=stats['new_today'],
        new_week=stats['new_week'],
        new_month=stats['new_month'],
        users_with_subscription=users_with_subscription,
        users_with_active_subscription=users_with_active,
        users_with_trial=users_with_trial,
        users_with_expired_subscription=users_with_expired,
        total_balance_kopeks=total_balance,
        total_balance_rubles=total_balance / 100,
        avg_balance_kopeks=avg_balance,
        active_today=active_today,
        active_week=active_week,
        active_month=active_month,
    )


# === User Detail ===


@router.get('/{user_id}', response_model=UserDetailResponse)
async def get_user_detail(
    user_id: int,
    admin: User = Depends(require_permission('users:read')),
    db: AsyncSession = Depends(get_cabinet_db),
):
    """Get detailed user information by ID."""
    user = await get_user_by_id(db, user_id)
    if not user:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail='User not found',
        )

    # Get spending stats
    spending_stats = await get_users_spending_stats(db, [user.id])
    user_stats = spending_stats.get(user.id, {'total_spent': 0, 'purchase_count': 0})

    # Build subscription info (all subscriptions + legacy single)
    subs = getattr(user, 'subscriptions', None) or []
    all_subscriptions_info = []
    for sub in subs:
        all_subscriptions_info.append(await _build_subscription_info_async(db, sub))

    # Legacy: pick first active or most recent for backward compat
    subscription_info = None
    primary_sub = next((s for s in subs if s.is_active), subs[0] if subs else None)
    if primary_sub:
        subscription_info = await _build_subscription_info_async(db, primary_sub)

    # Build promo group info
    promo_group_info = None
    if user.promo_group:
        promo_group_info = UserPromoGroupInfo(
            id=user.promo_group.id,
            name=user.promo_group.name,
            is_default=user.promo_group.is_default,
        )

    # Get referrals count
    referrals = await get_referrals(db, user.id)
    referrals_count = len(referrals)

    # Calculate total referral earnings (canonical source: ReferralEarning)
    referral_earnings_q = select(func.coalesce(func.sum(ReferralEarning.amount_kopeks), 0)).where(
        ReferralEarning.user_id == user.id
    )
    referral_earnings = (await db.execute(referral_earnings_q)).scalar() or 0

    # Get referrer info
    referred_by_username = None
    if user.referred_by_id:
        referrer_q = select(User).where(User.id == user.referred_by_id)
        referrer_result = await db.execute(referrer_q)
        referrer = referrer_result.scalar_one_or_none()
        if referrer:
            referred_by_username = referrer.username or referrer.full_name

    referral_info = UserReferralInfo(
        referral_code=user.referral_code or '',
        referrals_count=referrals_count,
        total_earnings_kopeks=referral_earnings,
        commission_percent=user.referral_commission_percent,
        referred_by_id=user.referred_by_id,
        referred_by_username=referred_by_username,
    )

    # Get recent transactions
    transactions_q = (
        select(Transaction).where(Transaction.user_id == user.id).order_by(Transaction.created_at.desc()).limit(20)
    )
    transactions_result = await db.execute(transactions_q)
    transactions = transactions_result.scalars().all()

    _EXPENSE_TYPES = {
        TransactionType.WITHDRAWAL.value,
        TransactionType.SUBSCRIPTION_PAYMENT.value,
        TransactionType.GIFT_PAYMENT.value,
    }

    recent_transactions = [
        UserTransactionItem(
            id=t.id,
            type=t.type,
            amount_kopeks=-abs(t.amount_kopeks) if t.type in _EXPENSE_TYPES else t.amount_kopeks,
            amount_rubles=-abs(t.amount_kopeks) / 100 if t.type in _EXPENSE_TYPES else t.amount_kopeks / 100,
            description=t.description,
            payment_method=t.payment_method,
            is_completed=t.is_completed,
            created_at=t.created_at,
        )
        for t in transactions
    ]

    # Get campaign info
    campaign_name = None
    campaign_id = None
    campaign_reg = await get_campaign_registration_by_user(db, user.id)
    if campaign_reg and campaign_reg.campaign:
        campaign_name = campaign_reg.campaign.name
        campaign_id = campaign_reg.campaign.id

    return UserDetailResponse(
        id=user.id,
        telegram_id=user.telegram_id,
        username=user.username,
        first_name=user.first_name,
        last_name=user.last_name,
        full_name=user.full_name,
        status=user.status,
        language=user.language,
        balance_kopeks=user.balance_kopeks,
        balance_rubles=user.balance_rubles,
        email=user.email,
        email_verified=user.email_verified,
        created_at=user.created_at,
        updated_at=user.updated_at,
        last_activity=user.last_activity,
        cabinet_last_login=user.cabinet_last_login,
        subscription=subscription_info,
        subscriptions=all_subscriptions_info,
        promo_group=promo_group_info,
        referral=referral_info,
        total_spent_kopeks=user_stats.get('total_spent', 0),
        purchase_count=user_stats.get('purchase_count', 0),
        used_promocodes=user.used_promocodes or 0,
        has_had_paid_subscription=user.has_had_paid_subscription,
        lifetime_used_traffic_bytes=user.lifetime_used_traffic_bytes or 0,
        campaign_name=campaign_name,
        campaign_id=campaign_id,
        restriction_topup=user.restriction_topup,
        restriction_subscription=user.restriction_subscription,
        restriction_reason=user.restriction_reason,
        promo_offer_discount_percent=user.promo_offer_discount_percent,
        promo_offer_discount_source=user.promo_offer_discount_source,
        promo_offer_discount_expires_at=user.promo_offer_discount_expires_at,
        recent_transactions=recent_transactions,
        remnawave_uuid=(
            primary_sub.remnawave_uuid
            if settings.is_multi_tariff_enabled() and primary_sub and primary_sub.remnawave_uuid
            else user.remnawave_uuid
        ),
    )


@router.get('/by-telegram/{telegram_id}', response_model=UserDetailResponse)
async def get_user_by_telegram(
    telegram_id: int,
    admin: User = Depends(require_permission('users:read')),
    db: AsyncSession = Depends(get_cabinet_db),
):
    """Get user by Telegram ID."""
    user = await get_user_by_telegram_id(db, telegram_id)
    if not user:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail='User not found',
        )
    return await get_user_detail(user.id, admin, db)


# === Panel Info ===


@router.get('/{user_id}/panel-info', response_model=UserPanelInfoResponse)
async def get_user_panel_info(
    user_id: int,
    admin: User = Depends(require_permission('users:read')),
    db: AsyncSession = Depends(get_cabinet_db),
    subscription_id: int | None = Query(None, description='Subscription ID for multi-tariff panel lookup'),
):
    """Get user panel info from Remnawave (config links, traffic, connection data)."""
    user = await get_user_by_id(db, user_id)
    if not user:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail='User not found',
        )

    try:
        from app.services.remnawave_service import RemnaWaveService

        service = RemnaWaveService()
        if not service.is_configured:
            return UserPanelInfoResponse(found=False)

        async with service.get_api_client() as api:
            panel_user = None

            # Multi-tariff: use per-subscription UUID
            if settings.is_multi_tariff_enabled() and subscription_id:
                from app.database.crud.subscription import get_subscription_by_id_for_user

                sub = await get_subscription_by_id_for_user(db, subscription_id, user_id)
                if sub and sub.remnawave_uuid:
                    panel_user = await api.get_user_by_uuid(sub.remnawave_uuid)
            # Single-tariff: user-level UUID
            elif user.remnawave_uuid:
                panel_user = await api.get_user_by_uuid(user.remnawave_uuid)

            # Fallback: search by telegram_id (single-tariff only)
            if not panel_user and not settings.is_multi_tariff_enabled() and user.telegram_id:
                panel_users = await api.get_user_by_telegram_id(user.telegram_id)
                if panel_users:
                    panel_user = panel_users[0]

            # Fallback: search by email (single-tariff, OAuth users)
            if not panel_user and not settings.is_multi_tariff_enabled() and user.email:
                panel_users_by_email = await api.get_user_by_email(user.email)
                if panel_users_by_email:
                    panel_user = panel_users_by_email[0]

            if not panel_user:
                return UserPanelInfoResponse(found=False)

            # Resolve last connected node name via accessible nodes (lighter than get_all_nodes)
            last_node_name = None
            last_node_uuid = None
            if panel_user.user_traffic and panel_user.user_traffic.last_connected_node_uuid:
                last_node_uuid = panel_user.user_traffic.last_connected_node_uuid
                try:
                    accessible = await api.get_user_accessible_nodes(panel_user.uuid)
                    for node in accessible:
                        if node.uuid == last_node_uuid:
                            last_node_name = node.node_name
                            break
                except Exception:
                    logger.warning('Failed to resolve node name for user', user_id=user_id)

            return UserPanelInfoResponse(
                found=True,
                trojan_password=panel_user.trojan_password,
                vless_uuid=panel_user.vless_uuid,
                ss_password=panel_user.ss_password,
                subscription_url=panel_user.subscription_url,
                happ_link=panel_user.happ_link,
                used_traffic_bytes=panel_user.used_traffic_bytes,
                lifetime_used_traffic_bytes=panel_user.lifetime_used_traffic_bytes,
                traffic_limit_bytes=panel_user.traffic_limit_bytes,
                first_connected_at=panel_user.first_connected_at,
                online_at=panel_user.online_at,
                last_connected_node_uuid=last_node_uuid,
                last_connected_node_name=last_node_name,
            )

    except Exception as e:
        logger.error('Error getting panel info for user', user_id=user_id, error=e)
        return UserPanelInfoResponse(found=False)


@router.get('/{user_id}/subscription-request-history')
async def get_subscription_request_history(
    user_id: int,
    admin: User = Depends(require_permission('users:read')),
    db: AsyncSession = Depends(get_cabinet_db),
    subscription_id: int | None = Query(None, description='Subscription ID for multi-tariff'),
    offset: int = Query(0, ge=0),
    limit: int = Query(20, ge=1, le=100),
):
    """Get subscription request history from RemnaWave panel."""
    from app.database.crud.user import get_user_by_id

    user = await get_user_by_id(db, user_id)
    if not user:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='User not found')

    panel_uuid = None
    if settings.is_multi_tariff_enabled() and subscription_id:
        from app.database.crud.subscription import get_subscription_by_id_for_user

        sub = await get_subscription_by_id_for_user(db, subscription_id, user_id)
        if sub:
            panel_uuid = sub.remnawave_uuid
    else:
        panel_uuid = getattr(user, 'remnawave_uuid', None)

    if not panel_uuid:
        return {'total': 0, 'records': []}

    try:
        from app.services.remnawave_service import RemnaWaveService

        service = RemnaWaveService()
        if not service.is_configured:
            return {'total': 0, 'records': []}

        async with service.get_api_client() as api:
            result = await api.get_subscription_request_history(panel_uuid, offset=offset, limit=limit)
            return result
    except Exception as e:
        logger.error('Error getting subscription request history', user_id=user_id, error=e)
        return {'total': 0, 'records': []}


@router.get('/{user_id}/node-usage', response_model=UserNodeUsageResponse)
async def get_user_node_usage(
    user_id: int,
    admin: User = Depends(require_permission('users:read')),
    db: AsyncSession = Depends(get_cabinet_db),
    subscription_id: int | None = Query(None, description='Subscription ID for multi-tariff'),
):
    """Get user per-node traffic usage (always 30 days with daily breakdown)."""
    user = await get_user_by_id(db, user_id)
    if not user:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail='User not found',
        )

    # Resolve panel UUID
    _panel_uuid = None
    if settings.is_multi_tariff_enabled() and subscription_id:
        from app.database.crud.subscription import get_subscription_by_id_for_user

        sub = await get_subscription_by_id_for_user(db, subscription_id, user_id)
        if sub:
            _panel_uuid = sub.remnawave_uuid
    else:
        _panel_uuid = user.remnawave_uuid

    if not _panel_uuid:
        return UserNodeUsageResponse(items=[])

    try:
        from app.services.remnawave_service import RemnaWaveService

        service = RemnaWaveService()
        if not service.is_configured:
            return UserNodeUsageResponse(items=[])

        end_date = datetime.now(UTC)
        start_date = end_date - timedelta(days=30)
        start_str = start_date.strftime('%Y-%m-%d')
        end_str = end_date.strftime('%Y-%m-%d')

        async with service.get_api_client() as api:
            # Get user's accessible nodes (1 API call)
            accessible_nodes = await api.get_user_accessible_nodes(_panel_uuid)

            # Get user bandwidth stats (1 API call)
            # Response: {categories: [dates], series: [{uuid, name, countryCode, total, data: [daily]}, ...]}
            stats = await api.get_bandwidth_stats_user(_panel_uuid, start_str, end_str)

            categories: list[str] = []
            series_map: dict[str, dict] = {}
            if isinstance(stats, dict):
                categories = stats.get('categories', [])
                for s in stats.get('series', []):
                    series_map[s['uuid']] = {
                        'name': s.get('name', ''),
                        'country_code': s.get('countryCode', ''),
                        'total': int(s.get('total', 0)),
                        'daily': [int(v) for v in s.get('data', [])],
                    }

            # Build items: accessible nodes + any extra from stats
            items = []
            seen_uuids: set[str] = set()
            for node in accessible_nodes:
                seen_uuids.add(node.uuid)
                sr = series_map.get(node.uuid)
                items.append(
                    UserNodeUsageItem(
                        node_uuid=node.uuid,
                        node_name=sr['name'] if sr else node.node_name,
                        country_code=sr['country_code'] if sr else node.country_code,
                        total_bytes=sr['total'] if sr else 0,
                        daily_bytes=sr['daily'] if sr else [],
                    )
                )
            for nid, sr in series_map.items():
                if nid not in seen_uuids:
                    items.append(
                        UserNodeUsageItem(
                            node_uuid=nid,
                            node_name=sr['name'],
                            country_code=sr['country_code'],
                            total_bytes=sr['total'],
                            daily_bytes=sr['daily'],
                        )
                    )

            items.sort(key=lambda x: x.total_bytes, reverse=True)
            return UserNodeUsageResponse(items=items, categories=categories)

    except Exception as e:
        logger.error('Error getting node usage for user', user_id=user_id, error=e)
        return UserNodeUsageResponse(items=[])


# === Balance Management ===


@router.post('/{user_id}/balance', response_model=UpdateBalanceResponse)
async def update_user_balance(
    user_id: int,
    request: UpdateBalanceRequest,
    admin: User = Depends(require_permission('users:balance')),
    db: AsyncSession = Depends(get_cabinet_db),
):
    """
    Update user balance.

    - Positive amount: adds to balance
    - Negative amount: subtracts from balance
    """
    user = await get_user_by_id(db, user_id)
    if not user:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail='User not found',
        )

    old_balance = user.balance_kopeks

    if request.amount_kopeks >= 0:
        # Add balance
        success = await add_user_balance(
            db=db,
            user=user,
            amount_kopeks=request.amount_kopeks,
            description=request.description,
            create_transaction=request.create_transaction,
            transaction_type=TransactionType.DEPOSIT,
            payment_method=PaymentMethod.MANUAL,
        )
    else:
        # Subtract balance
        amount_to_subtract = abs(request.amount_kopeks)
        if user.balance_kopeks < amount_to_subtract:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail=f'Insufficient balance. Current: {user.balance_kopeks}, requested: {amount_to_subtract}',
            )
        success = await subtract_user_balance(
            db=db,
            user=user,
            amount_kopeks=amount_to_subtract,
            description=request.description,
            create_transaction=request.create_transaction,
            payment_method=PaymentMethod.MANUAL,
        )

    if not success:
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail='Failed to update balance',
        )

    # Refresh user
    await db.refresh(user)

    logger.info(
        'Admin updated balance for user',
        admin_id=admin.id,
        user_id=user_id,
        old_balance=old_balance,
        balance_kopeks=user.balance_kopeks,
        amount_kopeks=format(request.amount_kopeks, '+d'),
    )

    return UpdateBalanceResponse(
        success=True,
        old_balance_kopeks=old_balance,
        new_balance_kopeks=user.balance_kopeks,
        message=f'Balance updated: {old_balance / 100:.2f}₽ -> {user.balance_kopeks / 100:.2f}₽',
    )


# === Subscription Management ===


@router.post('/{user_id}/subscription', response_model=UpdateSubscriptionResponse)
async def update_user_subscription(
    user_id: int,
    request: UpdateSubscriptionRequest,
    admin: User = Depends(require_permission('users:subscription')),
    db: AsyncSession = Depends(get_cabinet_db),
):
    """
    Update user subscription.

    Actions:
    - **extend**: Extend subscription by X days
    - **set_end_date**: Set specific end date
    - **change_tariff**: Change subscription tariff
    - **set_traffic**: Set traffic limit and/or used traffic
    - **toggle_autopay**: Enable/disable autopay
    - **cancel**: Cancel subscription (set status to expired)
    - **activate**: Activate subscription
    - **create**: Create new subscription if not exists
    """
    user = await get_user_by_id(db, user_id)
    if not user:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail='User not found',
        )

    subs = getattr(user, 'subscriptions', None) or []
    is_multi_tariff = settings.is_multi_tariff_enabled()

    # Select target subscription
    if request.subscription_id:
        subscription = next((s for s in subs if s.id == request.subscription_id), None)
        if not subscription and request.action != 'create':
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail=f'Subscription {request.subscription_id} not found for this user',
            )
    else:
        subscription = next((s for s in subs if s.is_active), subs[0] if subs else None)

    if request.action == 'create':
        # In multi-tariff mode, allow creating additional subscriptions
        if subscription and not is_multi_tariff:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail='User already has a subscription. Enable multi-tariff mode to add more.',
            )

        # Проверка: нельзя создать вторую активную подписку с тем же тарифом
        if is_multi_tariff and request.tariff_id:
            from app.database.crud.subscription import get_subscription_by_user_and_tariff

            existing = await get_subscription_by_user_and_tariff(db, user.id, request.tariff_id)
            if existing:
                raise HTTPException(
                    status_code=status.HTTP_409_CONFLICT,
                    detail='User already has an active subscription for this tariff. Extend it instead.',
                )

        from app.database.crud.subscription import create_paid_subscription

        days = request.days or 30
        is_trial = request.is_trial or False
        traffic_limit = request.traffic_limit_gb or 100
        device_limit = request.device_limit or 1
        connected_squads = []

        # Get tariff for settings if provided
        if request.tariff_id:
            tariff = await get_tariff_by_id(db, request.tariff_id)
            if tariff:
                if not request.traffic_limit_gb:
                    traffic_limit = tariff.traffic_limit_gb
                if not request.device_limit:
                    device_limit = tariff.device_limit
                if tariff.allowed_squads:
                    connected_squads = tariff.allowed_squads

        from sqlalchemy.exc import IntegrityError

        try:
            new_sub = await create_paid_subscription(
                db=db,
                user_id=user.id,
                duration_days=days,
                traffic_limit_gb=traffic_limit,
                device_limit=device_limit,
                is_trial=is_trial,
                tariff_id=request.tariff_id,
                connected_squads=connected_squads,
            )
        except IntegrityError:
            await db.rollback()
            raise HTTPException(
                status_code=status.HTTP_409_CONFLICT,
                detail='User already has an active subscription for this tariff. Extend it instead.',
            )

        # Sync to Remnawave panel
        await _sync_subscription_to_panel(db, user, new_sub)

        logger.info('Admin created subscription for user', admin_id=admin.id, user_id=user_id)

        return UpdateSubscriptionResponse(
            success=True,
            message=f'Subscription created for {days} days',
            subscription=await _build_subscription_info_async(db, new_sub),
        )

    if not subscription:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail='User has no subscription',
        )

    if request.action == 'extend':
        if not request.days or request.days <= 0:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail='Days must be a positive integer',
            )

        await extend_subscription(db, subscription, request.days)
        await db.refresh(subscription)

        # Sync to Remnawave panel
        await _sync_subscription_to_panel(db, user, subscription)

        logger.info(
            'Admin extended subscription for user by days', admin_id=admin.id, user_id=user_id, days=request.days
        )

        return UpdateSubscriptionResponse(
            success=True,
            message=f'Subscription extended by {request.days} days',
            subscription=await _build_subscription_info_async(db, subscription),
        )

    if request.action == 'shorten':
        if not request.days or request.days <= 0:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail='Days must be a positive integer',
            )

        # Сокращение через отрицательный аргумент: extend_subscription(-N) уменьшает end_date
        await extend_subscription(db, subscription, -request.days)
        await db.refresh(subscription)

        # Check if subscription expired after shortening
        if subscription.end_date <= datetime.now(UTC):
            subscription.status = SubscriptionStatus.EXPIRED.value
            await db.commit()
            await db.refresh(subscription)

        # Sync to Remnawave panel
        await _sync_subscription_to_panel(db, user, subscription)

        logger.info(
            'Admin shortened subscription for user by days', admin_id=admin.id, user_id=user_id, days=request.days
        )

        return UpdateSubscriptionResponse(
            success=True,
            message=f'Subscription shortened by {request.days} days',
            subscription=await _build_subscription_info_async(db, subscription),
        )

    if request.action == 'set_end_date':
        if not request.end_date:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail='end_date parameter is required',
            )

        subscription.end_date = request.end_date
        if request.end_date > datetime.now(UTC):
            subscription.status = SubscriptionStatus.ACTIVE.value
        else:
            subscription.status = SubscriptionStatus.EXPIRED.value

        await db.commit()
        await db.refresh(subscription)

        # Sync to Remnawave panel
        await _sync_subscription_to_panel(db, user, subscription)

        logger.info('Admin set end_date for user subscription', admin_id=admin.id, user_id=user_id)

        return UpdateSubscriptionResponse(
            success=True,
            message=f'Subscription end date set to {request.end_date.isoformat()}',
            subscription=await _build_subscription_info_async(db, subscription),
        )

    if request.action == 'change_tariff':
        if request.tariff_id is None:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail='tariff_id parameter is required',
            )

        tariff = await get_tariff_by_id(db, request.tariff_id)
        if not tariff:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail='Tariff not found',
            )

        # Проверка: нельзя сменить тариф, если у пользователя уже есть
        # другая активная подписка с целевым тарифом
        if is_multi_tariff and request.tariff_id != subscription.tariff_id:
            from app.database.crud.subscription import get_subscription_by_user_and_tariff

            existing = await get_subscription_by_user_and_tariff(db, user.id, request.tariff_id)
            if existing and existing.id != subscription.id:
                raise HTTPException(
                    status_code=status.HTTP_409_CONFLICT,
                    detail='User already has an active subscription for the target tariff',
                )

        # Preserve extra purchased devices above the old tariff's base limit
        from app.database.crud.subscription import calc_device_limit_on_tariff_switch

        old_tariff = await get_tariff_by_id(db, subscription.tariff_id) if subscription.tariff_id else None

        subscription.tariff_id = request.tariff_id
        subscription.traffic_limit_gb = tariff.traffic_limit_gb
        subscription.device_limit = calc_device_limit_on_tariff_switch(
            current_device_limit=subscription.device_limit,
            old_tariff_device_limit=old_tariff.device_limit if old_tariff else None,
            new_tariff_device_limit=tariff.device_limit,
            max_device_limit=tariff.max_device_limit,
        )
        # Set squads from tariff
        if tariff.allowed_squads:
            subscription.connected_squads = tariff.allowed_squads

        # Convert trial subscription to paid when switching to a non-trial tariff
        if subscription.is_trial and not tariff.is_trial_available:
            subscription.is_trial = False
            if subscription.end_date and subscription.end_date > datetime.now(UTC):
                subscription.status = SubscriptionStatus.ACTIVE.value
            logger.info('Converted trial subscription to paid', user_id=user_id, tariff_name=tariff.name)

        # Сбрасываем докупленный трафик при смене тарифа
        from sqlalchemy import delete as sql_delete

        await db.execute(sql_delete(TrafficPurchase).where(TrafficPurchase.subscription_id == subscription.id))
        subscription.purchased_traffic_gb = 0
        subscription.traffic_reset_at = None

        if settings.RESET_TRAFFIC_ON_TARIFF_SWITCH:
            subscription.traffic_used_gb = 0.0

        # Записываем транзакцию о смене тарифа
        from app.database.crud.transaction import create_transaction

        await create_transaction(
            db=db,
            user_id=user.id,
            type=TransactionType.SUBSCRIPTION_PAYMENT,
            amount_kopeks=0,
            description=f"Смена тарифа администратором на '{tariff.name}'",
            commit=False,
        )

        await db.commit()
        await db.refresh(subscription)

        # Синхронизируем с RemnaWave (discovery/create + сброс трафика по админ-настройке)
        try:
            await _sync_subscription_to_panel(
                db,
                user,
                subscription,
                reset_traffic=settings.RESET_TRAFFIC_ON_TARIFF_SWITCH,
                reset_traffic_reason='смена тарифа (cabinet admin)',
            )
        except Exception as e:
            logger.error('Failed to sync tariff switch with RemnaWave', error=e)

        logger.info('Admin changed tariff for user to', admin_id=admin.id, user_id=user_id, tariff_name=tariff.name)

        return UpdateSubscriptionResponse(
            success=True,
            message=f'Tariff changed to {tariff.name}',
            subscription=await _build_subscription_info_async(db, subscription),
        )

    if request.action == 'set_traffic':
        if request.traffic_limit_gb is not None:
            subscription.traffic_limit_gb = request.traffic_limit_gb

        if request.traffic_used_gb is not None:
            subscription.traffic_used_gb = request.traffic_used_gb

        await db.commit()
        await db.refresh(subscription)

        # Sync to Remnawave panel
        await _sync_subscription_to_panel(db, user, subscription)

        logger.info('Admin updated traffic for user', admin_id=admin.id, user_id=user_id)

        return UpdateSubscriptionResponse(
            success=True,
            message='Traffic settings updated',
            subscription=await _build_subscription_info_async(db, subscription),
        )

    if request.action == 'toggle_autopay':
        if request.autopay_enabled is None:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail='autopay_enabled parameter is required',
            )

        subscription.autopay_enabled = request.autopay_enabled
        await db.commit()
        await db.refresh(subscription)

        state = 'enabled' if request.autopay_enabled else 'disabled'
        logger.info('Admin autopay for user', admin_id=admin.id, state=state, user_id=user_id)

        return UpdateSubscriptionResponse(
            success=True,
            message=f'Autopay {state}',
            subscription=await _build_subscription_info_async(db, subscription),
        )

    if request.action == 'cancel':
        subscription.status = SubscriptionStatus.EXPIRED.value
        subscription.end_date = datetime.now(UTC)
        # For daily tariffs: mark as paused to prevent auto-resume by DailySubscriptionService
        if subscription.tariff and getattr(subscription.tariff, 'is_daily', False):
            subscription.is_daily_paused = True
        await db.commit()
        await db.refresh(subscription)

        # Sync to Remnawave panel
        await _sync_subscription_to_panel(db, user, subscription)

        logger.info('Admin cancelled subscription for user', admin_id=admin.id, user_id=user_id)

        return UpdateSubscriptionResponse(
            success=True,
            message='Subscription cancelled',
            subscription=await _build_subscription_info_async(db, subscription),
        )

    if request.action == 'activate':
        # Проверка: нельзя активировать, если у пользователя уже есть
        # другая активная подписка с тем же тарифом
        if is_multi_tariff and subscription.tariff_id:
            from app.database.crud.subscription import get_subscription_by_user_and_tariff

            existing = await get_subscription_by_user_and_tariff(db, user.id, subscription.tariff_id)
            if existing and existing.id != subscription.id:
                raise HTTPException(
                    status_code=status.HTTP_409_CONFLICT,
                    detail='Cannot activate: user already has an active subscription for this tariff',
                )

        subscription.status = SubscriptionStatus.ACTIVE.value
        if subscription.end_date and subscription.end_date <= datetime.now(UTC):
            # Extend by 30 days if expired
            subscription.end_date = datetime.now(UTC) + timedelta(days=30)
        await db.commit()
        await db.refresh(subscription)

        # Sync to Remnawave panel
        await _sync_subscription_to_panel(db, user, subscription)

        logger.info('Admin activated subscription for user', admin_id=admin.id, user_id=user_id)

        return UpdateSubscriptionResponse(
            success=True,
            message='Subscription activated',
            subscription=await _build_subscription_info_async(db, subscription),
        )

    if request.action == 'add_traffic':
        if not request.traffic_gb:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail='traffic_gb parameter is required for add_traffic action',
            )

        from app.database.crud.subscription import add_subscription_traffic, reactivate_subscription

        await add_subscription_traffic(db, subscription, request.traffic_gb)

        # Реактивируем подписку если она была DISABLED/EXPIRED (например, после LIMITED/EXPIRED в RemnaWave)
        await reactivate_subscription(db, subscription)

        await db.refresh(subscription)

        # Sync to Remnawave panel
        await _sync_subscription_to_panel(db, user, subscription)

        # Явно включаем пользователя на панели (PATCH может не снять LIMITED-статус)
        _enable_uuid = (
            subscription.remnawave_uuid if settings.is_multi_tariff_enabled() else getattr(user, 'remnawave_uuid', None)
        )
        if _enable_uuid and subscription.status == 'active':
            from app.services.subscription_service import SubscriptionService

            subscription_service = SubscriptionService()
            await subscription_service.enable_remnawave_user(_enable_uuid)

        logger.info('Admin added traffic for user', admin_id=admin.id, traffic_gb=request.traffic_gb, user_id=user_id)

        return UpdateSubscriptionResponse(
            success=True,
            message=f'Added {request.traffic_gb} GB traffic (30 days)',
            subscription=await _build_subscription_info_async(db, subscription),
        )

    if request.action == 'remove_traffic':
        if not request.traffic_purchase_id:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail='traffic_purchase_id parameter is required for remove_traffic action',
            )

        # Find the traffic purchase
        tp_query = select(TrafficPurchase).where(
            TrafficPurchase.id == request.traffic_purchase_id,
            TrafficPurchase.subscription_id == subscription.id,
        )
        tp_result = await db.execute(tp_query)
        traffic_purchase = tp_result.scalar_one_or_none()
        if not traffic_purchase:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail='Traffic purchase not found',
            )

        removed_gb = traffic_purchase.traffic_gb

        # Decrement counters
        subscription.traffic_limit_gb = max(0, subscription.traffic_limit_gb - removed_gb)
        current_purchased = getattr(subscription, 'purchased_traffic_gb', 0) or 0
        subscription.purchased_traffic_gb = max(0, current_purchased - removed_gb)

        # Delete the purchase record
        await db.delete(traffic_purchase)

        # Recalculate traffic_reset_at from remaining active purchases
        now = datetime.now(UTC)
        remaining_query = select(TrafficPurchase).where(
            TrafficPurchase.subscription_id == subscription.id,
            TrafficPurchase.expires_at > now,
            TrafficPurchase.id != request.traffic_purchase_id,
        )
        remaining_result = await db.execute(remaining_query)
        remaining_purchases = remaining_result.scalars().all()

        if remaining_purchases:
            subscription.traffic_reset_at = min(p.expires_at for p in remaining_purchases)
        else:
            subscription.traffic_reset_at = None

        await db.commit()
        await db.refresh(subscription)

        # Sync to Remnawave panel
        await _sync_subscription_to_panel(db, user, subscription)

        logger.info(
            'Admin removed traffic purchase ( GB) for user',
            admin_id=admin.id,
            traffic_purchase_id=request.traffic_purchase_id,
            removed_gb=removed_gb,
            user_id=user_id,
        )

        return UpdateSubscriptionResponse(
            success=True,
            message=f'Removed {removed_gb} GB traffic package',
            subscription=await _build_subscription_info_async(db, subscription),
        )

    if request.action == 'set_device_limit':
        if request.device_limit is None:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail='device_limit parameter is required for set_device_limit action',
            )

        subscription.device_limit = request.device_limit
        await db.commit()
        await db.refresh(subscription)

        # Sync to Remnawave panel
        await _sync_subscription_to_panel(db, user, subscription)

        logger.info(
            'Admin set device limit to for user', admin_id=admin.id, device_limit=request.device_limit, user_id=user_id
        )

        return UpdateSubscriptionResponse(
            success=True,
            message=f'Device limit set to {request.device_limit}',
            subscription=await _build_subscription_info_async(db, subscription),
        )

    raise HTTPException(
        status_code=status.HTTP_400_BAD_REQUEST,
        detail=f'Unknown action: {request.action}',
    )


# === Available Tariffs ===


@router.get('/{user_id}/available-tariffs', response_model=UserAvailableTariffsResponse)
async def get_user_available_tariffs(
    user_id: int,
    include_inactive: bool = Query(False, description='Include inactive tariffs'),
    admin: User = Depends(require_permission('users:read')),
    db: AsyncSession = Depends(get_cabinet_db),
):
    """
    Get list of tariffs available for a specific user.

    Takes into account user's promo group to determine which tariffs are accessible.
    Shows all tariffs with availability flag.
    """
    user = await get_user_by_id(db, user_id)
    if not user:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail='User not found',
        )

    # Get all tariffs
    from app.database.crud.tariff import get_all_tariffs

    tariffs = await get_all_tariffs(db, include_inactive=include_inactive)

    # Get current subscription tariff
    current_tariff_id = None
    current_tariff_name = None
    subs = getattr(user, 'subscriptions', None) or []
    subscription = next((s for s in subs if s.is_active), subs[0] if subs else None)
    if subscription and subscription.tariff_id:
        current_tariff_id = subscription.tariff_id
        if subscription.tariff:
            current_tariff_name = subscription.tariff.name

    # Build tariff items
    tariff_items = []
    for tariff in tariffs:
        # Check if available for user's promo group
        is_available = tariff.is_available_for_promo_group(user.promo_group_id)
        requires_promo_group = bool(tariff.allowed_promo_groups)

        # Build period prices
        period_prices = []
        if tariff.period_prices:
            for days_str, price_kopeks in sorted(tariff.period_prices.items(), key=lambda x: int(x[0])):
                days = int(days_str)
                period_prices.append(
                    PeriodPriceInfo(
                        days=days,
                        price_kopeks=price_kopeks,
                        price_rubles=price_kopeks / 100,
                    )
                )

        tariff_items.append(
            UserAvailableTariffItem(
                id=tariff.id,
                name=tariff.name,
                description=tariff.description,
                is_active=tariff.is_active,
                is_trial_available=tariff.is_trial_available,
                traffic_limit_gb=tariff.traffic_limit_gb,
                device_limit=tariff.device_limit,
                tier_level=tariff.tier_level,
                display_order=tariff.display_order,
                period_prices=period_prices,
                is_daily=tariff.is_daily,
                daily_price_kopeks=tariff.daily_price_kopeks,
                custom_days_enabled=tariff.custom_days_enabled,
                price_per_day_kopeks=tariff.price_per_day_kopeks,
                min_days=tariff.min_days,
                max_days=tariff.max_days,
                device_price_kopeks=tariff.device_price_kopeks,
                max_device_limit=tariff.max_device_limit,
                traffic_topup_enabled=tariff.traffic_topup_enabled,
                traffic_topup_packages=tariff.traffic_topup_packages or {},
                max_topup_traffic_gb=tariff.max_topup_traffic_gb,
                is_available=is_available,
                requires_promo_group=requires_promo_group,
            )
        )

    # Sort by display_order, then by tier_level
    tariff_items.sort(key=lambda t: (t.display_order, t.tier_level))

    return UserAvailableTariffsResponse(
        user_id=user.id,
        promo_group_id=user.promo_group_id,
        promo_group_name=user.promo_group.name if user.promo_group else None,
        tariffs=tariff_items,
        total=len(tariff_items),
        current_tariff_id=current_tariff_id,
        current_tariff_name=current_tariff_name,
    )


# === Status Management ===


@router.post('/{user_id}/status', response_model=UpdateUserStatusResponse)
async def update_user_status(
    user_id: int,
    request: UpdateUserStatusRequest,
    admin: User = Depends(require_permission('users:edit')),
    db: AsyncSession = Depends(get_cabinet_db),
):
    """Update user status (active, blocked, deleted)."""
    user = await get_user_by_id(db, user_id)
    if not user:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail='User not found',
        )

    old_status = user.status
    new_status = request.status.value

    if old_status == new_status:
        return UpdateUserStatusResponse(
            success=True,
            old_status=old_status,
            new_status=new_status,
            message='Status unchanged',
        )

    user.status = new_status
    user.updated_at = datetime.now(UTC)
    await db.commit()
    await db.refresh(user)

    action = f'{old_status} -> {new_status}'
    if request.reason:
        action += f' (reason: {request.reason})'

    logger.info('Admin changed status for user', admin_id=admin.id, user_id=user_id, action=action)

    return UpdateUserStatusResponse(
        success=True,
        old_status=old_status,
        new_status=new_status,
        message=f'Status changed from {old_status} to {new_status}',
    )


@router.post('/{user_id}/block', response_model=UpdateUserStatusResponse)
async def block_user(
    user_id: int,
    reason: str | None = None,
    admin: User = Depends(require_permission('users:block')),
    db: AsyncSession = Depends(get_cabinet_db),
):
    """Block a user — sets DB status AND disables panel user in RemnaWave."""
    from app.services.user_service import UserService

    user_service = UserService()
    success = await user_service.block_user(
        db,
        user_id,
        admin.id,
        reason=reason or 'Заблокирован администратором',
    )
    if not success:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='User not found or block failed')

    return UpdateUserStatusResponse(
        success=True,
        old_status='active',
        new_status='blocked',
        message='User blocked',
    )


@router.post('/{user_id}/unblock', response_model=UpdateUserStatusResponse)
async def unblock_user(
    user_id: int,
    admin: User = Depends(require_permission('users:block')),
    db: AsyncSession = Depends(get_cabinet_db),
):
    """Unblock a user — sets DB status AND re-enables panel user in RemnaWave."""
    from app.services.user_service import UserService

    user_service = UserService()
    success = await user_service.unblock_user(db, user_id, admin.id)
    if not success:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='User not found or unblock failed')

    return UpdateUserStatusResponse(
        success=True,
        old_status='blocked',
        new_status='active',
        message='User unblocked',
    )


# === Restrictions Management ===


@router.post('/{user_id}/restrictions', response_model=UpdateRestrictionsResponse)
async def update_user_restrictions(
    user_id: int,
    request: UpdateRestrictionsRequest,
    admin: User = Depends(require_permission('users:edit')),
    db: AsyncSession = Depends(get_cabinet_db),
):
    """Update user restrictions (topup, subscription)."""
    user = await get_user_by_id(db, user_id)
    if not user:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail='User not found',
        )

    if request.restriction_topup is not None:
        user.restriction_topup = request.restriction_topup

    if request.restriction_subscription is not None:
        user.restriction_subscription = request.restriction_subscription

    if request.restriction_reason is not None:
        user.restriction_reason = request.restriction_reason

    user.updated_at = datetime.now(UTC)
    await db.commit()
    await db.refresh(user)

    logger.info(
        'Admin updated restrictions for user topup=, subscription',
        admin_id=admin.id,
        user_id=user_id,
        restriction_topup=user.restriction_topup,
        restriction_subscription=user.restriction_subscription,
    )

    return UpdateRestrictionsResponse(
        success=True,
        restriction_topup=user.restriction_topup,
        restriction_subscription=user.restriction_subscription,
        restriction_reason=user.restriction_reason,
        message='Restrictions updated',
    )


# === Promo Group Management ===


@router.post('/{user_id}/promo-group', response_model=UpdatePromoGroupResponse)
async def update_user_promo_group(
    user_id: int,
    request: UpdatePromoGroupRequest,
    admin: User = Depends(require_permission('users:promo_group')),
    db: AsyncSession = Depends(get_cabinet_db),
):
    """Update user promo group."""
    user = await get_user_by_id(db, user_id)
    if not user:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail='User not found',
        )

    old_promo_group_id = user.promo_group_id
    new_promo_group_id = request.promo_group_id
    promo_group_name = None

    if new_promo_group_id is not None:
        # Verify promo group exists
        result = await db.execute(select(PromoGroup).where(PromoGroup.id == new_promo_group_id))
        promo_group = result.scalar_one_or_none()
        if not promo_group:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail='Promo group not found',
            )
        promo_group_name = promo_group.name

    # Update M2M table (authoritative source) — not just the legacy FK column.
    # Without this, sync_user_primary_promo_group overwrites the admin change
    # on the next transaction.
    await db.execute(sa_delete(UserPromoGroup).where(UserPromoGroup.user_id == user_id))

    if new_promo_group_id is not None:
        db.add(
            UserPromoGroup(
                user_id=user_id,
                promo_group_id=new_promo_group_id,
                assigned_by='admin',
            )
        )

    await db.flush()
    await sync_user_primary_promo_group(db, user_id)
    await db.commit()
    await db.refresh(user)

    logger.info(
        'Admin changed promo group for user',
        admin_id=admin.id,
        user_id=user_id,
        old_promo_group_id=old_promo_group_id,
        new_promo_group_id=new_promo_group_id,
    )

    return UpdatePromoGroupResponse(
        success=True,
        old_promo_group_id=old_promo_group_id,
        new_promo_group_id=new_promo_group_id,
        promo_group_name=promo_group_name,
        message='Promo group updated',
    )


# === Referral Commission ===


@router.post('/{user_id}/referral-commission', response_model=UpdateReferralCommissionResponse)
async def update_user_referral_commission(
    user_id: int,
    request: UpdateReferralCommissionRequest,
    admin: User = Depends(require_permission('users:referral')),
    db: AsyncSession = Depends(get_cabinet_db),
):
    """Update user's individual referral commission percentage."""
    # Prevent admin from modifying their own commission
    if user_id == admin.id:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail='Admin cannot modify their own referral commission',
        )

    user = await get_user_by_id(db, user_id)
    if not user:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail='User not found',
        )

    old_commission = user.referral_commission_percent
    user.referral_commission_percent = request.commission_percent
    user.updated_at = datetime.now(UTC)
    await PermissionService.log_action(
        db,
        user_id=admin.id,
        action='update_referral_commission',
        resource_type='user',
        resource_id=str(user_id),
        details={'old_commission': old_commission, 'new_commission': request.commission_percent},
    )
    await db.commit()

    logger.info(
        'Admin changed referral commission for user',
        admin_id=admin.id,
        user_id=user_id,
        old_commission=old_commission,
        commission_percent=request.commission_percent,
    )

    return UpdateReferralCommissionResponse(
        success=True,
        old_commission_percent=old_commission,
        new_commission_percent=request.commission_percent,
        message='Referral commission updated',
    )


# === Assign Referrer ===


@router.post('/{user_id}/assign-referrer', response_model=AssignReferrerResponse)
async def assign_user_referrer(
    user_id: int,
    request: AssignReferrerRequest,
    admin: User = Depends(require_permission('users:referral')),
    db: AsyncSession = Depends(get_cabinet_db),
):
    """Manually assign a referrer to a user (e.g. cabinet-registered users without telegram_id).

    Bonuses are NOT triggered immediately — they will apply on the user's next topup.
    """
    user = await get_user_by_id(db, user_id)
    if not user:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail='User not found',
        )

    if user_id == request.referrer_id:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail='User cannot be their own referrer',
        )

    # Prevent admin self-enrichment
    if request.referrer_id == admin.id:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail='Admin cannot assign themselves as referrer',
        )

    referrer = await get_user_by_id(db, request.referrer_id)
    if not referrer:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail='Referrer user not found',
        )

    # Prevent circular referral chains of any depth via recursive CTE
    if await _would_create_referral_cycle(db, user_id, request.referrer_id):
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail='Circular referral: assigning this referrer would create a cycle in the referral chain',
        )

    old_referrer_id = user.referred_by_id
    user.referred_by_id = request.referrer_id
    user.updated_at = datetime.now(UTC)
    await PermissionService.log_action(
        db,
        user_id=admin.id,
        action='assign_referrer',
        resource_type='user',
        resource_id=str(user_id),
        details={'old_referrer_id': old_referrer_id, 'new_referrer_id': request.referrer_id},
    )
    await db.commit()

    logger.info(
        'Admin assigned referrer to user',
        admin_id=admin.id,
        user_id=user_id,
        old_referrer_id=old_referrer_id,
        new_referrer_id=request.referrer_id,
    )

    return AssignReferrerResponse(
        success=True,
        old_referrer_id=old_referrer_id,
        new_referrer_id=request.referrer_id,
        message='Referrer assigned successfully. Bonuses will apply on next user topup.',
    )


# === Remove Referrer ===


@router.delete('/{user_id}/referrer', response_model=RemoveReferrerResponse)
async def remove_user_referrer(
    user_id: int,
    admin: User = Depends(require_permission('users:referral')),
    db: AsyncSession = Depends(get_cabinet_db),
):
    """Remove who referred this user (set referred_by_id to None)."""
    user = await get_user_by_id(db, user_id)
    if not user:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail='User not found',
        )

    if user.referred_by_id is None:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail='User does not have a referrer',
        )

    old_referrer_id = user.referred_by_id
    user.referred_by_id = None
    user.updated_at = datetime.now(UTC)
    await PermissionService.log_action(
        db,
        user_id=admin.id,
        action='remove_referrer',
        resource_type='user',
        resource_id=str(user_id),
        details={'old_referrer_id': old_referrer_id},
    )
    await db.commit()

    logger.info(
        'Admin removed referrer from user',
        admin_id=admin.id,
        user_id=user_id,
        old_referrer_id=old_referrer_id,
    )

    return RemoveReferrerResponse(
        success=True,
        old_referrer_id=old_referrer_id,
        message='Referrer removed successfully',
    )


# === Remove Referral ===


@router.delete('/{user_id}/referrals/{referral_user_id}', response_model=RemoveReferralResponse)
async def remove_user_referral(
    user_id: int,
    referral_user_id: int,
    admin: User = Depends(require_permission('users:referral')),
    db: AsyncSession = Depends(get_cabinet_db),
):
    """Remove a specific referral from a user (unbind referral_user from this referrer)."""
    # Verify the referrer user exists
    referrer = await get_user_by_id(db, user_id)
    if not referrer:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail='Referrer user not found',
        )

    referral_user = await get_user_by_id(db, referral_user_id)
    if not referral_user:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail='Referral user not found',
        )

    if referral_user.referred_by_id != user_id:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail='This user is not a referral of the specified referrer',
        )

    referral_user.referred_by_id = None
    referral_user.updated_at = datetime.now(UTC)
    await PermissionService.log_action(
        db,
        user_id=admin.id,
        action='remove_referral',
        resource_type='user',
        resource_id=str(user_id),
        details={'removed_referral_user_id': referral_user_id},
    )
    await db.commit()

    logger.info(
        'Admin removed referral from user',
        admin_id=admin.id,
        referrer_user_id=user_id,
        removed_referral_user_id=referral_user_id,
    )

    return RemoveReferralResponse(
        success=True,
        removed_user_id=referral_user_id,
        message='Referral removed successfully',
    )


async def _would_create_referral_cycle(db: AsyncSession, user_id: int, referrer_id: int) -> bool:
    """Walk the referrer's ancestor chain; if user_id appears, a cycle would form."""
    max_depth = 50
    anchor = (
        select(User.id, User.referred_by_id, literal(0).label('depth'))
        .where(User.id == referrer_id)
        .cte(name='ancestors', recursive=True)
    )
    rpart = (
        select(User.id, User.referred_by_id, (anchor.c.depth + 1).label('depth'))
        .join(anchor, User.id == anchor.c.referred_by_id)
        .where(anchor.c.depth < max_depth)
    )
    ancestors_cte = anchor.union_all(rpart)
    result = await db.execute(
        select(literal(1)).where(ancestors_cte.c.id == user_id).select_from(ancestors_cte).limit(1)
    )
    return result.scalar_one_or_none() is not None


# === Devices ===


@router.get('/{user_id}/devices', response_model=UserDevicesResponse)
async def get_user_devices(
    user_id: int,
    admin: User = Depends(require_permission('users:read')),
    db: AsyncSession = Depends(get_cabinet_db),
    subscription_id: int | None = Query(None, description='Subscription ID for multi-tariff'),
):
    """Get user devices from Remnawave panel."""
    user = await get_user_by_id(db, user_id)
    if not user:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='User not found')

    # Resolve panel UUID
    _dev_uuid = None
    if settings.is_multi_tariff_enabled() and subscription_id:
        from app.database.crud.subscription import get_subscription_by_id_for_user

        sub = await get_subscription_by_id_for_user(db, subscription_id, user_id)
        if sub:
            _dev_uuid = sub.remnawave_uuid
    else:
        _dev_uuid = user.remnawave_uuid

    if not _dev_uuid:
        return UserDevicesResponse()

    try:
        from app.services.remnawave_service import RemnaWaveService

        service = RemnaWaveService()
        if not service.is_configured:
            return UserDevicesResponse()

        async with service.get_api_client() as api:
            response = await api.get_user_devices_all(_dev_uuid)

            devices = []
            for d in response.get('devices', []):
                hwid = d.get('hwid') or d.get('deviceId') or d.get('id')
                if not hwid:
                    continue
                devices.append(
                    DeviceInfo(
                        hwid=hwid,
                        platform=d.get('platform') or d.get('platformType') or '',
                        device_model=d.get('deviceModel') or d.get('model') or d.get('name') or '',
                        created_at=d.get('updatedAt') or d.get('lastSeen') or d.get('createdAt'),
                    )
                )

            device_limit = 0
            subs = getattr(user, 'subscriptions', None) or []
            subscription = next((s for s in subs if s.is_active), subs[0] if subs else None)
            if subscription:
                device_limit = subscription.device_limit or 0

            return UserDevicesResponse(
                devices=devices,
                total=response.get('total', len(devices)),
                device_limit=device_limit,
            )

    except Exception as e:
        logger.error('Error fetching devices for user', user_id=user_id, error=e)
        return UserDevicesResponse()


@router.delete('/{user_id}/devices/{hwid}', response_model=DeleteDeviceResponse)
async def delete_user_device(
    user_id: int,
    hwid: str,
    admin: User = Depends(require_permission('users:edit')),
    db: AsyncSession = Depends(get_cabinet_db),
    subscription_id: int | None = Query(None, description='Subscription ID for multi-tariff'),
):
    """Delete a single device for user."""
    user = await get_user_by_id(db, user_id)
    if not user:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='User not found')

    _uuid = None
    if settings.is_multi_tariff_enabled() and subscription_id:
        from app.database.crud.subscription import get_subscription_by_id_for_user

        sub = await get_subscription_by_id_for_user(db, subscription_id, user_id)
        if sub:
            _uuid = sub.remnawave_uuid
    else:
        _uuid = user.remnawave_uuid

    if not _uuid:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='User has no panel account')

    try:
        from app.services.remnawave_service import RemnaWaveService

        service = RemnaWaveService()
        async with service.get_api_client() as api:
            success = await api.remove_device(_uuid, hwid)

        if success:
            logger.info('Admin deleted device for user', admin_id=admin.id, hwid=hwid, user_id=user_id)
            return DeleteDeviceResponse(success=True, message='Device deleted', deleted_hwid=hwid)
        return DeleteDeviceResponse(success=False, message='Failed to delete device')

    except Exception as e:
        logger.error('Error deleting device for user', hwid=hwid, user_id=user_id, error=e)
        return DeleteDeviceResponse(success=False, message='Ошибка удаления устройства')


@router.delete('/{user_id}/devices', response_model=ResetDevicesResponse)
async def reset_user_devices(
    user_id: int,
    admin: User = Depends(require_permission('users:edit')),
    db: AsyncSession = Depends(get_cabinet_db),
    subscription_id: int | None = Query(None, description='Subscription ID for multi-tariff'),
):
    """Reset all devices for user."""
    user = await get_user_by_id(db, user_id)
    if not user:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='User not found')

    _rst_uuid = None
    if settings.is_multi_tariff_enabled() and subscription_id:
        from app.database.crud.subscription import get_subscription_by_id_for_user

        sub = await get_subscription_by_id_for_user(db, subscription_id, user_id)
        if sub:
            _rst_uuid = sub.remnawave_uuid
    else:
        _rst_uuid = user.remnawave_uuid

    if not _rst_uuid:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='User has no panel account')

    try:
        from app.services.remnawave_service import RemnaWaveService

        service = RemnaWaveService()
        async with service.get_api_client() as api:
            devices_info = await api.get_user_devices_all(_rst_uuid)
            devices = devices_info.get('devices', [])
            total = len(devices)

            if total == 0:
                return ResetDevicesResponse(success=True, message='No devices to reset', deleted_count=0)

            deleted = 0
            for d in devices:
                device_hwid = d.get('hwid') or d.get('deviceId') or d.get('id')
                if device_hwid:
                    try:
                        await api.remove_device(_rst_uuid, device_hwid)
                        deleted += 1
                    except Exception:
                        pass

        logger.info('Admin reset devices for user /', admin_id=admin.id, user_id=user_id, deleted=deleted, total=total)
        return ResetDevicesResponse(success=True, message=f'Deleted {deleted}/{total} devices', deleted_count=deleted)

    except Exception as e:
        logger.error('Error resetting devices for user', user_id=user_id, error=e)
        return ResetDevicesResponse(success=False, message='Ошибка сброса устройств')


# === Delete User ===


@router.delete('/{user_id}', response_model=DeleteUserResponse)
async def delete_user(
    user_id: int,
    request: DeleteUserRequest = DeleteUserRequest(),
    admin: User = Depends(require_permission('users:delete')),
    db: AsyncSession = Depends(get_cabinet_db),
):
    """
    Delete a user.

    - **soft_delete=True**: Mark user as deleted (default)
    - **soft_delete=False**: Permanently delete from database
    """
    user = await get_user_by_id(db, user_id)
    if not user:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail='User not found',
        )

    if request.soft_delete:
        await soft_delete_user(db, user)
        action = 'soft deleted'
    else:
        # Hard delete
        await db.delete(user)
        await db.commit()
        action = 'permanently deleted'

    reason_text = f' (reason: {request.reason})' if request.reason else ''
    logger.info('Admin user', admin_id=admin.id, action=action, user_id=user_id, reason_text=reason_text)

    return DeleteUserResponse(
        success=True,
        message=f'User {action} successfully',
    )


@router.delete('/{user_id}/full', response_model=FullDeleteUserResponse)
async def full_delete_user(
    user_id: int,
    request: FullDeleteUserRequest = FullDeleteUserRequest(),
    admin: User = Depends(require_permission('users:delete')),
    db: AsyncSession = Depends(get_cabinet_db),
):
    """
    Full user deletion - removes from bot database AND Remnawave panel.

    Uses UserService.delete_user_account() which handles:
    - Deleting/disabling user in Remnawave panel
    - Removing all related records (payments, transactions, etc.)
    - Removing user from database
    """
    from app.services.user_service import UserService

    user = await get_user_by_id(db, user_id)
    if not user:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail='User not found',
        )

    # Pre-fetch admin.id to avoid MissingGreenlet after transaction rollback
    admin_id_val = admin.id

    # UserService.delete_user_account handles both bot DB and Remnawave panel
    user_service = UserService()
    delete_result = await user_service.delete_user_account(
        db, user_id, admin_id_val, force_panel_delete=request.delete_from_panel
    )

    reason_text = f' (reason: {request.reason})' if request.reason else ''
    logger.info(
        'Admin fully deleted user',
        admin_id=admin_id_val,
        user_id=user_id,
        reason_text=reason_text,
        bot_deleted=delete_result.bot_deleted,
        panel_deleted=delete_result.panel_deleted,
        panel_error=delete_result.panel_error,
    )

    return FullDeleteUserResponse(
        success=delete_result.bot_deleted,
        message='User fully deleted from bot and panel' if delete_result.bot_deleted else 'Failed to delete user',
        deleted_from_bot=delete_result.bot_deleted,
        deleted_from_panel=delete_result.panel_deleted,
        panel_error=delete_result.panel_error,
    )


@router.post('/{user_id}/reset-trial', response_model=ResetTrialResponse)
async def reset_user_trial(
    user_id: int,
    request: ResetTrialRequest = ResetTrialRequest(),
    admin: User = Depends(require_permission('users:subscription')),
    db: AsyncSession = Depends(get_cabinet_db),
):
    """
    Reset user trial - allows user to activate trial again.

    Actions:
    - Delete current subscription if exists
    - Reset has_used_trial flag to False
    - User can now activate a new trial
    """
    user = await get_user_by_id(db, user_id)
    if not user:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail='User not found',
        )

    subscription_deleted = False

    # Delete subscriptions if any exist
    subs = getattr(user, 'subscriptions', None) or []
    if subs:
        from app.database.crud.subscription import is_active_paid_subscription

        # In multi-tariff: only delete trial subscriptions, keep paid ones
        trial_subs = [s for s in subs if s.is_trial]
        non_trial_subs = [s for s in subs if not s.is_trial]

        subs_to_delete = trial_subs if (settings.is_multi_tariff_enabled() and non_trial_subs) else subs

        if not subs_to_delete:
            logger.info('No trial subscriptions to delete', user_id=user_id)
        else:
            # Check if we'd be deleting paid subscriptions
            has_active_paid = any(is_active_paid_subscription(s) for s in subs_to_delete)
            if has_active_paid:
                logger.info(
                    '⏭️ Пропуск удаления: среди удаляемых есть активная оплаченная подписка',
                    user_id=user_id,
                )
            else:
                # Deactivate in Remnawave panel first
                from app.services.subscription_service import SubscriptionService

                subscription_service = SubscriptionService()
                for sub in subs_to_delete:
                    _sub_uuid = sub.remnawave_uuid if settings.is_multi_tariff_enabled() else user.remnawave_uuid
                    if _sub_uuid:
                        try:
                            await subscription_service.disable_remnawave_user(_sub_uuid)
                        except Exception as e:
                            logger.warning('Failed to disable Remnawave during trial reset', error=e)

                # Delete only target subscriptions
                from sqlalchemy import delete

                for sub in subs_to_delete:
                    await db.execute(delete(SubscriptionServer).where(SubscriptionServer.subscription_id == sub.id))
                    await db.execute(delete(Subscription).where(Subscription.id == sub.id))
                subscription_deleted = True

    # Reset trial flag
    user.has_used_trial = False
    user.updated_at = datetime.now(UTC)

    await db.commit()

    reason_text = f' (reason: {request.reason})' if request.reason else ''
    logger.info('Admin reset trial for user', admin_id=admin.id, user_id=user_id, reason_text=reason_text)

    return ResetTrialResponse(
        success=True,
        message='Trial reset successfully. User can now activate a new trial.',
        subscription_deleted=subscription_deleted,
        has_used_trial_reset=True,
    )


@router.post('/{user_id}/reset-subscription', response_model=ResetSubscriptionResponse)
async def reset_user_subscription(
    user_id: int,
    request: ResetSubscriptionRequest = ResetSubscriptionRequest(),
    admin: User = Depends(require_permission('users:subscription')),
    db: AsyncSession = Depends(get_cabinet_db),
):
    """
    Reset user subscription - removes/deactivates subscription.

    Actions:
    - Delete subscription from bot database
    - Optionally deactivate in Remnawave panel
    - User will have no active subscription
    """
    user = await get_user_by_id(db, user_id)
    if not user:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail='User not found',
        )

    subscription_deleted = False
    panel_deactivated = False
    panel_error: str | None = None

    subs = getattr(user, 'subscriptions', None) or []
    if not subs:
        return ResetSubscriptionResponse(
            success=True,
            message='User has no subscription to reset',
            subscription_deleted=False,
            panel_deactivated=False,
        )

    # Deactivate in Remnawave panel if requested
    if request.deactivate_in_panel:
        try:
            from app.services.subscription_service import SubscriptionService

            subscription_service = SubscriptionService()
            if settings.is_multi_tariff_enabled():
                for sub in subs:
                    if sub.remnawave_uuid:
                        try:
                            await subscription_service.disable_remnawave_user(sub.remnawave_uuid)
                        except Exception:
                            pass
                panel_deactivated = True
            elif user.remnawave_uuid:
                panel_deactivated = await subscription_service.disable_remnawave_user(user.remnawave_uuid)
            if panel_deactivated:
                logger.info('Disabled Remnawave users for subscription reset', user_id=user_id)
        except Exception as e:
            panel_error = 'Ошибка обработки пользователя в Remnawave'
            logger.warning('Failed to disable Remnawave user during subscription reset', error=e)

    # Delete all subscriptions from database
    from sqlalchemy import delete

    for sub in subs:
        await db.execute(delete(SubscriptionServer).where(SubscriptionServer.subscription_id == sub.id))
    await db.execute(delete(Subscription).where(Subscription.user_id == user_id))
    subscription_deleted = True

    user.updated_at = datetime.now(UTC)
    await db.commit()

    reason_text = f' (reason: {request.reason})' if request.reason else ''
    logger.info('Admin reset subscription for user', admin_id=admin.id, user_id=user_id, reason_text=reason_text)

    return ResetSubscriptionResponse(
        success=True,
        message='Subscription reset successfully',
        subscription_deleted=subscription_deleted,
        panel_deactivated=panel_deactivated,
        panel_error=panel_error,
    )


@router.post('/{user_id}/disable', response_model=DisableUserResponse)
async def disable_user(
    user_id: int,
    request: DisableUserRequest = DisableUserRequest(),
    admin: User = Depends(require_permission('users:block')),
    db: AsyncSession = Depends(get_cabinet_db),
):
    """
    Disable user - deactivates subscription and blocks access.

    Actions:
    - Deactivate subscription in bot database
    - Deactivate in Remnawave panel
    - Block user account
    """
    user = await get_user_by_id(db, user_id)
    if not user:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail='User not found',
        )

    subscription_deactivated = False
    panel_deactivated = False
    panel_error: str | None = None

    # Deactivate subscriptions in panel (skip if active paid subscription)
    from app.database.crud.subscription import deactivate_subscription, is_active_paid_subscription

    subs = getattr(user, 'subscriptions', None) or []
    has_active_paid = any(is_active_paid_subscription(s) for s in subs)

    if has_active_paid:
        logger.info(
            '⏭️ Пропуск отключения RemnaWave: у пользователя активная оплаченная подписка',
            user_id=user_id,
            remnawave_uuid=user.remnawave_uuid,
        )
    else:
        try:
            from app.services.subscription_service import SubscriptionService

            subscription_service = SubscriptionService()
            if settings.is_multi_tariff_enabled():
                for sub in subs:
                    if sub.remnawave_uuid:
                        try:
                            await subscription_service.disable_remnawave_user(sub.remnawave_uuid)
                        except Exception:
                            pass
                panel_deactivated = True
            elif user.remnawave_uuid:
                panel_deactivated = await subscription_service.disable_remnawave_user(user.remnawave_uuid)
            if panel_deactivated:
                logger.info('Disabled Remnawave user(s)', user_id=user_id)
        except Exception as e:
            panel_error = 'Ошибка обработки пользователя в Remnawave'
            logger.warning('Failed to disable Remnawave user', error=e)

    # Deactivate all subscriptions in bot database (skip active paid ones)
    for sub in subs:
        if is_active_paid_subscription(sub):
            continue
        await deactivate_subscription(db, sub)
        # For daily: mark paused to prevent auto-resume
        if sub.tariff and getattr(sub.tariff, 'is_daily', False):
            sub.is_daily_paused = True
            await db.commit()
        subscription_deactivated = True
    if subscription_deactivated:
        logger.info('Deactivated subscriptions for user', user_id=user_id)

    # Block user account
    user.status = UserStatus.BLOCKED.value
    user.updated_at = datetime.now(UTC)
    await db.commit()

    reason_text = f' (reason: {request.reason})' if request.reason else ''
    logger.info('Admin disabled user', admin_id=admin.id, user_id=user_id, reason_text=reason_text)

    return DisableUserResponse(
        success=True,
        message='User disabled successfully',
        subscription_deactivated=subscription_deactivated,
        panel_deactivated=panel_deactivated,
        user_blocked=True,
        panel_error=panel_error,
    )


# === User Referrals ===


@router.get('/{user_id}/referrals', response_model=UsersListResponse)
async def get_user_referrals(
    user_id: int,
    offset: int = Query(0, ge=0),
    limit: int = Query(50, ge=1, le=200),
    admin: User = Depends(require_permission('users:read')),
    db: AsyncSession = Depends(get_cabinet_db),
):
    """Get list of users referred by this user."""
    user = await get_user_by_id(db, user_id)
    if not user:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail='User not found',
        )

    referrals = await get_referrals(db, user.id)

    # Apply pagination manually
    total = len(referrals)
    referrals = referrals[offset : offset + limit]

    # Get spending stats
    user_ids = [r.id for r in referrals]
    spending_stats = await get_users_spending_stats(db, user_ids) if user_ids else {}

    items = [_build_user_list_item(r, spending_stats) for r in referrals]

    return UsersListResponse(
        users=items,
        total=total,
        offset=offset,
        limit=limit,
    )


# === User Transactions ===


@router.get('/{user_id}/transactions')
async def get_user_transactions(
    user_id: int,
    offset: int = Query(0, ge=0),
    limit: int = Query(50, ge=1, le=200),
    transaction_type: str | None = Query(None),
    admin: User = Depends(require_permission('users:read')),
    db: AsyncSession = Depends(get_cabinet_db),
):
    """Get user transactions."""
    user = await get_user_by_id(db, user_id)
    if not user:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail='User not found',
        )

    query = select(Transaction).where(Transaction.user_id == user.id)

    if transaction_type:
        query = query.where(Transaction.type == transaction_type)

    # Get total count
    count_query = select(func.count(Transaction.id)).where(Transaction.user_id == user.id)
    if transaction_type:
        count_query = count_query.where(Transaction.type == transaction_type)
    total = (await db.execute(count_query)).scalar() or 0

    # Get transactions
    query = query.order_by(Transaction.created_at.desc()).offset(offset).limit(limit)
    result = await db.execute(query)
    transactions = result.scalars().all()

    _EXPENSE_TYPES = {
        TransactionType.WITHDRAWAL.value,
        TransactionType.SUBSCRIPTION_PAYMENT.value,
        TransactionType.GIFT_PAYMENT.value,
    }

    items = [
        UserTransactionItem(
            id=t.id,
            type=t.type,
            amount_kopeks=-abs(t.amount_kopeks) if t.type in _EXPENSE_TYPES else t.amount_kopeks,
            amount_rubles=-abs(t.amount_kopeks) / 100 if t.type in _EXPENSE_TYPES else t.amount_kopeks / 100,
            description=t.description,
            payment_method=t.payment_method,
            is_completed=t.is_completed,
            created_at=t.created_at,
        )
        for t in transactions
    ]

    return {
        'transactions': items,
        'total': total,
        'offset': offset,
        'limit': limit,
    }


# === Panel Sync ===


@router.get('/{user_id}/sync/status', response_model=PanelSyncStatusResponse)
async def get_user_sync_status(
    user_id: int,
    subscription_id: int | None = Query(None, description='Subscription ID for multi-tariff sync'),
    admin: User = Depends(require_permission('users:sync')),
    db: AsyncSession = Depends(get_cabinet_db),
):
    """
    Get sync status between bot and panel for a user.

    Shows differences between bot data and panel data.
    When subscription_id is provided, checks that specific subscription instead of first-active.
    """
    user = await get_user_by_id(db, user_id)
    if not user:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail='User not found',
        )

    # Bot data
    bot_sub_status = None
    bot_sub_end_date = None
    bot_traffic_limit = 0
    bot_traffic_used = 0.0
    bot_device_limit = 0
    bot_squads: list[str] = []

    subs = getattr(user, 'subscriptions', None) or []
    if subscription_id:
        active_sub = next((s for s in subs if s.id == subscription_id), None)
        if not active_sub:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail='Subscription not found',
            )
    else:
        active_sub = next((s for s in subs if s.is_active), subs[0] if subs else None)
    if active_sub:
        bot_sub_status = active_sub.status
        bot_sub_end_date = active_sub.end_date
        bot_traffic_limit = active_sub.traffic_limit_gb
        bot_traffic_used = active_sub.traffic_used_gb or 0.0
        bot_device_limit = active_sub.device_limit or 0
        bot_squads = active_sub.connected_squads or []

    # In multi-tariff mode, UUID lives on subscription, not user
    effective_uuid = (
        active_sub.remnawave_uuid
        if settings.is_multi_tariff_enabled() and active_sub and active_sub.remnawave_uuid
        else user.remnawave_uuid
    )

    # Panel data
    panel_found = False
    panel_status = None
    panel_expire_at = None
    panel_traffic_limit = 0.0
    panel_traffic_used = 0.0
    panel_device_limit = 0
    panel_squads: list[str] = []
    differences = []

    try:
        from app.services.remnawave_service import RemnaWaveService

        service = RemnaWaveService()
        if service.is_configured:
            async with service.get_api_client() as api:
                panel_user = None

                # Try by UUID first (works for all users including OAuth)
                if effective_uuid:
                    panel_user = await api.get_user_by_uuid(effective_uuid)

                # Fallback: search by telegram_id
                if not panel_user and user.telegram_id:
                    panel_users = await api.get_user_by_telegram_id(user.telegram_id)
                    if panel_users:
                        panel_user = panel_users[0]

                # Fallback: search by email (OAuth users)
                if not panel_user and user.email:
                    panel_users_by_email = await api.get_user_by_email(user.email)
                    if panel_users_by_email:
                        panel_user = panel_users_by_email[0]

                if panel_user:
                    panel_found = True
                    panel_status = panel_user.status.value if panel_user.status else None
                    panel_expire_at = panel_user.expire_at
                    panel_traffic_limit = (
                        panel_user.traffic_limit_bytes / (1024**3) if panel_user.traffic_limit_bytes else 0
                    )
                    panel_traffic_used = (
                        panel_user.used_traffic_bytes / (1024**3) if panel_user.used_traffic_bytes else 0
                    )
                    panel_device_limit = panel_user.hwid_device_limit or 0
                    # Extract squad UUIDs from active_internal_squads
                    panel_squads = [
                        s.get('uuid', '') for s in (panel_user.active_internal_squads or []) if s.get('uuid')
                    ]

                    # Check differences
                    if bot_sub_status and panel_status:
                        bot_active = bot_sub_status in ('active', 'trial')
                        panel_active = panel_status.upper() == 'ACTIVE'
                        if bot_active != panel_active:
                            differences.append(f'Status: bot={bot_sub_status}, panel={panel_status}')

                    if bot_sub_end_date and panel_expire_at:
                        bot_end_utc = bot_sub_end_date if bot_sub_end_date.tzinfo else bot_sub_end_date
                        panel_end_utc = panel_datetime_to_utc(panel_expire_at)

                        diff_seconds = abs((bot_end_utc - panel_end_utc).total_seconds())
                        # Allow for timezone offset (3 hours = MSK) and small sync delays
                        # If diff is ~3 hours (10800 sec) +/- 5 min, assume it's timezone issue
                        is_timezone_diff = abs(diff_seconds - 10800) < 300  # 3 hours +/- 5 min
                        if diff_seconds > 3600 and not is_timezone_diff:  # More than 1 hour and not timezone
                            differences.append(f'End date differs by {diff_seconds / 3600:.1f} hours')

                    if abs(bot_traffic_limit - panel_traffic_limit) > 1:
                        differences.append(
                            f'Traffic limit: bot={bot_traffic_limit}GB, panel={panel_traffic_limit:.1f}GB'
                        )

                    if abs(bot_traffic_used - panel_traffic_used) > 0.5:
                        differences.append(
                            f'Traffic used: bot={bot_traffic_used:.2f}GB, panel={panel_traffic_used:.2f}GB'
                        )

                    # Compare device limits
                    if bot_device_limit != panel_device_limit:
                        differences.append(f'Device limit: bot={bot_device_limit}, panel={panel_device_limit}')

                    # Compare squads
                    bot_squads_set = set(bot_squads) if bot_squads else set()
                    panel_squads_set = set(panel_squads) if panel_squads else set()
                    if bot_squads_set != panel_squads_set:
                        only_in_bot = bot_squads_set - panel_squads_set
                        only_in_panel = panel_squads_set - bot_squads_set
                        squad_diff_parts = []
                        if only_in_bot:
                            squad_diff_parts.append(f'only in bot: {len(only_in_bot)}')
                        if only_in_panel:
                            squad_diff_parts.append(f'only in panel: {len(only_in_panel)}')
                        differences.append(f'Squads mismatch ({", ".join(squad_diff_parts)})')

    except Exception as e:
        logger.warning('Failed to get panel data for user', user_id=user_id, error=e)
        differences.append(f'Error fetching panel data: {e!s}')

    # Resolve tariff name for context
    sub_tariff_name: str | None = None
    if active_sub:
        try:
            await db.refresh(active_sub, ['tariff'])
            if active_sub.tariff:
                sub_tariff_name = active_sub.tariff.name
        except Exception:
            pass

    return PanelSyncStatusResponse(
        user_id=user.id,
        telegram_id=user.telegram_id,
        remnawave_uuid=effective_uuid,
        last_sync=user.last_remnawave_sync,
        subscription_id=active_sub.id if active_sub else None,
        subscription_tariff_name=sub_tariff_name,
        bot_subscription_status=bot_sub_status,
        bot_subscription_end_date=bot_sub_end_date,
        bot_traffic_limit_gb=bot_traffic_limit,
        bot_traffic_used_gb=bot_traffic_used,
        bot_device_limit=bot_device_limit,
        bot_squads=bot_squads,
        panel_found=panel_found,
        panel_status=panel_status,
        panel_expire_at=panel_expire_at,
        panel_traffic_limit_gb=panel_traffic_limit,
        panel_traffic_used_gb=panel_traffic_used,
        panel_device_limit=panel_device_limit,
        panel_squads=panel_squads,
        has_differences=len(differences) > 0,
        differences=differences,
    )


@router.post('/{user_id}/sync/from-panel', response_model=SyncFromPanelResponse)
async def sync_user_from_panel(
    user_id: int,
    subscription_id: int | None = Query(None, description='Subscription ID for multi-tariff sync'),
    request: SyncFromPanelRequest = SyncFromPanelRequest(),
    admin: User = Depends(require_permission('users:sync')),
    db: AsyncSession = Depends(get_cabinet_db),
):
    """
    Sync user data FROM panel TO bot.

    Fetches user data from Remnawave panel and updates local database.
    When subscription_id is provided, syncs that specific subscription instead of first-active.
    """
    user = await get_user_by_id(db, user_id)
    if not user:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail='User not found',
        )

    try:
        from app.services.remnawave_service import RemnaWaveService

        service = RemnaWaveService()
        if not service.is_configured:
            raise HTTPException(
                status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
                detail=service.configuration_error or 'Remnawave API not configured',
            )

        changes = {}
        errors = []
        panel_info = None

        # Select the target subscription for sync
        from_subs = getattr(user, 'subscriptions', None) or []
        if subscription_id:
            selected_sub = next((s for s in from_subs if s.id == subscription_id), None)
            if not selected_sub:
                raise HTTPException(
                    status_code=status.HTTP_404_NOT_FOUND,
                    detail='Subscription not found',
                )
        else:
            selected_sub = None

        async with service.get_api_client() as api:
            # Find user in panel: UUID → telegram_id → email
            panel_user = None

            if settings.is_multi_tariff_enabled():
                if selected_sub and selected_sub.remnawave_uuid:
                    # Specific subscription requested — use its UUID directly
                    panel_user = await api.get_user_by_uuid(selected_sub.remnawave_uuid)
                elif selected_sub and not selected_sub.remnawave_uuid:
                    # Specific subscription requested but not yet linked to panel — cannot sync
                    raise HTTPException(
                        status_code=status.HTTP_400_BAD_REQUEST,
                        detail='This subscription is not linked to the panel yet. Sync to panel first.',
                    )
                else:
                    # No specific subscription — iterate all subscription UUIDs
                    sub_uuids = [s.remnawave_uuid for s in from_subs if s.remnawave_uuid]
                    for _uuid in sub_uuids:
                        panel_user = await api.get_user_by_uuid(_uuid)
                        if panel_user:
                            break
            elif user.remnawave_uuid:
                panel_user = await api.get_user_by_uuid(user.remnawave_uuid)

            if not panel_user and user.telegram_id:
                panel_users = await api.get_user_by_telegram_id(user.telegram_id)
                if panel_users:
                    panel_user = panel_users[0]

            if not panel_user and user.email:
                panel_users_by_email = await api.get_user_by_email(user.email)
                if panel_users_by_email:
                    panel_user = panel_users_by_email[0]

            if not panel_user:
                return SyncFromPanelResponse(
                    success=False,
                    message='User not found in panel',
                    errors=['No user found in Remnawave panel by UUID, telegram_id, or email'],
                )

            # Build panel info
            active_squads = []
            if hasattr(panel_user, 'active_internal_squads') and panel_user.active_internal_squads:
                for squad in panel_user.active_internal_squads:
                    if hasattr(squad, 'uuid'):
                        active_squads.append(squad.uuid)
                    elif isinstance(squad, str):
                        active_squads.append(squad)

            panel_info = PanelUserInfo(
                uuid=panel_user.uuid,
                short_uuid=panel_user.short_uuid,
                username=panel_user.username,
                status=panel_user.status.value if panel_user.status else None,
                expire_at=panel_datetime_to_utc(panel_user.expire_at) if panel_user.expire_at else None,
                traffic_limit_gb=panel_user.traffic_limit_bytes / (1024**3) if panel_user.traffic_limit_bytes else 0,
                traffic_used_gb=panel_user.used_traffic_bytes / (1024**3) if panel_user.used_traffic_bytes else 0,
                device_limit=panel_user.hwid_device_limit or 1,
                subscription_url=panel_user.subscription_url,
                active_squads=active_squads,
            )

            # Update remnawave_uuid if different
            # In multi-tariff mode the UUID belongs to the subscription, not the user
            if not settings.is_multi_tariff_enabled() and user.remnawave_uuid != panel_user.uuid:
                changes['remnawave_uuid'] = {'old': user.remnawave_uuid, 'new': panel_user.uuid}
                user.remnawave_uuid = panel_user.uuid

            # Update subscription if requested
            # Use explicitly selected subscription or fall back to first-active
            sync_sub = selected_sub or next((s for s in from_subs if s.is_active), from_subs[0] if from_subs else None)
            if request.update_subscription and sync_sub:
                sub = sync_sub

                # Update end date (normalize timezone)
                if panel_user.expire_at:
                    panel_expire_utc = panel_datetime_to_utc(panel_user.expire_at)

                    sub_end_utc = sub.end_date
                    if sub_end_utc is not None and sub_end_utc.tzinfo is None:
                        sub_end_utc = sub_end_utc.replace(tzinfo=UTC)
                    if sub_end_utc != panel_expire_utc:
                        # Предупреждаем если локальная дата новее панельной
                        # (например, автопокупка уже продлила подписку)
                        if sub_end_utc and panel_expire_utc and sub_end_utc > panel_expire_utc:
                            logger.warning(
                                'Sync: локальная end_date новее панельной, перезаписываем. '
                                'Возможно автопокупка уже продлила подписку.',
                                user_id=user_id,
                                local_end_date=sub_end_utc.isoformat(),
                                panel_expire_at=panel_expire_utc.isoformat(),
                            )
                            errors.append(
                                f'Warning: local end_date ({sub_end_utc.isoformat()}) is newer than '
                                f'panel expire_at ({panel_expire_utc.isoformat()}). '
                                f'Panel value applied — check if auto-purchase extended subscription.'
                            )
                        changes['end_date'] = {
                            'old': sub.end_date.isoformat() if sub.end_date else None,
                            'new': panel_expire_utc.isoformat(),
                        }
                        sub.end_date = panel_expire_utc

                # Update status
                panel_status_str = panel_user.status.value if panel_user.status else 'DISABLED'
                now = datetime.now(UTC)
                # Compare with normalized panel expire date
                panel_expire_for_check = panel_expire_utc if panel_user.expire_at else None
                if panel_status_str == 'ACTIVE' and panel_expire_for_check and panel_expire_for_check > now:
                    new_status = SubscriptionStatus.ACTIVE.value
                elif panel_expire_for_check and panel_expire_for_check <= now:
                    new_status = SubscriptionStatus.EXPIRED.value
                else:
                    new_status = SubscriptionStatus.DISABLED.value

                if sub.status != new_status:
                    changes['status'] = {'old': sub.status, 'new': new_status}
                    sub.status = new_status

                # Update traffic limit
                panel_traffic_limit = (
                    int(panel_user.traffic_limit_bytes / (1024**3)) if panel_user.traffic_limit_bytes else 0
                )
                if sub.traffic_limit_gb != panel_traffic_limit:
                    changes['traffic_limit_gb'] = {'old': sub.traffic_limit_gb, 'new': panel_traffic_limit}
                    sub.traffic_limit_gb = panel_traffic_limit

                # Update device limit
                panel_device_limit = panel_user.hwid_device_limit or 1
                if sub.device_limit != panel_device_limit:
                    changes['device_limit'] = {'old': sub.device_limit, 'new': panel_device_limit}
                    sub.device_limit = panel_device_limit

                # Update connected squads
                if active_squads and sub.connected_squads != active_squads:
                    changes['connected_squads'] = {'old': sub.connected_squads, 'new': active_squads}
                    sub.connected_squads = active_squads

                # Update subscription URL
                if panel_user.subscription_url and sub.subscription_url != panel_user.subscription_url:
                    changes['subscription_url'] = {'old': sub.subscription_url, 'new': panel_user.subscription_url}
                    sub.subscription_url = panel_user.subscription_url

                # Update short UUID
                if panel_user.short_uuid and sub.remnawave_short_uuid != panel_user.short_uuid:
                    changes['remnawave_short_uuid'] = {'old': sub.remnawave_short_uuid, 'new': panel_user.short_uuid}
                    sub.remnawave_short_uuid = panel_user.short_uuid

                # Update crypto link
                if panel_user.happ_crypto_link and sub.subscription_crypto_link != panel_user.happ_crypto_link:
                    changes['subscription_crypto_link'] = {'old': sub.subscription_crypto_link, 'new': '***'}
                    sub.subscription_crypto_link = panel_user.happ_crypto_link

            # Update traffic usage if requested
            if request.update_traffic and sync_sub:
                panel_traffic_used = panel_user.used_traffic_bytes / (1024**3) if panel_user.used_traffic_bytes else 0
                if abs((sync_sub.traffic_used_gb or 0) - panel_traffic_used) > 0.01:
                    changes['traffic_used_gb'] = {'old': sync_sub.traffic_used_gb, 'new': panel_traffic_used}
                    sync_sub.traffic_used_gb = panel_traffic_used

            # Create subscription if missing but user exists in panel
            if request.create_if_missing and not sync_sub and panel_user.expire_at:
                from app.database.crud.subscription import create_paid_subscription

                panel_traffic_limit = (
                    int(panel_user.traffic_limit_bytes / (1024**3)) if panel_user.traffic_limit_bytes else 100
                )
                panel_expire_utc = panel_datetime_to_utc(panel_user.expire_at)
                days_remaining = max(1, math.ceil((panel_expire_utc - datetime.now(UTC)).total_seconds() / 86400))

                new_sub = await create_paid_subscription(
                    db=db,
                    user_id=user.id,
                    duration_days=days_remaining,
                    traffic_limit_gb=panel_traffic_limit,
                    device_limit=panel_user.hwid_device_limit or 1,
                    connected_squads=active_squads,
                )
                new_sub.remnawave_short_uuid = panel_user.short_uuid
                new_sub.subscription_url = panel_user.subscription_url
                changes['subscription_created'] = True

            # Update last sync time
            user.last_remnawave_sync = datetime.now(UTC)
            user.updated_at = datetime.now(UTC)

            await db.commit()

        logger.info(
            'Admin synced user from panel. Changes', admin_id=admin.id, user_id=user_id, value=list(changes.keys())
        )

        return SyncFromPanelResponse(
            success=True,
            message=f'Synced {len(changes)} changes from panel' if changes else 'No changes needed',
            panel_user=panel_info,
            changes=changes,
            errors=errors,
        )

    except HTTPException:
        raise
    except Exception as e:
        logger.error('Error syncing user from panel', user_id=user_id, error=e)
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail=f'Sync error: {e!s}',
        )


@router.post('/{user_id}/sync/to-panel', response_model=SyncToPanelResponse)
async def sync_user_to_panel(
    user_id: int,
    subscription_id: int | None = Query(None, description='Subscription ID for multi-tariff sync'),
    request: SyncToPanelRequest = SyncToPanelRequest(),
    admin: User = Depends(require_permission('users:sync')),
    db: AsyncSession = Depends(get_cabinet_db),
):
    """
    Sync user data FROM bot TO panel.

    Sends user/subscription data to Remnawave panel, creating or updating as needed.
    When subscription_id is provided, syncs that specific subscription instead of first-active.
    """
    user = await get_user_by_id(db, user_id)
    if not user:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail='User not found',
        )

    push_subs = getattr(user, 'subscriptions', None) or []
    if subscription_id:
        push_sub = next((s for s in push_subs if s.id == subscription_id), None)
        if not push_sub:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail='Subscription not found',
            )
    else:
        push_sub = next((s for s in push_subs if s.is_active), push_subs[0] if push_subs else None)
    if not push_sub:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail='User has no subscription to sync',
        )

    try:
        from app.config import settings
        from app.external.remnawave_api import UserStatus as PanelUserStatus
        from app.services.remnawave_service import RemnaWaveService
        from app.services.subscription_service import get_traffic_reset_strategy
        from app.utils.subscription_utils import resolve_hwid_device_limit_for_payload

        service = RemnaWaveService()
        if not service.is_configured:
            raise HTTPException(
                status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
                detail=service.configuration_error or 'Remnawave API not configured',
            )

        sub = push_sub
        changes = {}
        errors = []
        action = 'no_changes'
        panel_uuid = (
            sub.remnawave_uuid if settings.is_multi_tariff_enabled() and sub.remnawave_uuid else user.remnawave_uuid
        )

        # Prepare data for panel
        is_active = (
            sub.status in (SubscriptionStatus.ACTIVE.value, SubscriptionStatus.TRIAL.value)
            and sub.end_date
            and sub.end_date > datetime.now(UTC)
        )
        panel_status = PanelUserStatus.ACTIVE if is_active else PanelUserStatus.DISABLED

        # Ensure expire_at is in future for panel
        expire_at = sub.end_date
        if expire_at and expire_at <= datetime.now(UTC):
            expire_at = datetime.now(UTC) + timedelta(minutes=1)

        username = settings.format_remnawave_username(
            full_name=user.full_name,
            username=user.username,
            telegram_id=user.telegram_id,
            email=user.email,
            user_id=user.id,
        )

        description = settings.format_remnawave_user_description(
            full_name=user.full_name,
            username=user.username,
            telegram_id=user.telegram_id,
            email=user.email,
            user_id=user.id,
        )

        hwid_limit = resolve_hwid_device_limit_for_payload(sub)
        traffic_limit_bytes = sub.traffic_limit_gb * (1024**3) if sub.traffic_limit_gb > 0 else 0

        # Загружаем tariff для внешнего сквада
        try:
            await db.refresh(sub, ['tariff'])
        except Exception:
            pass
        ext_squad_uuid = sub.tariff.external_squad_uuid if sub.tariff else None

        async with service.get_api_client() as api:
            # Validate existing UUID
            if panel_uuid:
                existing_user = await api.get_user_by_uuid(panel_uuid)
                if not existing_user:
                    logger.warning('Stale remnawave_uuid, clearing', user_id=user.id, panel_uuid=panel_uuid)
                    panel_uuid = None
                    if settings.is_multi_tariff_enabled():
                        sub.remnawave_uuid = None
                    else:
                        user.remnawave_uuid = None

            # Fallback: search by telegram_id (single-tariff only)
            if not panel_uuid and not settings.is_multi_tariff_enabled() and user.telegram_id:
                existing_users = await api.get_user_by_telegram_id(user.telegram_id)
                if existing_users:
                    panel_uuid = existing_users[0].uuid
                    user.remnawave_uuid = panel_uuid
                    changes['remnawave_uuid_discovered'] = panel_uuid

            # Fallback: search by email (single-tariff, OAuth users)
            if not panel_uuid and not settings.is_multi_tariff_enabled() and user.email:
                existing_users = await api.get_user_by_email(user.email)
                if existing_users:
                    panel_uuid = existing_users[0].uuid
                    user.remnawave_uuid = panel_uuid
                    changes['remnawave_uuid_discovered'] = panel_uuid

            if panel_uuid:
                # Update existing user
                update_kwargs = {'uuid': panel_uuid}

                if request.update_status:
                    update_kwargs['status'] = panel_status
                    changes['status'] = panel_status.value

                if request.update_expire_date and expire_at:
                    update_kwargs['expire_at'] = expire_at
                    changes['expire_at'] = expire_at.isoformat()

                if request.update_traffic_limit:
                    update_kwargs['traffic_limit_bytes'] = traffic_limit_bytes
                    update_kwargs['traffic_limit_strategy'] = get_traffic_reset_strategy(sub.tariff)
                    changes['traffic_limit_gb'] = sub.traffic_limit_gb

                if request.update_squads and sub.connected_squads:
                    update_kwargs['active_internal_squads'] = sub.connected_squads
                    changes['connected_squads'] = sub.connected_squads

                update_kwargs['description'] = description
                if hwid_limit is not None:
                    update_kwargs['hwid_device_limit'] = hwid_limit
                    changes['device_limit'] = hwid_limit

                # Внешний сквад: синхронизируем из тарифа (если задан)
                # Не отправляем null — RemnaWave API не принимает null для externalSquadUuid (A039)
                if ext_squad_uuid is not None:
                    update_kwargs['external_squad_uuid'] = ext_squad_uuid

                try:
                    await api.update_user(**update_kwargs)
                    action = 'updated'
                except Exception as update_error:
                    error_code = (getattr(update_error, 'response_data', None) or {}).get('errorCode', '')
                    if (
                        hasattr(update_error, 'status_code') and update_error.status_code == 404
                    ) or error_code == 'A018':
                        # User not found in panel, create new
                        panel_uuid = None
                    else:
                        raise

            if not panel_uuid and request.create_if_missing:
                # Create new user in panel
                create_kwargs = {
                    'username': username,
                    'expire_at': expire_at or (datetime.now(UTC) + timedelta(days=30)),
                    'status': panel_status,
                    'traffic_limit_bytes': traffic_limit_bytes,
                    'traffic_limit_strategy': get_traffic_reset_strategy(sub.tariff),
                    'telegram_id': user.telegram_id,
                    'email': user.email,
                    'description': description,
                    'active_internal_squads': sub.connected_squads or [],
                }

                if hwid_limit is not None:
                    create_kwargs['hwid_device_limit'] = hwid_limit
                if ext_squad_uuid is not None:
                    create_kwargs['external_squad_uuid'] = ext_squad_uuid

                # Multi-tariff: subscription-specific username
                if settings.is_multi_tariff_enabled() and getattr(sub, 'remnawave_short_id', None):
                    create_kwargs['username'] = f'{username}_{sub.remnawave_short_id}'

                new_panel_user = await api.create_user(**create_kwargs)
                panel_uuid = new_panel_user.uuid
                sub.remnawave_uuid = new_panel_user.uuid
                sub.remnawave_short_uuid = new_panel_user.short_uuid
                sub.subscription_url = new_panel_user.subscription_url
                if not settings.is_multi_tariff_enabled():
                    user.remnawave_uuid = new_panel_user.uuid

                changes['created_in_panel'] = True
                changes['panel_uuid'] = panel_uuid
                changes['short_uuid'] = new_panel_user.short_uuid
                action = 'created'

            # Update last sync time
            user.last_remnawave_sync = datetime.now(UTC)
            user.updated_at = datetime.now(UTC)

            await db.commit()

        logger.info('Admin synced user to panel. Action', admin_id=admin.id, user_id=user_id, action=action)

        return SyncToPanelResponse(
            success=True,
            message=f'User {action} in panel' if action != 'no_changes' else 'No changes needed',
            action=action,
            panel_uuid=panel_uuid,
            changes=changes,
            errors=errors,
        )

    except HTTPException:
        raise
    except Exception as e:
        logger.error('Error syncing user to panel', user_id=user_id, error=e)
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail=f'Sync error: {e!s}',
        )


# === User Gifts ===


@router.get('/{user_id}/gifts', response_model=AdminUserGiftsResponse)
async def get_user_gifts(
    user_id: int,
    admin: User = Depends(require_permission('users:read')),
    db: AsyncSession = Depends(get_cabinet_db),
) -> AdminUserGiftsResponse:
    """Get all gift subscriptions sent and received by user."""
    from sqlalchemy.orm import noload

    # Lightweight existence check (avoids eager-loading all User relationships)
    user_exists = await db.execute(select(User.id).where(User.id == user_id))
    if not user_exists.scalar_one_or_none():
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='User not found')

    # True totals via COUNT queries
    sent_total = (
        await db.execute(
            select(func.count(GuestPurchase.id)).where(
                GuestPurchase.buyer_user_id == user_id,
                GuestPurchase.is_gift.is_(True),
            )
        )
    ).scalar() or 0

    received_total = (
        await db.execute(
            select(func.count(GuestPurchase.id)).where(
                GuestPurchase.user_id == user_id,
                GuestPurchase.is_gift.is_(True),
            )
        )
    ).scalar() or 0

    # Sent gifts (user is buyer) — suppress unneeded relationships
    sent_result = await db.execute(
        select(GuestPurchase)
        .options(
            selectinload(GuestPurchase.tariff),
            selectinload(GuestPurchase.user),
            noload(GuestPurchase.buyer),
            noload(GuestPurchase.landing),
        )
        .where(
            GuestPurchase.buyer_user_id == user_id,
            GuestPurchase.is_gift.is_(True),
        )
        .order_by(GuestPurchase.created_at.desc())
        .limit(200)
    )
    sent_purchases = sent_result.scalars().all()

    # Received gifts (user is recipient) — suppress unneeded relationships
    received_result = await db.execute(
        select(GuestPurchase)
        .options(
            selectinload(GuestPurchase.tariff),
            selectinload(GuestPurchase.buyer),
            noload(GuestPurchase.user),
            noload(GuestPurchase.landing),
        )
        .where(
            GuestPurchase.user_id == user_id,
            GuestPurchase.is_gift.is_(True),
        )
        .order_by(GuestPurchase.created_at.desc())
        .limit(200)
    )
    received_purchases = received_result.scalars().all()

    sent_items = [_build_gift_item(p, receiver=p.user) for p in sent_purchases]
    received_items = [_build_gift_item(p, buyer=p.buyer) for p in received_purchases]

    return AdminUserGiftsResponse(
        sent=sent_items,
        received=received_items,
        sent_total=sent_total,
        received_total=received_total,
    )


def _build_gift_item(
    p: GuestPurchase,
    receiver: User | None = None,
    buyer: User | None = None,
) -> AdminUserGiftItem:
    """Build an admin gift item from a GuestPurchase."""
    tariff_name = p.tariff.name if p.tariff else None
    device_limit = p.tariff.device_limit if p.tariff else 1
    return AdminUserGiftItem(
        id=p.id,
        token=p.token[:12],
        status=p.status,
        tariff_name=tariff_name,
        period_days=p.period_days,
        device_limit=device_limit,
        amount_kopeks=p.amount_kopeks,
        payment_method=p.payment_method,
        gift_recipient_type=p.gift_recipient_type,
        gift_recipient_value=p.gift_recipient_value,
        gift_message=p.gift_message,
        buyer_user_id=p.buyer_user_id,
        buyer_username=buyer.username if buyer else None,
        buyer_full_name=buyer.full_name if buyer else None,
        receiver_user_id=p.user_id,
        receiver_username=receiver.username if receiver else None,
        receiver_full_name=receiver.full_name if receiver else None,
        created_at=p.created_at,
        paid_at=p.paid_at,
        delivered_at=p.delivered_at,
    )
