"""Маршруты управления серверами в административном API."""

from __future__ import annotations

from collections.abc import Iterable
from typing import TYPE_CHECKING, Any

from fastapi import APIRouter, Depends, HTTPException, Query, Security, status
from sqlalchemy import func, or_, select
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload

from app.database.crud.server_squad import (
    create_server_squad,
    delete_server_squad,
    get_server_connected_users,
    get_server_squad_by_id,
    get_server_squad_by_uuid,
    get_server_statistics,
    sync_server_user_counts,
    sync_with_remnawave,
    update_server_squad,
    update_server_squad_promo_groups,
)
from app.database.models import PromoGroup, ServerSquad, User
from app.utils.cache import cache

from ..dependencies import get_db_session, require_api_token
from ..schemas.servers import (
    ServerConnectedUser,
    ServerConnectedUsersResponse,
    ServerCountsSyncResponse,
    ServerCreateRequest,
    ServerDeleteResponse,
    ServerListResponse,
    ServerResponse,
    ServerStatisticsResponse,
    ServerSyncResponse,
    ServerUpdateRequest,
)
from ..schemas.users import PromoGroupSummary


try:  # pragma: no cover - импорт может провалиться без optional-зависимостей
    from app.services.remnawave_service import RemnaWaveService  # type: ignore
except Exception:  # pragma: no cover - скрываем функционал, если сервис недоступен
    RemnaWaveService = None  # type: ignore[assignment]


if TYPE_CHECKING:  # pragma: no cover - только для подсказок типов в IDE
    from app.services.remnawave_service import (  # type: ignore
        RemnaWaveService as RemnaWaveServiceType,
    )
else:
    RemnaWaveServiceType = Any


router = APIRouter()


def _serialize_promo_group(group: PromoGroup) -> PromoGroupSummary:
    return PromoGroupSummary(
        id=group.id,
        name=group.name,
        server_discount_percent=group.server_discount_percent,
        traffic_discount_percent=group.traffic_discount_percent,
        device_discount_percent=group.device_discount_percent,
        apply_discounts_to_addons=getattr(group, 'apply_discounts_to_addons', True),
    )


def _serialize_server(server: ServerSquad) -> ServerResponse:
    promo_groups = [
        _serialize_promo_group(group)
        for group in sorted(
            getattr(server, 'allowed_promo_groups', []) or [],
            key=lambda pg: pg.name.lower() if getattr(pg, 'name', None) else '',
        )
    ]

    return ServerResponse(
        id=server.id,
        squad_uuid=server.squad_uuid,
        display_name=server.display_name,
        original_name=server.original_name,
        country_code=server.country_code,
        is_available=bool(server.is_available),
        is_trial_eligible=bool(server.is_trial_eligible),
        price_kopeks=int(server.price_kopeks or 0),
        price_rubles=round((server.price_kopeks or 0) / 100, 2),
        description=server.description,
        sort_order=int(server.sort_order or 0),
        max_users=server.max_users,
        current_users=int(server.current_users or 0),
        created_at=getattr(server, 'created_at', None),
        updated_at=getattr(server, 'updated_at', None),
        promo_groups=promo_groups,
    )


def _serialize_connected_user(user: User) -> ServerConnectedUser:
    subscription = getattr(user, 'subscription', None)
    subscription_status = getattr(subscription, 'status', None)
    if hasattr(subscription_status, 'value'):
        subscription_status = subscription_status.value

    return ServerConnectedUser(
        id=user.id,
        telegram_id=user.telegram_id,
        username=user.username,
        first_name=user.first_name,
        last_name=user.last_name,
        status=getattr(getattr(user, 'status', None), 'value', user.status),
        balance_kopeks=int(user.balance_kopeks or 0),
        balance_rubles=round((user.balance_kopeks or 0) / 100, 2),
        subscription_id=getattr(subscription, 'id', None),
        subscription_status=subscription_status,
        subscription_end_date=getattr(subscription, 'end_date', None),
    )


def _apply_filters(
    filters: Iterable[Any],
    query,
):
    for condition in filters:
        query = query.where(condition)
    return query


def _get_remnawave_service() -> RemnaWaveServiceType:
    if RemnaWaveService is None:  # pragma: no cover - зависимость не доступна
        raise HTTPException(
            status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
            detail='RemnaWave сервис недоступен',
        )

    return RemnaWaveService()


def _ensure_service_configured(service: RemnaWaveServiceType) -> None:
    if RemnaWaveService is None:  # pragma: no cover - зависимость не доступна
        raise HTTPException(
            status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
            detail='RemnaWave сервис недоступен',
        )

    if not service.is_configured:
        raise HTTPException(
            status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
            detail=service.configuration_error or 'RemnaWave API не настроен',
        )


async def _validate_promo_group_ids(db: AsyncSession, promo_group_ids: Iterable[int]) -> list[int]:
    unique_ids = [int(pg_id) for pg_id in set(promo_group_ids)]

    if not unique_ids:
        raise HTTPException(
            status.HTTP_400_BAD_REQUEST,
            'Нужно выбрать хотя бы одну промогруппу',
        )

    result = await db.execute(select(PromoGroup.id).where(PromoGroup.id.in_(unique_ids)))
    found_ids = result.scalars().all()

    if not found_ids:
        raise HTTPException(
            status.HTTP_400_BAD_REQUEST,
            'Не найдены промогруппы для обновления сервера',
        )

    return unique_ids


@router.get('', response_model=ServerListResponse)
async def list_servers(
    _: Any = Security(require_api_token),
    db: AsyncSession = Depends(get_db_session),
    page: int = Query(1, ge=1),
    limit: int = Query(50, ge=1, le=200),
    available_only: bool = Query(False, alias='available'),
    search: str | None = Query(default=None),
) -> ServerListResponse:
    filters = []

    if available_only:
        filters.append(ServerSquad.is_available.is_(True))

    if search:
        pattern = f'%{search.lower()}%'
        filters.append(
            or_(
                func.lower(ServerSquad.display_name).like(pattern),
                func.lower(ServerSquad.original_name).like(pattern),
                func.lower(ServerSquad.squad_uuid).like(pattern),
                func.lower(ServerSquad.country_code).like(pattern),
            )
        )

    base_query = (
        select(ServerSquad)
        .options(selectinload(ServerSquad.allowed_promo_groups))
        .order_by(ServerSquad.sort_order, ServerSquad.display_name)
    )

    count_query = select(func.count(ServerSquad.id))

    if filters:
        base_query = _apply_filters(filters, base_query)
        count_query = _apply_filters(filters, count_query)

    total = await db.scalar(count_query) or 0

    result = await db.execute(base_query.offset((page - 1) * limit).limit(limit))
    servers = result.scalars().unique().all()

    return ServerListResponse(
        items=[_serialize_server(server) for server in servers],
        total=int(total),
        page=page,
        limit=limit,
    )


@router.get('/stats', response_model=ServerStatisticsResponse)
async def get_servers_statistics(
    _: Any = Security(require_api_token),
    db: AsyncSession = Depends(get_db_session),
) -> ServerStatisticsResponse:
    stats = await get_server_statistics(db)

    return ServerStatisticsResponse(
        total_servers=int(stats.get('total_servers', 0) or 0),
        available_servers=int(stats.get('available_servers', 0) or 0),
        unavailable_servers=int(stats.get('unavailable_servers', 0) or 0),
        servers_with_connections=int(stats.get('servers_with_connections', 0) or 0),
        total_revenue_kopeks=int(stats.get('total_revenue_kopeks', 0) or 0),
        total_revenue_rubles=float(stats.get('total_revenue_rubles', 0) or 0),
    )


@router.post('', response_model=ServerResponse, status_code=status.HTTP_201_CREATED)
async def create_server_endpoint(
    payload: ServerCreateRequest,
    _: Any = Security(require_api_token),
    db: AsyncSession = Depends(get_db_session),
) -> ServerResponse:
    existing = await get_server_squad_by_uuid(db, payload.squad_uuid)
    if existing:
        raise HTTPException(
            status.HTTP_400_BAD_REQUEST,
            'Server with this UUID already exists',
        )

    try:
        server = await create_server_squad(
            db,
            squad_uuid=payload.squad_uuid,
            display_name=payload.display_name,
            original_name=payload.original_name,
            country_code=payload.country_code,
            price_kopeks=payload.price_kopeks,
            description=payload.description,
            max_users=payload.max_users,
            is_available=payload.is_available,
            is_trial_eligible=payload.is_trial_eligible,
            sort_order=payload.sort_order,
            promo_group_ids=payload.promo_group_ids,
        )
    except ValueError as error:
        raise HTTPException(status.HTTP_400_BAD_REQUEST, str(error)) from error

    await cache.delete_pattern('available_countries*')

    server = await get_server_squad_by_id(db, server.id)
    assert server is not None
    return _serialize_server(server)


@router.get('/{server_id}', response_model=ServerResponse)
async def get_server_endpoint(
    server_id: int,
    _: Any = Security(require_api_token),
    db: AsyncSession = Depends(get_db_session),
) -> ServerResponse:
    server = await get_server_squad_by_id(db, server_id)
    if not server:
        raise HTTPException(status.HTTP_404_NOT_FOUND, 'Server not found')

    return _serialize_server(server)


@router.patch('/{server_id}', response_model=ServerResponse)
async def update_server_endpoint(
    server_id: int,
    payload: ServerUpdateRequest,
    _: Any = Security(require_api_token),
    db: AsyncSession = Depends(get_db_session),
) -> ServerResponse:
    server = await get_server_squad_by_id(db, server_id)
    if not server:
        raise HTTPException(status.HTTP_404_NOT_FOUND, 'Server not found')

    updates = payload.model_dump(exclude_unset=True, by_alias=False)
    promo_group_ids = updates.pop('promo_group_ids', None)

    validated_promo_group_ids: list[int] | None = None
    if promo_group_ids is not None:
        validated_promo_group_ids = await _validate_promo_group_ids(db, promo_group_ids)

    if updates:
        server = await update_server_squad(db, server_id, **updates) or server

    if promo_group_ids is not None:
        try:
            assert validated_promo_group_ids is not None
            server = await update_server_squad_promo_groups(db, server_id, validated_promo_group_ids) or server
        except ValueError as error:
            raise HTTPException(status.HTTP_400_BAD_REQUEST, str(error)) from error

    await cache.delete_pattern('available_countries*')

    server = await get_server_squad_by_id(db, server_id)
    assert server is not None
    return _serialize_server(server)


@router.delete('/{server_id}', response_model=ServerDeleteResponse)
async def delete_server_endpoint(
    server_id: int,
    _: Any = Security(require_api_token),
    db: AsyncSession = Depends(get_db_session),
) -> ServerDeleteResponse:
    server = await get_server_squad_by_id(db, server_id)
    if not server:
        raise HTTPException(status.HTTP_404_NOT_FOUND, 'Server not found')

    deleted = await delete_server_squad(db, server_id)
    if not deleted:
        raise HTTPException(
            status.HTTP_400_BAD_REQUEST,
            'Server cannot be deleted because it has active connections',
        )

    await cache.delete_pattern('available_countries*')

    return ServerDeleteResponse(success=True, message='Server deleted')


@router.get(
    '/{server_id}/users',
    response_model=ServerConnectedUsersResponse,
)
async def get_server_connected_users_endpoint(
    server_id: int,
    _: Any = Security(require_api_token),
    db: AsyncSession = Depends(get_db_session),
    limit: int = Query(100, ge=1, le=500),
    offset: int = Query(0, ge=0),
) -> ServerConnectedUsersResponse:
    server = await get_server_squad_by_id(db, server_id)
    if not server:
        raise HTTPException(status.HTTP_404_NOT_FOUND, 'Server not found')

    users = await get_server_connected_users(db, server_id)
    total = len(users)
    sliced = users[offset : offset + limit]

    return ServerConnectedUsersResponse(
        items=[_serialize_connected_user(user) for user in sliced],
        total=total,
        limit=limit,
        offset=offset,
    )


@router.post('/sync', response_model=ServerSyncResponse)
async def sync_servers_with_remnawave(
    _: Any = Security(require_api_token),
    db: AsyncSession = Depends(get_db_session),
) -> ServerSyncResponse:
    service = _get_remnawave_service()
    _ensure_service_configured(service)

    squads = await service.get_all_squads()
    total = len(squads)

    created = updated = removed = 0
    if squads:
        created, updated, removed = await sync_with_remnawave(db, squads)

    await cache.delete_pattern('available_countries*')

    return ServerSyncResponse(
        created=created,
        updated=updated,
        removed=removed,
        total=total,
    )


@router.post('/sync-counts', response_model=ServerCountsSyncResponse)
async def sync_server_counts(
    _: Any = Security(require_api_token),
    db: AsyncSession = Depends(get_db_session),
) -> ServerCountsSyncResponse:
    updated = await sync_server_user_counts(db)
    return ServerCountsSyncResponse(updated=updated)
