"""
WebSocket consumer для реал-тайм синхронизации доски
"""
import json
import asyncio
import logging
from channels.generic.websocket import AsyncWebsocketConsumer
from channels.db import database_sync_to_async
from django.contrib.auth.models import AnonymousUser
from redis.exceptions import BusyLoadingError, ConnectionError as RedisConnectionError

logger = logging.getLogger(__name__)


class BoardConsumer(AsyncWebsocketConsumer):
    """Consumer для синхронизации доски между пользователями"""
    
    async def connect(self):
        """Подключение к WebSocket"""
        self.board_id = self.scope['url_route']['kwargs']['board_id']
        self.room_group_name = f'board_{self.board_id}'
        self.user = self.scope.get('user', AnonymousUser())
        
        # Логируем информацию о подключении для отладки
        query_string = self.scope.get('query_string', b'').decode()
        logger.info(f'[BoardConsumer] Попытка подключения: board_id={self.board_id}, user={self.user}, is_anonymous={self.user.is_anonymous}, query_string={query_string}')
        
        # Проверяем авторизацию
        if self.user.is_anonymous:
            logger.warning(f'[BoardConsumer] Отклонено: пользователь не авторизован для доски {self.board_id}')
            await self.close(code=4001)  # Unauthorized
            return
        
        # Присоединяемся к группе доски с обработкой ошибок Redis
        max_retries = 5
        retry_delay = 1
        
        for attempt in range(max_retries):
            try:
                await self.channel_layer.group_add(
                    self.room_group_name,
                    self.channel_name
                )
                break
            except (BusyLoadingError, RedisConnectionError) as e:
                if attempt < max_retries - 1:
                    logger.warning(f'Redis загружается, повторная попытка {attempt + 1}/{max_retries}: {e}')
                    await asyncio.sleep(retry_delay * (attempt + 1))
                else:
                    logger.error(f'Не удалось подключиться к Redis после {max_retries} попыток: {e}')
                    await self.close()
                    return
            except Exception as e:
                logger.error(f'Ошибка при подключении к channel layer: {e}', exc_info=True)
                await self.close()
                return
        
        await self.accept()
        
        logger.info(f'[BoardConsumer] Пользователь {self.user.email} подключился к доске {self.board_id}')
        
        # Отправляем всем что пользователь присоединился
        try:
            await self.channel_layer.group_send(
                self.room_group_name,
                {
                    'type': 'user_joined',
                    'user': {
                        'id': self.user.id,
                        'name': f'{self.user.first_name} {self.user.last_name}'.strip() or self.user.email,
                    }
                }
            )
        except (BusyLoadingError, RedisConnectionError) as e:
            logger.warning(f'Redis недоступен при отправке сообщения user_joined: {e}')
    
    async def disconnect(self, close_code):
        """Отключение от WebSocket"""
        if hasattr(self, 'room_group_name'):
            # Проверяем, что пользователь авторизован перед отправкой сообщений
            user = getattr(self, 'user', None)
            if user and not user.is_anonymous:
                try:
                    # Отправляем всем что пользователь отключился
                    await self.channel_layer.group_send(
                        self.room_group_name,
                        {
                            'type': 'user_left',
                            'user': {
                                'id': user.id,
                                'name': f'{user.first_name} {user.last_name}'.strip() or user.email,
                            }
                        }
                    )
                except (BusyLoadingError, RedisConnectionError) as e:
                    logger.warning(f'Redis недоступен при отправке сообщения user_left: {e}')
                except Exception as e:
                    logger.warning(f'Ошибка при отправке сообщения user_left: {e}')
            
            try:
                # Покидаем группу
                await self.channel_layer.group_discard(
                    self.room_group_name,
                    self.channel_name
                )
            except (BusyLoadingError, RedisConnectionError) as e:
                logger.warning(f'Redis недоступен при покидании группы: {e}')
            except Exception as e:
                logger.warning(f'Ошибка при покидании группы: {e}')
            
            # Логируем отключение с проверкой пользователя
            if user and not user.is_anonymous:
                logger.info(f'[BoardConsumer] Пользователь {user.email} отключился от доски {self.board_id}')
            else:
                logger.info(f'[BoardConsumer] Анонимный пользователь отключился от доски {self.board_id}')
    
    async def receive(self, text_data):
        """Получение сообщения от клиента"""
        # Проверяем, что пользователь авторизован
        if not hasattr(self, 'user') or not self.user or self.user.is_anonymous:
            logger.warning('[BoardConsumer] Попытка отправить сообщение неавторизованным пользователем')
            return
        
        try:
            data = json.loads(text_data)
            message_type = data.get('type')
            
            if message_type == 'draw':
                board_data = data.get('data', {})
                elements = board_data.get('elements', [])
                files = board_data.get('files', {})
                
                # Логируем размер данных
                size_kb = len(text_data) / 1024
                user_email = getattr(self.user, 'email', 'unknown')
                print(f'[BoardConsumer] Получено от {user_email}: {len(elements)} элементов, {len(files)} файлов, размер: {size_kb:.2f} KB')
                
                if len(files) > 0:
                    print(f'[BoardConsumer] Files: {list(files.keys())}')
                
                # Отправляем изменения всем кроме отправителя
                try:
                    await self.channel_layer.group_send(
                        self.room_group_name,
                        {
                            'type': 'board_update',
                            'data': board_data,
                            'user_id': self.user.id,
                        }
                    )
                except (BusyLoadingError, RedisConnectionError) as e:
                    logger.warning(f'Redis недоступен при отправке board_update: {e}')
            
            elif message_type == 'clear':
                # Очистка доски
                try:
                    await self.channel_layer.group_send(
                        self.room_group_name,
                        {
                            'type': 'board_clear',
                            'user_id': self.user.id,
                        }
                    )
                except (BusyLoadingError, RedisConnectionError) as e:
                    logger.warning(f'Redis недоступен при отправке board_clear: {e}')
            
            elif message_type == 'undo':
                # Отменить действие
                try:
                    await self.channel_layer.group_send(
                        self.room_group_name,
                        {
                            'type': 'board_undo',
                            'user_id': self.user.id,
                        }
                    )
                except (BusyLoadingError, RedisConnectionError) as e:
                    logger.warning(f'Redis недоступен при отправке board_undo: {e}')
        
        except json.JSONDecodeError:
            print('[BoardConsumer] Ошибка декодирования JSON')
    
    async def board_update(self, event):
        """Отправка обновления доски клиенту"""
        # Проверяем, что пользователь авторизован
        if not hasattr(self, 'user') or not self.user or self.user.is_anonymous:
            return
        
        # Не отправляем обратно отправителю
        if event.get('user_id') != self.user.id:
            board_data = event['data']
            elements = board_data.get('elements', [])
            files = board_data.get('files', {})
            
            message = json.dumps({
                'type': 'draw',
                'data': board_data,
            })
            
            size_kb = len(message) / 1024
            user_email = getattr(self.user, 'email', 'unknown')
            print(f'[BoardConsumer] Отправка {user_email}: {len(elements)} элементов, {len(files)} файлов, размер: {size_kb:.2f} KB')
            
            await self.send(text_data=message)
    
    async def board_clear(self, event):
        """Очистка доски"""
        if not hasattr(self, 'user') or not self.user or self.user.is_anonymous:
            return
        if event.get('user_id') != self.user.id:
            await self.send(text_data=json.dumps({
                'type': 'clear',
            }))
    
    async def board_undo(self, event):
        """Отменить действие"""
        if not hasattr(self, 'user') or not self.user or self.user.is_anonymous:
            return
        if event.get('user_id') != self.user.id:
            await self.send(text_data=json.dumps({
                'type': 'undo',
            }))
    
    async def user_joined(self, event):
        """Уведомление о присоединении пользователя"""
        if not hasattr(self, 'user') or not self.user or self.user.is_anonymous:
            return
        if event['user']['id'] != self.user.id:
            await self.send(text_data=json.dumps({
                'type': 'user_joined',
                'user': event['user'],
            }))
    
    async def user_left(self, event):
        """Уведомление об отключении пользователя"""
        if not hasattr(self, 'user') or not self.user or self.user.is_anonymous:
            return
        if event['user']['id'] != self.user.id:
            await self.send(text_data=json.dumps({
                'type': 'user_left',
                'user': event['user'],
            }))
