"""
API views для управления профилями.
"""
from rest_framework import viewsets, status
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated, AllowAny
from django.db import models
from django.utils import timezone
from django.core.cache import cache
from datetime import datetime
from zoneinfo import available_timezones, ZoneInfo
from .models import User, Client, Parent, MentorStudentConnection
from apps.notifications.models import Notification
from apps.notifications.services import NotificationService
from .serializers import UserSerializer, ClientSerializer, ParentSerializer
from .geo_utils import get_countries, search_cities
from .utils import normalize_phone
from .tasks import send_student_welcome_email_task, send_mentor_invitation_email_task
from django.conf import settings
import secrets
import csv
import io
import requests
from django.core.cache import cache


POPULAR_CITIES = [
    # Россия
    {"country_code": "RU", "country_name": "Россия", "city": "Москва", "timezone": "Europe/Moscow"},
    {"country_code": "RU", "country_name": "Россия", "city": "Санкт-Петербург", "timezone": "Europe/Moscow"},
    {"country_code": "RU", "country_name": "Россия", "city": "Новосибирск", "timezone": "Asia/Novosibirsk"},
    {"country_code": "RU", "country_name": "Россия", "city": "Екатеринбург", "timezone": "Asia/Yekaterinburg"},
    {"country_code": "RU", "country_name": "Россия", "city": "Казань", "timezone": "Europe/Moscow"},
    {"country_code": "RU", "country_name": "Россия", "city": "Нижний Новгород", "timezone": "Europe/Moscow"},
    {"country_code": "RU", "country_name": "Россия", "city": "Самара", "timezone": "Europe/Samara"},
    {"country_code": "RU", "country_name": "Россия", "city": "Красноярск", "timezone": "Asia/Krasnoyarsk"},
    {"country_code": "RU", "country_name": "Россия", "city": "Владивосток", "timezone": "Asia/Vladivostok"},
    {"country_code": "RU", "country_name": "Россия", "city": "Улан-Удэ", "timezone": "Asia/Irkutsk"},
    {"country_code": "RU", "country_name": "Россия", "city": "Иркутск", "timezone": "Asia/Irkutsk"},
    {"country_code": "RU", "country_name": "Россия", "city": "Чита", "timezone": "Asia/Chita"},
    {"country_code": "RU", "country_name": "Россия", "city": "Хабаровск", "timezone": "Asia/Vladivostok"},
    {"country_code": "RU", "country_name": "Россия", "city": "Омск", "timezone": "Asia/Omsk"},
    {"country_code": "RU", "country_name": "Россия", "city": "Челябинск", "timezone": "Asia/Yekaterinburg"},
    {"country_code": "RU", "country_name": "Россия", "city": "Уфа", "timezone": "Asia/Yekaterinburg"},
    {"country_code": "RU", "country_name": "Россия", "city": "Ростов-на-Дону", "timezone": "Europe/Moscow"},
    {"country_code": "RU", "country_name": "Россия", "city": "Пермь", "timezone": "Asia/Yekaterinburg"},
    {"country_code": "RU", "country_name": "Россия", "city": "Воронеж", "timezone": "Europe/Moscow"},
    {"country_code": "RU", "country_name": "Россия", "city": "Волгоград", "timezone": "Europe/Moscow"},
    {"country_code": "RU", "country_name": "Россия", "city": "Краснодар", "timezone": "Europe/Moscow"},
    {"country_code": "RU", "country_name": "Россия", "city": "Барнаул", "timezone": "Asia/Barnaul"},
    {"country_code": "RU", "country_name": "Россия", "city": "Томск", "timezone": "Asia/Tomsk"},
    {"country_code": "RU", "country_name": "Россия", "city": "Якутск", "timezone": "Asia/Yakutsk"},
    {"country_code": "RU", "country_name": "Россия", "city": "Калининград", "timezone": "Europe/Kaliningrad"},
    # Казахстан
    {"country_code": "KZ", "country_name": "Казахстан", "city": "Алматы", "timezone": "Asia/Almaty"},
    {"country_code": "KZ", "country_name": "Казахстан", "city": "Астана", "timezone": "Asia/Almaty"},
    # Беларусь
    {"country_code": "BY", "country_name": "Беларусь", "city": "Минск", "timezone": "Europe/Minsk"},
    # Украина
    {"country_code": "UA", "country_name": "Украина", "city": "Киев", "timezone": "Europe/Kyiv"},
]


class ProfileViewSet(viewsets.ViewSet):
    """
    ViewSet для управления профилем.
    
    me: Текущий пользователь
    update_profile: Обновить профиль
    change_password: Сменить пароль
    settings: Настройки профиля
    update_settings: Обновить настройки
    """
    
    permission_classes = [IsAuthenticated]
    
    @action(detail=False, methods=['get'])
    def me(self, request):
        """
        Получить данные текущего пользователя.
        
        GET /api/users/profile/me/
        """
        user = request.user
        # User.save() автоматически создаст universal_code, если он отсутствует
        if not user.universal_code or len(str(user.universal_code).strip()) != 8:
            user.save(update_fields=['universal_code'])
        
        serializer = UserSerializer(user, context={'request': request})
        
        # Добавляем дополнительную информацию
        data = serializer.data
        
        # Оптимизация: используем select_related для избежания дополнительных запросов
        # Если клиент - добавляем данные клиента
        if user.role == 'client':
            # Используем select_related для оптимизации
            client = Client.objects.select_related('user').filter(user=user).first()
            if client:
                data['client_info'] = ClientSerializer(client, context={'request': request}).data
        
        # Если родитель - добавляем данные родителя
        elif user.role == 'parent':
            # Используем select_related и prefetch_related для оптимизации
            # Важно: prefetch_related для children__mentors, чтобы менторы загружались
            parent = Parent.objects.select_related('user').prefetch_related(
                'children', 'children__user', 'children__mentors'
            ).filter(user=user).first()
            if parent:
                data['parent_info'] = ParentSerializer(parent, context={'request': request}).data
        
        return Response(data)
    
    @action(detail=False, methods=['post'])
    def load_telegram_avatar(self, request):
        """
        Загрузить аватар из Telegram.
        
        POST /api/users/profile/load_telegram_avatar/
        
        Примечание: Telegram API имеет ограничения на частоту запросов (rate limiting).
        Если вы получили ошибку "Flood control exceeded", подождите указанное время перед повторной попыткой.
        """
        import requests
        import os
        from django.core.files.base import ContentFile
        from django.conf import settings
        from django.core.cache import cache
        
        user = request.user
        
        # Проверяем, что у пользователя есть telegram_id
        if not user.telegram_id:
            return Response(
                {'error': 'Telegram не подключен'},
                status=status.HTTP_400_BAD_REQUEST
            )
        
        # Проверяем кеш - если недавно уже пытались загрузить, не делаем повторный запрос
        cache_key = f'telegram_avatar_load_{user.telegram_id}'
        last_attempt = cache.get(cache_key)
        if last_attempt:
            # Если прошло меньше 60 секунд с последней попытки, возвращаем ошибку
            from django.utils import timezone
            from datetime import timedelta
            time_passed = (timezone.now() - last_attempt).total_seconds()
            wait_time = 60 - int(time_passed)
            
            if wait_time > 0:
                minutes = wait_time // 60
                seconds = wait_time % 60
                if minutes > 0:
                    time_str = f'{minutes} {minutes == 1 and "минуту" or "минут"}'
                    if seconds > 0:
                        time_str += f' {seconds} {seconds == 1 and "секунду" or "секунд"}'
                else:
                    time_str = f'{seconds} {seconds == 1 and "секунду" or "секунд"}'
                
                return Response(
                    {
                        'error': f'Пожалуйста, подождите {time_str} перед повторной попыткой загрузки аватара из Telegram',
                        'retry_after': wait_time,
                        'last_attempt': last_attempt.isoformat() if hasattr(last_attempt, 'isoformat') else str(last_attempt)
                    },
                    status=status.HTTP_429_TOO_MANY_REQUESTS
                )
        
        # Сохраняем время попытки в кеш на 60 секунд
        from django.utils import timezone
        cache.set(cache_key, timezone.now(), 60)
        
        try:
            import asyncio
            from telegram import Bot
            from telegram.error import RetryAfter, TelegramError
            from django.conf import settings as django_settings
            
            if not django_settings.TELEGRAM_BOT_TOKEN:
                return Response(
                    {'error': 'Telegram бот не настроен'},
                    status=status.HTTP_500_INTERNAL_SERVER_ERROR
                )
            
            # Асинхронная функция для получения фото
            async def get_telegram_photo():
                bot = Bot(token=django_settings.TELEGRAM_BOT_TOKEN)
                try:
                    import logging
                    logger = logging.getLogger(__name__)
                    
                    # Сначала проверяем, что пользователь существует и бот может получить информацию о нем
                    try:
                        chat_member = await bot.get_chat_member(user.telegram_id, user.telegram_id)
                        logger.info(f"User chat member info retrieved: {chat_member.status if hasattr(chat_member, 'status') else 'N/A'}")
                    except Exception as e:
                        logger.warning(f"Could not get chat member info: {str(e)}. User may not have started a conversation with the bot.")
                    
                    # Получаем фото профиля пользователя
                    logger.info(f"Attempting to get profile photos for user {user.telegram_id}")
                    photos = await bot.get_user_profile_photos(user.telegram_id, limit=1)
                    
                    # Получаем total_count и количество фото
                    total_count = getattr(photos, 'total_count', 0) if photos else 0
                    photos_list = getattr(photos, 'photos', []) if photos else []
                    photos_count = len(photos_list) if photos_list else 0
                    
                    logger.info(f"Profile photos response: total_count={total_count}, photos_list_length={photos_count}")
                    
                    # Если total_count > 0, но photos пустой - значит фото есть, но доступ ограничен
                    if total_count > 0 and photos_count == 0:
                        return None, None, 'Фото профиля есть, но доступ к нему ограничен настройками приватности Telegram. Пожалуйста, разрешите боту доступ к фото профиля в настройках приватности Telegram (Настройки → Конфиденциальность → Фото профиля → Все).'
                    
                    # Если total_count = 0 и photos пустой - фото нет
                    if total_count == 0 and photos_count == 0:
                        return None, None, 'У вас нет фото профиля в Telegram или оно недоступно. Установите фото профиля в Telegram и убедитесь, что в настройках приватности (Настройки → Конфиденциальность → Фото профиля) выбрано "Все".'
                    
                    # Проверяем, что список фото не пустой
                    if not photos_list or photos_count == 0:
                        return None, None, 'Не удалось получить фото профиля. Убедитесь, что:\n1. Вы начали диалог с ботом (@advent_dk_testing_bot)\n2. В настройках приватности Telegram (Настройки → Конфиденциальность → Фото профиля) выбрано "Все"\n3. Фото профиля установлено в Telegram'
                    
                    # Берем самое большое фото (последний элемент в массиве размеров)
                    photo_sizes = photos_list[0]
                    if not photo_sizes or len(photo_sizes) == 0:
                        logger.error("Photo sizes list is empty")
                        return None, None, 'Не удалось получить размеры фото профиля.'
                    
                    photo = photo_sizes[-1]  # Последний элемент - самое большое фото
                    
                    if not photo or not hasattr(photo, 'file_id'):
                        logger.error("Photo object is invalid or missing file_id")
                        return None, None, 'Не удалось получить идентификатор файла фото.'
                    
                    logger.info(f"Found photo with file_id: {photo.file_id}")
                    
                    # Получаем файл фото
                    file = await bot.get_file(photo.file_id)
                    logger.info(f"Got file path: {file.file_path}")
                    
                    if not file.file_path:
                        logger.error("File path is None or empty")
                        return None, None, 'Не удалось получить путь к файлу фото.'
                    
                    # Скачиваем фото используя метод download_file из библиотеки
                    try:
                        from io import BytesIO
                        photo_buffer = BytesIO()
                        await file.download_to_memory(out=photo_buffer)
                        photo_content_bytes = photo_buffer.getvalue()
                        
                        if not photo_content_bytes or len(photo_content_bytes) == 0:
                            logger.error("Downloaded photo is empty")
                            return None, None, 'Загруженное фото пустое'
                        
                        logger.info(f"Successfully downloaded photo: {len(photo_content_bytes)} bytes")
                        return photo_content_bytes, file.file_path, None
                    except Exception as download_error:
                        logger.error(f"Error downloading photo file: {str(download_error)}", exc_info=True)
                        # Fallback: попробуем через прямой запрос к API
                        try:
                            photo_url = f"https://api.telegram.org/file/bot{django_settings.TELEGRAM_BOT_TOKEN}/{file.file_path}"
                            photo_response = requests.get(photo_url, timeout=10)
                            
                            if photo_response.status_code != 200:
                                logger.error(f"Failed to download photo via direct API: status_code={photo_response.status_code}")
                                return None, None, f'Не удалось загрузить фото: HTTP {photo_response.status_code}. Убедитесь, что файл доступен.'
                            
                            if not photo_response.content or len(photo_response.content) == 0:
                                logger.error("Downloaded photo is empty (via direct API)")
                                return None, None, 'Загруженное фото пустое'
                            
                            logger.info(f"Successfully downloaded photo via direct API: {len(photo_response.content)} bytes")
                            return photo_response.content, file.file_path, None
                        except Exception as fallback_error:
                            logger.error(f"Fallback download also failed: {str(fallback_error)}", exc_info=True)
                            return None, None, f'Не удалось загрузить фото: {str(fallback_error)}'
                except RetryAfter as e:
                    # Обработка ошибки Flood control
                    retry_after = int(e.retry_after) if hasattr(e, 'retry_after') else None
                    return None, None, f'Слишком много запросов. Попробуйте через {retry_after} секунд' if retry_after else 'Слишком много запросов. Попробуйте позже'
                except TelegramError as e:
                    import logging
                    logger = logging.getLogger(__name__)
                    logger.error(f"Telegram API error: {str(e)}", exc_info=True)
                    error_msg = str(e)
                    # Более понятные сообщения об ошибках
                    if 'user not found' in error_msg.lower():
                        return None, None, 'Пользователь не найден в Telegram. Убедитесь, что вы связали аккаунт с ботом.'
                    elif 'forbidden' in error_msg.lower() or 'access denied' in error_msg.lower():
                        return None, None, 'Доступ запрещен. Убедитесь, что вы начали диалог с ботом и разрешили доступ к фото профиля в настройках приватности Telegram.'
                    return None, None, f'Ошибка Telegram API: {error_msg}'
                except Exception as e:
                    import logging
                    logger = logging.getLogger(__name__)
                    logger.error(f"Unexpected error getting Telegram photo: {str(e)}", exc_info=True)
                    return None, None, f'Неожиданная ошибка: {str(e)}'
                finally:
                    try:
                        await bot.close()
                    except:
                        pass
            
            # Запускаем асинхронную функцию
            loop = asyncio.new_event_loop()
            asyncio.set_event_loop(loop)
            try:
                photo_content, file_path, error_msg = loop.run_until_complete(get_telegram_photo())
            finally:
                loop.close()
            
            if error_msg:
                # Определяем статус код в зависимости от типа ошибки
                if 'Слишком много запросов' in error_msg:
                    status_code = status.HTTP_429_TOO_MANY_REQUESTS
                elif 'не найден' in error_msg.lower() or 'forbidden' in error_msg.lower() or 'доступ запрещен' in error_msg.lower() or 'доступ ограничен' in error_msg.lower():
                    status_code = status.HTTP_403_FORBIDDEN
                else:
                    status_code = status.HTTP_500_INTERNAL_SERVER_ERROR
                
                return Response(
                    {'error': error_msg},
                    status=status_code
                )
            
            if not photo_content:
                # Если error_msg уже был установлен, он уже был возвращен выше
                # Это означает, что photo_content = None, но error_msg тоже None
                # Значит, проблема в том, что фото не было найдено без конкретной ошибки
                import logging
                logger = logging.getLogger(__name__)
                logger.warning(f"Photo content is None for user {user.telegram_id}, but no error message was set. This should not happen.")
                return Response(
                    {'error': 'Не удалось загрузить фото профиля. Убедитесь, что:\n1. У вас установлено фото профиля в Telegram\n2. В настройках приватности Telegram (Настройки → Конфиденциальность → Фото профиля) выбрано "Все" - фото должно быть доступно для всех\n3. Вы начали диалог с ботом (@advent_dk_testing_bot) - отправьте команду /start\n4. Попробуйте перезапустить бота или подождите несколько минут'},
                    status=status.HTTP_404_NOT_FOUND
                )
            
            # Определяем расширение файла
            file_extension = file_path.split('.')[-1] if file_path and '.' in file_path else 'jpg'
            if file_extension not in ['jpg', 'jpeg', 'png', 'webp']:
                file_extension = 'jpg'
            
            # Удаляем старый аватар, если есть
            if user.avatar:
                try:
                    if os.path.isfile(user.avatar.path):
                        os.remove(user.avatar.path)
                except Exception as e:
                    import logging
                    logger = logging.getLogger(__name__)
                    logger.warning(f'Не удалось удалить старый аватар: {e}')
            
            # Сохраняем новое фото как аватар
            filename = f'telegram_avatar_{user.telegram_id}.{file_extension}'
            user.avatar.save(
                filename,
                ContentFile(photo_content),
                save=True
            )
            
            serializer = UserSerializer(user, context={'request': request})
            return Response({
                'success': True,
                'message': 'Аватар успешно загружен из Telegram',
                'user': serializer.data
            })
            
        except Exception as e:
            import logging
            logger = logging.getLogger(__name__)
            logger.error(f'Ошибка загрузки аватара из Telegram: {e}', exc_info=True)
            return Response(
                {'error': f'Не удалось загрузить аватар из Telegram: {str(e)}'},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR
            )
    
    @action(detail=False, methods=['patch'])
    def update_profile(self, request):
        """
        Обновить профиль.
        
        PATCH /api/users/profile/update_profile/
        
        Поддерживает:
        - Обновление текстовых полей (first_name, last_name, phone, bio, timezone, language)
        - Загрузку аватара (avatar - файл изображения)
        - Удаление аватара (avatar = null или пустая строка)
        """
        import os
        try:
            from PIL import Image
        except ImportError:
            Image = None
        
        user = request.user
        
        # Обработка удаления аватара
        if 'avatar' in request.data:
            avatar_value = request.data.get('avatar')
            # Проверяем, является ли это запросом на удаление
            if avatar_value is None or avatar_value == '' or avatar_value == 'null' or (isinstance(avatar_value, str) and avatar_value.strip() == ''):
                # Удаляем аватар
                if user.avatar:
                    try:
                        # Удаляем файл с диска
                        if os.path.isfile(user.avatar.path):
                            os.remove(user.avatar.path)
                    except Exception as e:
                        # Логируем ошибку, но продолжаем удаление из БД
                        import logging
                        logger = logging.getLogger(__name__)
                        logger.warning(f'Не удалось удалить файл аватара: {e}')
                    user.avatar = None
                    # Оптимизация: используем update_fields для частичного обновления
                    user.save(update_fields=['avatar'])
                    serializer = UserSerializer(user, context={'request': request})
                    return Response(serializer.data)
            elif hasattr(avatar_value, 'read'):  # Это файл
                # Валидация размера файла (макс 5MB)
                if avatar_value.size > 5 * 1024 * 1024:
                    return Response(
                        {'error': 'Размер файла не должен превышать 5MB'},
                        status=status.HTTP_400_BAD_REQUEST
                    )
                
                # Валидация формата файла
                allowed_formats = ['image/jpeg', 'image/jpg', 'image/png', 'image/webp', 'image/gif']
                if avatar_value.content_type not in allowed_formats:
                    return Response(
                        {'error': 'Поддерживаются только форматы: JPEG, PNG, WebP, GIF'},
                        status=status.HTTP_400_BAD_REQUEST
                    )
                
                # Валидация размеров изображения (если Pillow установлен)
                if Image:
                    try:
                        # Сохраняем временно для проверки
                        avatar_value.seek(0)
                        img = Image.open(avatar_value)
                        width, height = img.size
                        
                        # Минимальный размер
                        if width < 50 or height < 50:
                            return Response(
                                {'error': 'Минимальный размер изображения: 50x50 пикселей'},
                                status=status.HTTP_400_BAD_REQUEST
                            )
                        
                        # Максимальный размер (опционально, для оптимизации)
                        if width > 2000 or height > 2000:
                            # Можно автоматически уменьшить, но пока просто предупреждаем
                            pass
                        
                        # Возвращаем указатель в начало
                        avatar_value.seek(0)
                    except Exception as e:
                        return Response(
                            {'error': f'Не удалось обработать изображение: {str(e)}'},
                            status=status.HTTP_400_BAD_REQUEST
                        )
        
        # Обновляем основные данные
        allowed_fields = [
            'email',
            'first_name',
            'last_name',
            'phone',
            'bio',
            'avatar',
            'timezone',
            'language',
            'birth_date'
        ]
        
        for field in allowed_fields:
            if field in request.data:
                value = request.data[field]
                
                # Специальная обработка для email
                if field == 'email':
                    value = str(value).lower().strip()
                    if not value:
                        continue
                    if User.objects.filter(email=value).exclude(pk=user.pk).exists():
                        return Response({'error': 'Пользователь с таким email уже существует'}, status=400)
                    user.email = value
                    continue

                # Пропускаем пустые строки для опциональных полей
                if field in ['phone', 'bio', 'timezone', 'language'] and value == '':
                    setattr(user, field, '')
                elif value is not None:
                    if field == 'phone':
                        value = normalize_phone(str(value))
                    setattr(user, field, value)
        
        user.save()
        
        # Обновляем сериализатор с контекстом request для правильного формирования URL
        serializer = UserSerializer(user, context={'request': request})
        return Response(serializer.data)
    
    @action(detail=False, methods=['post'])
    def change_password(self, request):
        """
        Сменить пароль.
        
        POST /api/users/profile/change_password/
        Body: {
            "old_password": "...",
            "new_password": "..."
        }
        """
        user = request.user
        
        old_password = request.data.get('old_password')
        new_password = request.data.get('new_password')
        
        if not old_password or not new_password:
            return Response(
                {'error': 'Необходимо указать старый и новый пароли'},
                status=status.HTTP_400_BAD_REQUEST
            )
        
        # Проверяем старый пароль
        if not user.check_password(old_password):
            return Response(
                {'error': 'Неверный старый пароль'},
                status=status.HTTP_400_BAD_REQUEST
            )
        
        # Устанавливаем новый пароль
        user.set_password(new_password)
        user.save()
        
        return Response({'message': 'Пароль успешно изменен'})
    
    @action(detail=False, methods=['get'], url_path='settings')
    def get_settings(self, request):
        """
        Получить настройки профиля.
        
        GET /api/users/profile/settings/
        """
        user = request.user
        
        # Настройки из модели User
        settings = {
            'notifications': {
                'email_notifications': getattr(user, 'email_notifications', True),
                'push_notifications': getattr(user, 'push_notifications', True),
                'sms_notifications': getattr(user, 'sms_notifications', False)
            },
            'preferences': {
                'timezone': user.timezone,
                'language': user.language,
                'theme': getattr(user, 'theme', 'light'),
                'country': getattr(user, 'country', ''),
                'city': getattr(user, 'city', ''),
            },
            'privacy': {
                'show_online_status': getattr(user, 'show_online_status', True),
                'allow_messages': getattr(user, 'allow_messages', True)
            }
        }
        # Настройки ментора: доверие AI при проверке ДЗ
        if getattr(user, 'role', None) == 'mentor':
            settings['mentor_homework_ai'] = {
                'ai_trust_draft': getattr(user, 'ai_trust_draft', False),
                'ai_trust_publish': getattr(user, 'ai_trust_publish', False),
            }
        settings['onboarding_tours_seen'] = getattr(user, 'onboarding_tours_seen', {}) or {}
        
        return Response(settings)

    @action(detail=False, methods=['get'], url_path='countries')
    def get_countries_action(self, request):
        """
        Получить список стран.

        GET /api/profile/countries/
        """
        return Response(get_countries())

    @action(detail=False, methods=['get'], url_path='timezones')
    def get_timezones_action(self, request):
        """
        Получить список часовых поясов.

        GET /api/profile/timezones/
        """
        now = datetime.utcnow()
        timezones_data = []
        for name in sorted(available_timezones()):
            try:
                tz = ZoneInfo(name)
                offset = now.astimezone(tz).utcoffset()
                if offset is None:
                    continue
                total_minutes = int(offset.total_seconds() // 60)
                sign = '+' if total_minutes >= 0 else '-'
                hours, minutes = divmod(abs(total_minutes), 60)
                offset_str = f"{sign}{hours:02d}:{minutes:02d}"
                timezones_data.append({
                    "name": name,
                    "offset": offset_str,
                })
            except Exception:
                continue
        return Response(timezones_data)

    @action(detail=False, methods=['get'], url_path='cities/search', permission_classes=[AllowAny])
    def search_cities_from_csv(self, request):
        """
        Поиск городов из city.csv по запросу.
        Публичный endpoint (доступен без аутентификации).

        GET /api/profile/cities/search/?q=москва
        """
        query = request.query_params.get('q', '').strip()
        limit = int(request.query_params.get('limit', 20))
        
        if not query:
            return Response([])
        
        # Нормализуем запрос: приводим к lowercase после проверки на пустоту
        query_lower = query.lower().strip()
        
        import os
        possible_paths = [
            os.path.join(settings.BASE_DIR, '..', '..', 'city.csv'),
            os.path.join(settings.BASE_DIR, '..', 'city.csv'),
            os.path.join(settings.BASE_DIR, 'city.csv'),
            '/app/city.csv',
            '/code/city.csv',
        ]
        csv_path = next((p for p in possible_paths if os.path.exists(p)), None)
        # Ключ кеша по mtime — после загрузки нового city.csv подхватятся свежие данные
        if csv_path:
            cache_key = 'cities_csv_data_%s' % int(os.path.getmtime(csv_path))
            cities_data = cache.get(cache_key)
        else:
            cache_key = None
            cities_data = None
        
        if cities_data is None and csv_path:
            try:
                cities_data = []
                with open(csv_path, 'r', encoding='utf-8') as f:
                    reader = csv.DictReader(f)
                    for row in reader:
                        city_name = row.get('city', '').strip()
                        city_type = row.get('city_type', '').strip()
                        timezone = row.get('timezone', '').strip()
                        region = row.get('region', '').strip()
                        if not city_name and region:
                            city_name = region
                        if city_name and timezone:
                            full_city_name = f"{city_type} {city_name}".strip() if city_type else city_name
                            cities_data.append({
                                'name': city_name,
                                'full_search_name': full_city_name.lower(),
                                'timezone': timezone,
                                'region': region,
                                'city_type': city_type,
                                'full_name': f"{city_name}" + (f", {region}" if region and region != city_name else ""),
                            })
                cache.set(cache_key, cities_data, 24 * 60 * 60)
                import logging
                logger = logging.getLogger(__name__)
                logger.info(f"Loaded {len(cities_data)} cities from CSV")
            except Exception as e:
                # В случае ошибки возвращаем пустой список
                import logging
                logger = logging.getLogger(__name__)
                logger.error(f"Error loading city.csv: {e}")
                import traceback
                logger.error(traceback.format_exc())
                return Response({'error': str(e)}, status=500)
        
        # Если CSV пустой или не найден — используем cities_ru.json (geo_utils)
        if not cities_data:
            import logging
            logger = logging.getLogger(__name__)
            logger.info("city.csv empty or missing, using cities_ru.json fallback")
            from .geo_utils import search_cities as search_cities_json
            json_cities = search_cities_json(country="RU", query=query_lower)
            results = []
            for c in json_cities[:limit]:
                results.append({
                    'name': c.get('city', ''),
                    'timezone': c.get('timezone', ''),
                    'region': c.get('country_name', ''),
                    'full_name': f"{c.get('city', '')}, {c.get('country_name', '')}".strip(', '),
                })
            return Response(results)

        # Ищем города по запросу в данных из CSV
        results = []
        
        # Префиксы типов населенных пунктов, которые нужно убирать при поиске
        settlement_prefixes = ['г ', 'пгт ', 'пос ', 'с ', 'д ', 'дер ', 'село ', 'п ', 'рп ', 'ст ', 'ст-ца ', 'х ', 'клх ', 'снт ', 'днп ']
        
        # Нормализуем запрос: убираем префиксы, если они есть
        normalized_query = query_lower
        for prefix in settlement_prefixes:
            if normalized_query.startswith(prefix):
                normalized_query = normalized_query[len(prefix):].strip()
                break
        
        # Дополнительно: убираем все префиксы из начала запроса для более гибкого поиска
        for city in cities_data:
            city_name = city['name']
            city_name_lower = city_name.lower()
            full_search_name = city.get('full_search_name', city_name_lower)
            region_lower = city.get('region', '').lower()
            
            # Нормализуем название города для поиска (убираем префиксы)
            normalized_city_name = city_name_lower
            for prefix in settlement_prefixes:
                if normalized_city_name.startswith(prefix):
                    normalized_city_name = normalized_city_name[len(prefix):].strip()
                    break
            
            # Нормализуем полное имя с типом
            normalized_full_name = full_search_name
            for prefix in settlement_prefixes:
                if normalized_full_name.startswith(prefix):
                    normalized_full_name = normalized_full_name[len(prefix):].strip()
                    break
            
            # Ищем совпадения:
            # 1. В оригинальном названии города (без типа)
            # 2. В полном названии с типом (г Москва)
            # 3. В нормализованном названии города
            # 4. В нормализованном полном названии
            # 5. В регионе
            matches = (
                query_lower in city_name_lower or
                query_lower in full_search_name or
                normalized_query in normalized_city_name or
                normalized_query in normalized_full_name or
                query_lower in region_lower
            )
            
            if matches:
                # Определяем приоритет: точное совпадение в начале идет первым
                priority = 0
                if normalized_city_name.startswith(normalized_query):
                    priority = 1  # Точное совпадение в начале без типа
                elif city_name_lower.startswith(query_lower):
                    priority = 2  # Совпадение в начале с оригинальным названием
                elif full_search_name.startswith(query_lower):
                    priority = 3  # Совпадение в начале с полным именем (г Москва)
                elif normalized_query in normalized_city_name:
                    priority = 4  # Совпадение в любом месте без типа
                elif normalized_query in normalized_full_name:
                    priority = 5  # Совпадение в любом месте с типом
                elif query_lower in city_name_lower:
                    priority = 6  # Совпадение в любом месте оригинала
                else:
                    priority = 7  # Совпадение в регионе
                
                results.append({
                    'name': city['name'],
                    'timezone': city['timezone'],
                    'region': city.get('region', ''),
                    'full_name': city['full_name'],
                    'priority': priority,
                })
        
        # Сортируем результаты по приоритету (лучшие совпадения первыми)
        results.sort(key=lambda x: x['priority'])
        
        # Убираем поле priority из результата
        for result in results:
            result.pop('priority', None)
        
        return Response(results[:limit])

    @action(detail=False, methods=['get'], url_path='cities')
    def get_cities(self, request):
        """
        Получить список популярных городов и их часовых поясов.

        GET /api/profile/cities/
        Параметры:
          - country (опционально): ISO-код страны (например, RU, KZ, BY) или название страны.
        """
        country = request.query_params.get('country')

        cities = POPULAR_CITIES
        if country:
            country_lower = country.lower()
            cities = [
                c for c in POPULAR_CITIES
                if c["country_code"].lower() == country_lower
                or c["country_name"].lower().startswith(country_lower)
            ]

        return Response(cities)
    
    @action(detail=False, methods=['patch'])
    def update_settings(self, request):
        """
        Обновить настройки профиля.
        
        PATCH /api/users/profile/update_settings/
        Body: {
            "notifications": {...},
            "preferences": {...},
            "privacy": {...}
        }
        """
        user = request.user
        
        # Уведомления
        if 'notifications' in request.data:
            notifications = request.data['notifications']
            for key, value in notifications.items():
                if hasattr(user, key):
                    setattr(user, key, value)
        
        # Предпочтения
        if 'preferences' in request.data:
            preferences = request.data['preferences']
            for key, value in preferences.items():
                if hasattr(user, key):
                    setattr(user, key, value)
        
        # Приватность
        if 'privacy' in request.data:
            privacy = request.data['privacy']
            for key, value in privacy.items():
                if hasattr(user, key):
                    setattr(user, key, value)
        
        # Настройки ментора: доверие AI при проверке ДЗ
        if getattr(user, 'role', None) == 'mentor' and 'mentor_homework_ai' in request.data:
            mentor_ai = request.data['mentor_homework_ai']
            if isinstance(mentor_ai, dict):
                if 'ai_trust_draft' in mentor_ai:
                    user.ai_trust_draft = bool(mentor_ai['ai_trust_draft'])
                if 'ai_trust_publish' in mentor_ai:
                    user.ai_trust_publish = bool(mentor_ai['ai_trust_publish'])
        
        # Онбординг: отметка просмотренных туров
        if 'onboarding_tours_seen' in request.data:
            tours = request.data['onboarding_tours_seen']
            if isinstance(tours, dict):
                current = getattr(user, 'onboarding_tours_seen', None) or {}
                merged = {**current, **{k: bool(v) for k, v in tours.items()}}
                user.onboarding_tours_seen = merged
        
        user.save()
        
        return Response({'message': 'Настройки успешно обновлены'})
    
    @action(detail=False, methods=['delete'])
    def delete_account(self, request):
        """
        Удалить аккаунт.
        
        DELETE /api/users/profile/delete_account/
        Body: {
            "password": "..."
        }
        """
        user = request.user
        password = request.data.get('password')
        
        if not password:
            return Response(
                {'error': 'Необходимо указать пароль'},
                status=status.HTTP_400_BAD_REQUEST
            )
        
        # Проверяем пароль
        if not user.check_password(password):
            return Response(
                {'error': 'Неверный пароль'},
                status=status.HTTP_400_BAD_REQUEST
            )
        
        # Деактивируем аккаунт
        user.is_active = False
        user.save()
        
        return Response({'message': 'Аккаунт успешно удален'})


def _apply_mentor_connection(conn):
    """После полного подтверждения связи: добавить ментора к студенту, создать доску."""
    from apps.users.mentorship_views import _apply_connection
    _apply_connection(conn)


class ClientManagementViewSet(viewsets.ViewSet):
    """
    ViewSet для управления клиентами (для менторов).
    
    list: Список клиентов
    check_user: Проверить пользователя по email (существует ли, является ли клиентом)
    add_client: Отправить приглашение (по email или 8-символьному коду)
    remove_client: Удалить клиента
    client_details: Детали клиента
    """
    
    permission_classes = [IsAuthenticated]
    
    def list(self, request):
        """
        Список клиентов ментора.
        
        GET /api/users/manage/clients/?page=1&page_size=20
        """
        user = request.user
        
        if user.role != 'mentor':
            return Response(
                {'error': 'Только для менторов'},
                status=status.HTTP_403_FORBIDDEN
            )
        
        # Кеширование: кеш на 5 минут для каждого пользователя и страницы
        # Увеличено с 2 до 5 минут для ускорения повторных загрузок страницы "Студенты"
        page = int(request.query_params.get('page', 1))
        page_size = int(request.query_params.get('page_size', 20))
        cache_key = f'manage_clients_{user.id}_{page}_{page_size}'
        
        cached_data = cache.get(cache_key)
        
        if cached_data is not None:
            return Response(cached_data)
        
        # ВАЖНО: оптимизация страницы "Студенты"
        # Раньше ClientSerializer считал статистику занятий через 3 отдельных запроса на каждого клиента (N+1).
        # Здесь заранее аннотируем эти значения одним SQL-запросом.
        from django.db.models import Count, Q
        
        # Получаем ID студентов из принятых связей (MentorStudentConnection)
        # Это нужно для случаев, когда связь создана, но ментор ещё не добавлен в client.mentors
        accepted_connections = MentorStudentConnection.objects.filter(
            mentor=user,
            status=MentorStudentConnection.STATUS_ACCEPTED
        ).values_list('student_id', flat=True)
        
        # Получаем ID клиентов, у которых ментор уже добавлен в client.mentors
        clients_with_mentor = Client.objects.filter(mentors=user).values_list('id', flat=True)
        
        # Получаем ID клиентов из принятых связей, у которых ментор ещё не добавлен
        clients_to_sync = Client.objects.filter(
            user_id__in=accepted_connections
        ).exclude(id__in=clients_with_mentor).values_list('id', flat=True)
        
        # Синхронизация: добавляем ментора в client.mentors для клиентов с принятыми связями
        # Используем bulk операцию для эффективности
        if clients_to_sync:
            clients_to_update = Client.objects.filter(id__in=clients_to_sync)
            for client in clients_to_update:
                client.mentors.add(user)
        
        # Фильтруем клиентов: либо ментор в client.mentors, либо есть принятая связь
        clients = (
            Client.objects.filter(
                Q(mentors=user) | Q(user_id__in=accepted_connections)
            )
            .select_related('user')
            .prefetch_related('mentors')
            .distinct()
            .annotate(
                # Поля с суффиксом _annotated читает ClientSerializer (если присутствуют)
                scheduled_lessons_annotated=Count(
                    'lessons',
                    filter=Q(lessons__mentor=user, lessons__status='scheduled'),
                    distinct=True,
                ),
                completed_lessons_annotated=Count(
                    'lessons',
                    filter=Q(lessons__mentor=user, lessons__status='completed'),
                    distinct=True,
                ),
                total_lessons_annotated=Count(
                    'lessons',
                    filter=Q(lessons__mentor=user) & ~Q(lessons__status='cancelled'),
                    distinct=True,
                ),
            )
        )
        
        # Пагинация
        from rest_framework.pagination import PageNumberPagination
        paginator = PageNumberPagination()
        paginator.page_size = page_size
        paginated_clients = paginator.paginate_queryset(clients, request)
        
        serializer = ClientSerializer(paginated_clients, many=True, context={'request': request})
        
        response_data = paginator.get_paginated_response(serializer.data)
        
        # Ожидающие подтверждения приглашения (студент/родитель ещё не подтвердили)
        pending = MentorStudentConnection.objects.filter(
            mentor=user,
            initiator=MentorStudentConnection.INITIATOR_MENTOR,
            status__in=[MentorStudentConnection.STATUS_PENDING_STUDENT, MentorStudentConnection.STATUS_PENDING_PARENT],
        ).select_related('student').order_by('-created_at')
        def _client_id(u):
            try:
                return u.client_profile.id
            except (Client.DoesNotExist, AttributeError):
                return None

        response_data.data['pending_invitations'] = [
            {
                'id': inv.id,
                'invitation_id': inv.id,
                'status': inv.status,
                'created_at': inv.created_at.isoformat() if inv.created_at else None,
                'student': {
                    'id': _client_id(inv.student),
                    'email': inv.student.email,
                    'first_name': inv.student.first_name or '',
                    'last_name': inv.student.last_name or '',
                },
                'is_pending_invitation': True,
            }
            for inv in pending
        ]
        
        # Сохраняем в кеш на 5 минут (300 секунд) для ускорения повторных загрузок
        cache.set(cache_key, response_data.data, 300)
        
        return response_data
    
    @action(detail=False, methods=['get'], url_path='check-user')
    def check_user(self, request):
        """
        Проверить пользователя по email: зарегистрирован ли, является ли клиентом.
        GET /api/manage/clients/check-user/?email=...
        Ответ: { "exists": bool, "is_client": bool }
        """
        if request.user.role != 'mentor':
            return Response({'error': 'Только для менторов'}, status=status.HTTP_403_FORBIDDEN)
        email = (request.query_params.get('email') or '').strip().lower()
        if not email:
            return Response({'error': 'Укажите email'}, status=status.HTTP_400_BAD_REQUEST)
        try:
            u = User.objects.get(email=email)
            return Response({'exists': True, 'is_client': u.role == 'client'})
        except User.DoesNotExist:
            return Response({'exists': False, 'is_client': False})
    
    @action(detail=False, methods=['post'])
    def add_client(self, request):
        """
        Отправить приглашение студенту. Взаимодействие разрешено только после подтверждения
        студентом (и родителем, если привязан).
        
        POST /api/manage/clients/add_client/
        Body: либо { "email": "..." } — для незарегистрированных или по email;
              либо { "universal_code": "A1B2C3D4" } — 8-символьный код (цифры и латинские буквы) зарегистрированного ученика.
        Ответ: { "status": "invitation_sent", "message": "...", "invitation_id": id }
        """
        mentor = request.user
        if mentor.role != 'mentor':
            return Response({'error': 'Только для менторов'}, status=status.HTTP_403_FORBIDDEN)
        
        for page in range(1, 10):
            for size in [10, 20, 50, 100, 1000]:
                cache.delete(f'manage_clients_{mentor.id}_{page}_{size}')
        
        email = (request.data.get('email') or '').strip().lower()
        universal_code = (request.data.get('universal_code') or '').strip()
        
        if not email and not universal_code:
            return Response(
                {'error': 'Укажите email (для нового ученика) или 8-символьный универсальный код (для зарегистрированного)'},
                status=status.HTTP_400_BAD_REQUEST
            )
        if email and universal_code:
            return Response(
                {'error': 'Укажите только email или только универсальный код'},
                status=status.HTTP_400_BAD_REQUEST
            )
        
        client_user = None
        client = None
        is_new_user = False
        set_password_url = None
        
        if universal_code:
            universal_code = universal_code.upper().strip()
            allowed = set('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789')
            # Допускаем старый формат (6 цифр) и новый (8 символов: цифры + латинские буквы)
            valid_8 = len(universal_code) == 8 and all(c in allowed for c in universal_code)
            valid_6_legacy = len(universal_code) == 6 and universal_code.isdigit()
            if not (valid_8 or valid_6_legacy):
                return Response(
                    {'error': 'Универсальный код: 8 символов (цифры и латинские буквы) или 6 цифр (старый формат)'},
                    status=status.HTTP_400_BAD_REQUEST
                )
            try:
                client_user = User.objects.get(universal_code=universal_code)
            except User.DoesNotExist:
                return Response(
                    {'error': 'Пользователь с таким кодом не найден'},
                    status=status.HTTP_404_NOT_FOUND
                )
            if client_user.role != 'client':
                return Response(
                    {'error': 'Пользователь с этим кодом не является учеником'},
                    status=status.HTTP_400_BAD_REQUEST
                )
            client, _ = Client.objects.get_or_create(user=client_user)
        else:
            try:
                client_user = User.objects.get(email=email)
                if client_user.role != 'client':
                    return Response(
                        {'error': 'Пользователь с таким email зарегистрирован с другой ролью'},
                        status=status.HTTP_400_BAD_REQUEST
                    )
                client, _ = Client.objects.get_or_create(user=client_user)
            except User.DoesNotExist:
                temp_password = secrets.token_urlsafe(12)
                client_user = User.objects.create_user(
                    email=email,
                    password=temp_password,
                    first_name='',
                    last_name='',
                    role='client',
                    email_verified=False,
                )
                client = Client.objects.create(user=client_user)
                is_new_user = True
                reset_token = secrets.token_urlsafe(32)
                client_user.email_verification_token = reset_token
                client_user.save()
                set_password_url = f"{getattr(settings, 'FRONTEND_URL', '')}/reset-password?token={reset_token}"
        
        if mentor in client.mentors.all():
            return Response(
                {'error': 'Этот ученик уже добавлен к вам'},
                status=status.HTTP_400_BAD_REQUEST
            )
        
        conn, created = MentorStudentConnection.objects.get_or_create(
            mentor=mentor,
            student=client_user,
            defaults={
                'status': MentorStudentConnection.STATUS_PENDING_STUDENT,
                'initiator': MentorStudentConnection.INITIATOR_MENTOR,
                'confirm_token': secrets.token_urlsafe(32) if is_new_user or True else None,
            }
        )
        if not created:
            if conn.status == MentorStudentConnection.STATUS_ACCEPTED:
                return Response({'error': 'Ученик уже добавлен'}, status=status.HTTP_400_BAD_REQUEST)
            if conn.status == MentorStudentConnection.STATUS_PENDING_STUDENT:
                return Response({
                    'status': 'invitation_sent',
                    'message': 'Приглашение уже отправлено, ожидайте подтверждения',
                    'invitation_id': conn.id,
                }, status=status.HTTP_200_OK)
        
        if not conn.confirm_token:
            conn.confirm_token = secrets.token_urlsafe(32)
            conn.save(update_fields=['confirm_token'])
        
        confirm_url = f"{getattr(settings, 'FRONTEND_URL', '')}/invitation/confirm?token={conn.confirm_token}"
        if is_new_user and set_password_url:
            set_password_url = f"{set_password_url}&invitation_token={conn.confirm_token}"
        
        send_mentor_invitation_email_task.delay(
            conn.id,
            set_password_url=set_password_url if is_new_user else None,
        )
        # Уведомление студенту (in-app, telegram)
        NotificationService.create_notification_with_telegram(
            recipient=client_user,
            notification_type='mentor_invitation_new',
            title='Вас пригласили в качестве ученика',
            message=f'{mentor.get_full_name() or mentor.email} приглашает вас стать учеником. Подтвердите приглашение во вкладке «Входящие приглашения».',
            action_url='/request-mentor',
            data={'connection_id': conn.id, 'invitation_id': conn.id},
        )

        return Response({
            'status': 'invitation_sent',
            'message': 'Приглашение отправлено. После подтверждения учеником (и родителем при необходимости) взаимодействие будет разрешено.',
            'invitation_id': conn.id,
        }, status=status.HTTP_201_CREATED)
    
    @action(detail=True, methods=['delete'])
    def remove_client(self, request, pk=None):
        """
        Удалить клиента.
        
        DELETE /api/users/clients/{id}/remove_client/
        
        При удалении студента из списка ментора:
        - Удаляются все будущие занятия с этим учеником
        - Автоматически запрещается доступ ко всем материалам (через связь mentors)
        """
        from django.utils import timezone
        from apps.schedule.models import Lesson
        
        user = request.user
        
        if user.role != 'mentor':
            return Response(
                {'error': 'Только для менторов'},
                status=status.HTTP_403_FORBIDDEN
            )
        
        try:
            client = Client.objects.get(id=pk, mentors=user)
        except Client.DoesNotExist:
            return Response(
                {'error': 'Клиент не найден'},
                status=status.HTTP_404_NOT_FOUND
            )
        
        # Удаляем все будущие занятия с этим учеником
        now = timezone.now()
        future_lessons = Lesson.objects.filter(
            mentor=user,
            client=client,
            start_time__gt=now,
            status='scheduled'
        )
        future_lessons_count = future_lessons.count()
        future_lessons.delete()
        
        # Удаляем ментора (это автоматически запретит доступ ко всем материалам)
        client.mentors.remove(user)
        
        # Инвалидируем кеш списка клиентов для этого ментора
        # Удаляем все варианты кеша для этого пользователя (разные страницы и размеры)
        for page in range(1, 10):
            for size in [10, 20, 50, 100, 1000]:
                cache.delete(f'manage_clients_{user.id}_{page}_{size}')
        
        return Response({
            'message': 'Клиент успешно удален',
            'future_lessons_deleted': future_lessons_count
        })
    
    @action(detail=True, methods=['get'])
    def client_details(self, request, pk=None):
        # ... existing code ...
        return Response(data)

    @action(detail=False, methods=['post'], url_path='generate-invitation-link')
    def generate_invitation_link(self, request):
        """
        Сгенерировать или обновить токен ссылки-приглашения.
        POST /api/users/manage/clients/generate-invitation-link/
        """
        user = request.user
        if user.role != 'mentor':
            return Response({'error': 'Только для менторов'}, status=status.HTTP_403_FORBIDDEN)
        
        user.invitation_link_token = secrets.token_urlsafe(32)
        user.save(update_fields=['invitation_link_token'])
        
        from django.conf import settings
        frontend_url = getattr(settings, 'FRONTEND_URL', '').rstrip('/')
        link = f"{frontend_url}/invite/{user.invitation_link_token}"
        
        return Response({
            'invitation_link_token': user.invitation_link_token,
            'invitation_link': link
        })


class InvitationViewSet(viewsets.ViewSet):
    """
    Подтверждение приглашений ментор—студент.
    confirm_by_token: подтвердить по токену из письма (студент)
    my_invitations: список приглашений для текущего пользователя (студент/родитель)
    confirm_as_student: подтвердить как студент (по invitation_id, авторизованный)
    confirm_as_parent: подтвердить как родитель (по invitation_id)
    """
    permission_classes = [IsAuthenticated]

    @action(detail=False, methods=['post'], url_path='confirm-by-token', permission_classes=[AllowAny])
    def confirm_by_token(self, request):
        """
        Подтвердить приглашение по токену из письма (для студента).
        POST /api/invitation/confirm-by-token/ { "token": "..." }
        """
        token = (request.data.get('token') or request.query_params.get('token') or '').strip()
        if not token:
            return Response({'error': 'Укажите токен'}, status=status.HTTP_400_BAD_REQUEST)
        try:
            conn = MentorStudentConnection.objects.select_related('mentor', 'student').get(
                confirm_token=token,
                status=MentorStudentConnection.STATUS_PENDING_STUDENT,
                initiator=MentorStudentConnection.INITIATOR_MENTOR,
            )
        except MentorStudentConnection.DoesNotExist:
            return Response({'error': 'Приглашение не найдено или уже обработано'}, status=status.HTTP_404_NOT_FOUND)
        conn.student_confirmed_at = timezone.now()
        if conn.requires_parent_confirmation():
            conn.status = MentorStudentConnection.STATUS_PENDING_PARENT
        else:
            conn.status = MentorStudentConnection.STATUS_ACCEPTED
        conn.save(update_fields=['student_confirmed_at', 'status', 'updated_at'])
        if conn.status == MentorStudentConnection.STATUS_ACCEPTED:
            _apply_mentor_connection(conn)
        return Response({
            'status': 'student_confirmed',
            'message': 'Приглашение подтверждено. Родитель также должен подтвердить, если он привязан к вашему аккаунту.',
            'requires_parent': conn.requires_parent_confirmation(),
        })

    @action(detail=False, methods=['get'], url_path='my-invitations')
    def my_invitations(self, request):
        """
        Список приглашений для текущего пользователя (студент — свои, родитель — для своих детей).
        GET /api/invitation/my-invitations/
        """
        user = request.user
        if user.role == 'client':
            invitations = MentorStudentConnection.objects.filter(
                student=user,
                initiator=MentorStudentConnection.INITIATOR_MENTOR,
                status__in=[MentorStudentConnection.STATUS_PENDING_STUDENT, MentorStudentConnection.STATUS_PENDING_PARENT],
            ).select_related('mentor').order_by('-created_at')
        elif user.role == 'parent':
            try:
                parent = user.parent_profile
                children = parent.children.all()
                child_users = [c.user_id for c in children]
            except Parent.DoesNotExist:
                child_users = []
            invitations = MentorStudentConnection.objects.filter(
                student_id__in=child_users,
                initiator=MentorStudentConnection.INITIATOR_MENTOR,
                status=MentorStudentConnection.STATUS_PENDING_PARENT,
            ).select_related('mentor', 'student').order_by('-created_at')
        else:
            invitations = MentorStudentConnection.objects.none()
        data = [
            {
                'id': inv.id,
                'mentor': {'id': inv.mentor.id, 'email': inv.mentor.email, 'first_name': inv.mentor.first_name, 'last_name': inv.mentor.last_name},
                'student': {'id': inv.student.id, 'email': inv.student.email} if inv.student_id else None,
                'status': inv.status,
                'created_at': inv.created_at.isoformat() if inv.created_at else None,
                'student_confirmed_at': inv.student_confirmed_at.isoformat() if inv.student_confirmed_at else None,
            }
            for inv in invitations
        ]
        return Response(data)

    @action(detail=False, methods=['post'], url_path='confirm-as-student')
    def confirm_as_student(self, request):
        """
        Подтвердить приглашение как студент (авторизованный).
        POST /api/invitation/confirm-as-student/ { "invitation_id": id }
        """
        user = request.user
        if user.role != 'client':
            return Response({'error': 'Только для учеников'}, status=status.HTTP_403_FORBIDDEN)
        inv_id = request.data.get('invitation_id')
        if inv_id is None:
            return Response({'error': 'Укажите invitation_id'}, status=status.HTTP_400_BAD_REQUEST)
        try:
            inv_id = int(inv_id)
        except (TypeError, ValueError):
            return Response({'error': 'Некорректный invitation_id'}, status=status.HTTP_400_BAD_REQUEST)
        try:
            conn = MentorStudentConnection.objects.select_related('mentor').get(
                id=inv_id, student=user,
                status=MentorStudentConnection.STATUS_PENDING_STUDENT,
                initiator=MentorStudentConnection.INITIATOR_MENTOR,
            )
        except MentorStudentConnection.DoesNotExist:
            conn_any = MentorStudentConnection.objects.filter(id=inv_id, student=user).first()
            if conn_any:
                if conn_any.initiator != MentorStudentConnection.INITIATOR_MENTOR:
                    return Response({'error': 'Это запрос от вас, не приглашение. Принимает ментор.'}, status=status.HTTP_400_BAD_REQUEST)
                if conn_any.status == MentorStudentConnection.STATUS_ACCEPTED:
                    return Response({'error': 'Приглашение уже принято'}, status=status.HTTP_400_BAD_REQUEST)
                if conn_any.status == MentorStudentConnection.STATUS_PENDING_PARENT:
                    return Response({'error': 'Вы уже подтвердили. Ожидается подтверждение родителя.'}, status=status.HTTP_400_BAD_REQUEST)
            return Response({'error': 'Приглашение не найдено'}, status=status.HTTP_404_NOT_FOUND)
        conn.student_confirmed_at = timezone.now()
        if conn.requires_parent_confirmation():
            conn.status = MentorStudentConnection.STATUS_PENDING_PARENT
        else:
            conn.status = MentorStudentConnection.STATUS_ACCEPTED
        conn.save(update_fields=['student_confirmed_at', 'status', 'updated_at'])
        if conn.status == MentorStudentConnection.STATUS_ACCEPTED:
            _apply_mentor_connection(conn)
            # Уведомление ментору: приглашение принято (in-app, email, telegram)
            NotificationService.create_notification_with_telegram(
                recipient=conn.mentor,
                notification_type='mentor_invitation_accepted',
                title='Приглашение принято',
                message=f'{user.get_full_name() or user.email} принял(а) ваше приглашение.',
                action_url='/students',
                data={'connection_id': conn.id},
            )
        return Response({'status': 'student_confirmed', 'requires_parent': conn.requires_parent_confirmation()})

    @action(detail=False, methods=['post'], url_path='reject-as-student')
    def reject_as_student(self, request):
        """
        Отклонить приглашение как студент (авторизованный).
        POST /api/invitation/reject-as-student/ { "invitation_id": id }
        """
        user = request.user
        if user.role != 'client':
            return Response({'error': 'Только для учеников'}, status=status.HTTP_403_FORBIDDEN)
        inv_id = request.data.get('invitation_id')
        if inv_id is None:
            return Response({'error': 'Укажите invitation_id'}, status=status.HTTP_400_BAD_REQUEST)
        try:
            inv_id = int(inv_id)
        except (TypeError, ValueError):
            return Response({'error': 'Некорректный invitation_id'}, status=status.HTTP_400_BAD_REQUEST)
        try:
            conn = MentorStudentConnection.objects.select_related('mentor').get(
                id=inv_id, student=user,
                status=MentorStudentConnection.STATUS_PENDING_STUDENT,
                initiator=MentorStudentConnection.INITIATOR_MENTOR,
            )
        except MentorStudentConnection.DoesNotExist:
            return Response({'error': 'Приглашение не найдено'}, status=status.HTTP_404_NOT_FOUND)
        conn.status = MentorStudentConnection.STATUS_REJECTED
        conn.save(update_fields=['status', 'updated_at'])
        # Уведомление ментору: приглашение отклонено (in-app, email, telegram)
        NotificationService.create_notification_with_telegram(
            recipient=conn.mentor,
            notification_type='mentor_invitation_rejected',
            title='Приглашение отклонено',
            message=f'{user.get_full_name() or user.email} отклонил(а) ваше приглашение.',
            action_url='/students',
            data={'connection_id': conn.id},
        )
        return Response({'status': 'rejected', 'message': 'Приглашение отклонено'})

    @action(detail=False, methods=['post'], url_path='confirm-as-parent')
    def confirm_as_parent(self, request):
        # ... existing code ...
        return Response({'status': 'confirmed'})

    @action(detail=False, methods=['get'], url_path='info-by-token', permission_classes=[AllowAny])
    def info_by_token(self, request):
        """
        Получить информацию о менторе по токену ссылки-приглашения.
        GET /api/invitation/info-by-token/?token=...
        """
        token = request.query_params.get('token')
        if not token:
            return Response({'error': 'Токен не указан'}, status=status.HTTP_400_BAD_REQUEST)
        
        try:
            mentor = User.objects.get(invitation_link_token=token, role='mentor')
            return Response({
                'mentor_name': mentor.get_full_name(),
                'mentor_id': mentor.id,
                'avatar_url': request.build_absolute_uri(mentor.avatar.url) if mentor.avatar else None,
            })
        except User.DoesNotExist:
            return Response({'error': 'Недействительная ссылка'}, status=status.HTTP_404_NOT_FOUND)

    @action(detail=False, methods=['post'], url_path='register-by-link', permission_classes=[AllowAny])
    def register_by_link(self, request):
        """
        Регистрация ученика по ссылке-приглашению.
        POST /api/invitation/register-by-link/
        Body: {
            "token": "...",
            "first_name": "...",
            "last_name": "...",
            "email": "..." (optional),
            "password": "..." (optional),
            "timezone": "...",
            "city": "..."
        }
        """
        token = request.data.get('token')
        first_name = request.data.get('first_name')
        last_name = request.data.get('last_name')
        email = request.data.get('email', '').strip().lower()
        password = request.data.get('password')
        timezone_name = request.data.get('timezone', 'Europe/Moscow')
        city = request.data.get('city', '')

        if not all([token, first_name, last_name]):
            return Response({'error': 'Имя, фамилия и токен обязательны'}, status=status.HTTP_400_BAD_REQUEST)

        try:
            mentor = User.objects.get(invitation_link_token=token, role='mentor')
        except User.DoesNotExist:
            return Response({'error': 'Недействительная ссылка'}, status=status.HTTP_404_NOT_FOUND)

        # Если email указан, проверяем его уникальность
        if email:
            if User.objects.filter(email=email).exists():
                return Response({'error': 'Пользователь с таким email уже существует'}, status=status.HTTP_400_BAD_REQUEST)
        else:
            # Если email не указан, генерируем временный уникальный email
            email = f"student_{secrets.token_hex(8)}@platform.local"

        # Создаем пользователя
        temp_password = password or secrets.token_urlsafe(12)
        student_user = User.objects.create_user(
            email=email,
            password=temp_password,
            first_name=first_name,
            last_name=last_name,
            role='client',
            email_verified=True,  # Аккаунт по ссылке считается верифицированным
            timezone=timezone_name,
            city=city
        )
        
        # Генерируем персональный токен для входа
        student_user.login_token = secrets.token_urlsafe(32)
        student_user.save(update_fields=['login_token'])
        
        # Создаем профиль клиента
        client = Client.objects.create(user=student_user)
        
        # Создаем связь с ментором
        conn = MentorStudentConnection.objects.create(
            mentor=mentor,
            student=student_user,
            status=MentorStudentConnection.STATUS_ACCEPTED,
            initiator=MentorStudentConnection.INITIATOR_STUDENT,
            student_confirmed_at=timezone.now()
        )
        
        # Применяем связь (добавляем ментора к клиенту, создаем доску и т.д.)
        _apply_mentor_connection(conn)

        # Если был указан реальный email, но не указан пароль, отправляем пароль
        if email and not email.endswith('@platform.local') and not password:
            # Генерируем токен для сброса пароля, чтобы пользователь мог установить свой
            reset_token = secrets.token_urlsafe(32)
            student_user.email_verification_token = reset_token
            student_user.save(update_fields=['email_verification_token'])
            
            # Отправляем приветственное письмо
            send_student_welcome_email_task.delay(student_user.id, reset_token)

        # Генерируем JWT токены для автоматического входа
        from rest_framework_simplejwt.tokens import RefreshToken
        refresh = RefreshToken.for_user(student_user)
        
        # Обновляем из БД, чтобы в ответе был актуальный universal_code
        student_user.refresh_from_db()
        return Response({
            'refresh': str(refresh),
            'access': str(refresh.access_token),
            'user': UserSerializer(student_user, context={'request': request}).data,
            'message': 'Регистрация успешна'
        }, status=status.HTTP_201_CREATED)


class ParentManagementViewSet(viewsets.ViewSet):
    """
    ViewSet для управления родителями.
    
    add_child: Добавить ребенка
    remove_child: Удалить ребенка
    """
    
    permission_classes = [IsAuthenticated]
    
    @action(detail=False, methods=['post'])
    def add_child(self, request):
        """
        Добавить ребенка (для родителей).
        Создает нового пользователя если не существует (аналогично add_client для ментора).
        
        POST /api/users/manage/parents/add_child/
        Body: {
            "email": "...",
            "first_name": "...",
            "last_name": "...",
            "phone": "..." (optional),
            "grade": "..." (optional),
            "school": "..." (optional),
            "learning_goals": "..." (optional)
        }
        ИЛИ (для обратной совместимости):
        {
            "child_email": "..."
        }
        """
        user = request.user
        
        if user.role != 'parent':
            return Response(
                {'error': 'Только для родителей'},
                status=status.HTTP_403_FORBIDDEN
            )
        
        # Поддержка старого формата (child_email) и нового (email + данные)
        child_email = request.data.get('child_email') or request.data.get('email')
        
        if not child_email:
            return Response(
                {'error': 'Необходимо указать email ребенка'},
                status=status.HTTP_400_BAD_REQUEST
            )
        
        # Нормализуем email
        child_email = child_email.lower().strip()
        
        # Получаем или создаем профиль родителя
        parent, created = Parent.objects.get_or_create(user=user)
        
        # Ищем пользователя
        created = False
        try:
            child_user = User.objects.get(email=child_email)
            # Если пользователь существует, проверяем что это клиент
            if child_user.role != 'client':
                return Response(
                    {'error': 'Пользователь с таким email уже существует, но не является клиентом'},
                    status=status.HTTP_400_BAD_REQUEST
                )
        except User.DoesNotExist:
            created = True
            # Создаем нового пользователя-клиента
            first_name = request.data.get('first_name', '').strip()
            last_name = request.data.get('last_name', '').strip()
            phone = request.data.get('phone', '').strip()
            
            if not first_name or not last_name:
                return Response(
                    {'error': 'Для создания нового пользователя необходимо указать имя и фамилию'},
                    status=status.HTTP_400_BAD_REQUEST
                )
            
            # Создаем пользователя с временным паролем
            temp_password = secrets.token_urlsafe(12)
            
            child_user = User.objects.create_user(
                email=child_email,
                password=temp_password,
                first_name=first_name,
                last_name=last_name,
                phone=normalize_phone(phone) if phone else '',
                role='client',
                email_verified=True,  # Email автоматически подтвержден при добавлении родителем
            )
            
            # Генерируем токен для установки пароля
            reset_token = secrets.token_urlsafe(32)
            child_user.email_verification_token = reset_token
            child_user.save()
            
            # Отправляем приветственное письмо со ссылкой на установку пароля
            send_student_welcome_email_task.delay(child_user.id, reset_token)
        
        # Получаем или создаем профиль клиента
        if created:  # Если пользователь был только что создан
            child = Client.objects.create(
                user=child_user,
                grade=request.data.get('grade', ''),
                school=request.data.get('school', ''),
                learning_goals=request.data.get('learning_goals', ''),
            )
        else:
            child, _ = Client.objects.get_or_create(user=child_user)
            # Обновляем дополнительные поля если они переданы
            if 'grade' in request.data:
                child.grade = request.data.get('grade')
            if 'school' in request.data:
                child.school = request.data.get('school')
            if 'learning_goals' in request.data:
                child.learning_goals = request.data.get('learning_goals')
            if 'phone' in request.data and not child_user.phone:
                child_user.phone = normalize_phone(str(request.data.get('phone', '') or ''))
                child_user.save()
            child.save()
        
        # Добавляем ребенка (если еще не добавлен)
        if child not in parent.children.all():
            parent.children.add(child)
        
        # Возвращаем данные ребенка в формате ClientSerializer
        from .serializers import ClientSerializer
        child_serializer = ClientSerializer(child, context={'request': request})
        
        return Response({
            'id': str(child.id),
            'user': {
                'id': str(child_user.id),
                'email': child_user.email,
                'first_name': child_user.first_name,
                'last_name': child_user.last_name,
                'avatar': child_user.avatar.url if child_user.avatar else None,
                'avatar_url': request.build_absolute_uri(child_user.avatar.url) if child_user.avatar else None,
            },
            'grade': child.grade,
            'school': child.school,
            'learning_goals': child.learning_goals,
        })
    
    @action(detail=True, methods=['delete'])
    def remove_child(self, request, pk=None):
        """
        Удалить ребенка.
        
        DELETE /api/users/parents/{child_id}/remove_child/
        """
        user = request.user
        
        if user.role != 'parent':
            return Response(
                {'error': 'Только для родителей'},
                status=status.HTTP_403_FORBIDDEN
            )
        
        try:
            parent = Parent.objects.get(user=user)
        except Parent.DoesNotExist:
            return Response(
                {'error': 'Профиль родителя не найден'},
                status=status.HTTP_404_NOT_FOUND
            )
        
        try:
            child = Client.objects.get(id=pk)
        except Client.DoesNotExist:
            return Response(
                {'error': 'Клиент не найден'},
                status=status.HTTP_404_NOT_FOUND
            )
        
        # Удаляем ребенка
        parent.children.remove(child)
        
        return Response({'message': 'Ребенок успешно удален'})

