import json
import logging
from channels.generic.websocket import AsyncWebsocketConsumer
from channels.db import database_sync_to_async
from django.contrib.auth import get_user_model
from django.core.exceptions import ObjectDoesNotExist
from .models import Conversation, Message

# Initialize logger
logger = logging.getLogger(__name__)

User = get_user_model()


class ChatConsumer(AsyncWebsocketConsumer):
    async def connect(self):
        self.user = self.scope.get('user')
        if not self.user.is_authenticated:
            logger.warning("Unauthenticated user attempted to connect.")
            await self.close()
            return

        self.conversation_groups = set()
        try:
            await self.accept()
            await self.send_initial_state()
            logger.info(f"User {self.user} connected and initial state sent.")
        except Exception as e:
            logger.exception("Error during WebSocket connection setup.")
            await self.close()

    async def disconnect(self, close_code):
        try:
            for group_name in self.conversation_groups:
                await self.channel_layer.group_discard(group_name, self.channel_name)
            logger.info(f"User {self.user} disconnected and left groups: {self.conversation_groups}")
        except Exception as e:
            logger.exception("Error during WebSocket disconnection.")

    # Inside the ChatConsumer class

    async def receive(self, text_data):
        try:
            data = json.loads(text_data)
            action = data.get('action')
            if not action:
                logger.warning("No action specified in received data.")
                await self.send_error("No action specified.")
                return

            # Dispatch to appropriate handler based on action
            handler = getattr(self, f"handle_{action}", None)
            if handler:
                await handler(data)
            else:
                logger.warning(f"Unknown action received: {action}")
                await self.send_error(f"Unknown action: {action}")
        except json.JSONDecodeError:
            logger.error("Received invalid JSON.")
            await self.send_error("Invalid JSON format.")
        except Exception as e:
            logger.exception("Error processing received message.")
            await self.send_error("Internal server error.")

    async def send_initial_state(self):
        try:
            operator_conversations, operator_list = await self.get_operator_conversations()
            unpicked_conversations, unpicked_list = await self.get_unpicked_conversations()

            # Add groups for operator conversations
            for key in operator_list:
                group_name = f"conversation_{key}"
                await self.channel_layer.group_add(group_name, self.channel_name)
                self.conversation_groups.add(group_name)

            # Add groups for unpicked conversations
            for key in unpicked_list:
                group_name = f"unpicked_{key}"
                await self.channel_layer.group_add(group_name, self.channel_name)
                self.conversation_groups.add(group_name)

            # Send initial state to the client
            await self.send(text_data=json.dumps({
                'action': 'initial_state',
                'operator_conversations': operator_conversations,
                'unpicked_conversations': unpicked_conversations,
            }))
            logger.debug(f"Initial state sent to user {self.user}.")
        except Exception as e:
            logger.exception("Error sending initial state.")
            await self.send_error("Failed to send initial state.")

    @database_sync_to_async
    def get_operator_conversations(self):
        try:
            conversations = Conversation.objects.filter(operator=self.user, is_active=True)
            list_id = [str(c.key) for c in conversations]
            chats = [{
                'key': str(c.key),
                'unread_count': c.unread_count,
                'user': str(c.user)
            } for c in conversations]
            return {"chats": chats}, list_id
        except Exception as e:
            logger.exception("Error fetching operator conversations.")
            return {"chats": []}, []

    @database_sync_to_async
    def get_unpicked_conversations(self):
        try:
            conversations = Conversation.objects.filter(operator__isnull=True, is_active=True)
            list_id = [str(c.key) for c in conversations]
            chats = [{
                'key': str(c.key),
                'unread_count': c.unread_count,
                'user': str(c.user)
            } for c in conversations]
            return {"chats": chats}, list_id
        except Exception as e:
            logger.exception("Error fetching unpicked conversations.")
            return {"chats": []}, []

    async def handle_pick_conversation(self, data):
        conversation_key = data.get('conversation_key')
        if not conversation_key:
            logger.warning("pick_conversation action missing 'conversation_key'.")
            await self.send_error("Missing 'conversation_key'.")
            return

        try:
            success = await self.assign_conversation(conversation_key)
            if success:
                # Update group memberships
                await self.channel_layer.group_discard(f"unpicked_{conversation_key}", self.channel_name)
                group_name = f"chat_{conversation_key}"
                await self.channel_layer.group_add(group_name, self.channel_name)
                self.conversation_groups.add(group_name)

                # Notify other consumers about the picked conversation
                await self.channel_layer.group_send('unpicked_conversations', {
                    'type': 'conversation_picked',
                    'conversation_id': conversation_key,
                })

                # Send confirmation to the client
                await self.send(text_data=json.dumps({
                    'action': 'conversation_picked',
                    'conversation_id': conversation_key,
                }))
                logger.info(f"User {self.user} picked conversation {conversation_key}.")
            else:
                await self.send_error("Failed to pick conversation. It might have been picked by someone else.")
        except Exception as e:
            logger.exception("Error handling pick_conversation.")
            await self.send_error("Internal server error during pick_conversation.")

    @database_sync_to_async
    def assign_conversation(self, conversation_key):
        try:
            conversation = Conversation.objects.select_for_update().get(key=conversation_key)
            if conversation.operator is None and conversation.is_active:
                conversation.operator = self.user
                conversation.save()
                return True
            else:
                logger.warning(f"Conversation {conversation_key} is already assigned or inactive.")
                return False
        except ObjectDoesNotExist:
            logger.error(f"Conversation {conversation_key} does not exist.")
            return False
        except Exception as e:
            logger.exception("Error assigning conversation.")
            return False

    async def handle_end_conversation(self, data):
        conversation_key = data.get('conversation_key')
        if not conversation_key:
            logger.warning("end_conversation action missing 'conversation_key'.")
            await self.send_error("Missing 'conversation_key'.")
            return

        try:
            success = await self.end_conversation_db(conversation_key)
            if success:
                group_name = f"chat_{conversation_key}"
                await self.channel_layer.group_discard(group_name, self.channel_name)
                self.conversation_groups.discard(group_name)

                # Notify client about the ended conversation
                await self.send(text_data=json.dumps({
                    'action': 'conversation_ended',
                    'conversation_id': conversation_key,
                }))
                logger.info(f"User {self.user} ended conversation {conversation_key}.")
            else:
                await self.send_error("Failed to end conversation.")
        except Exception as e:
            logger.exception("Error handling end_conversation.")
            await self.send_error("Internal server error during end_conversation.")

    @database_sync_to_async
    def end_conversation_db(self, conversation_key):
        try:
            conversation = Conversation.objects.get(key=conversation_key)
            if conversation.operator == self.user and conversation.is_active:
                conversation.is_active = False
                conversation.save()
                return True
            else:
                logger.warning(f"User {self.user} attempted to end a conversation they don't own or inactive.")
                return False
        except ObjectDoesNotExist:
            logger.error(f"Conversation {conversation_key} does not exist.")
            return False
        except Exception as e:
            logger.exception("Error ending conversation.")
            return False

    async def handle_send_message(self, data):
        # Placeholder for message sending functionality
        # Implement as needed
        pass

    async def conversation_picked(self, event):
        try:
            await self.send(text_data=json.dumps({
                'action': 'conversation_picked',
                'conversation_id': event['conversation_id'],
            }))
            logger.debug(f"Notified user {self.user} about conversation picked: {event['conversation_id']}.")
        except Exception as e:
            logger.exception("Error sending conversation_picked event.")

    async def send_error(self, message):
        await self.send(text_data=json.dumps({
            'action': 'error',
            'message': message,
        }))

    # Inside the ChatConsumer class

    async def handle_get_messages(self, data):
        conversation_key = data.get('conversation_key')
        message_id = data.get('message_id')  # Optional

        if not conversation_key:
            logger.warning("get_messages action missing 'conversation_key'.")
            await self.send_error("Missing 'conversation_key'.")
            return

        try:
            is_in_conversation = await self.is_user_in_conversation(conversation_key)
            if not is_in_conversation:
                logger.warning(
                    f"User {self.user} attempted to access conversation {conversation_key} without permission.")
                await self.send_error("You do not have permission to access this conversation.")
                return

            if message_id:
                messages = await self.get_previous_messages(conversation_key, message_id)
                logger.info(f"Fetched messages before ID {message_id} for conversation {conversation_key}.")
            else:
                messages = await self.get_last_messages(conversation_key)
                logger.info(f"Fetched last messages for conversation {conversation_key}.")

            await self.send(text_data=json.dumps({
                'action': 'messages',
                'conversation_key': conversation_key,
                'messages': messages,
            }))
        except Exception as e:
            logger.exception("Error handling get_messages.")
            await self.send_error("Internal server error during get_messages.")

    @database_sync_to_async
    def get_last_messages(self, conversation_key, limit=15):
        try:
            conversation = Conversation.objects.get(key=conversation_key)
            messages = Message.objects.filter(conversation=conversation).order_by('-timestamp')[:limit]
            # Reverse to have chronological order
            messages = reversed(messages)
            return [self.serialize_message(msg) for msg in messages]
        except Conversation.DoesNotExist:
            logger.error(f"Conversation {conversation_key} does not exist.")
            return []
        except Exception as e:
            logger.exception("Error fetching last messages.")
            return []

    @database_sync_to_async
    def get_previous_messages(self, conversation_key, message_id, limit=15):
        try:
            conversation = Conversation.objects.get(key=conversation_key)
            reference_message = Message.objects.get(id=message_id, conversation=conversation)
            messages = Message.objects.filter(
                conversation=conversation,
                timestamp__lt=reference_message.timestamp
            ).order_by('-timestamp')[:limit]
            # Reverse to have chronological order
            messages = reversed(messages)
            return [self.serialize_message(msg) for msg in messages]
        except Conversation.DoesNotExist:
            logger.error(f"Conversation {conversation_key} does not exist.")
            return []
        except Message.DoesNotExist:
            logger.error(f"Message ID {message_id} does not exist in conversation {conversation_key}.")
            return []
        except Exception as e:
            logger.exception("Error fetching previous messages.")
            return []

    def serialize_message(self, msg):
        return {
            'id': msg.id,
            'sender': str(msg.sender),
            'text': msg.text,
            'timestamp': msg.timestamp.isoformat(),
        }

    @database_sync_to_async
    def is_user_in_conversation(self, conversation_key):
        try:
            conversation = Conversation.objects.get(key=conversation_key)
            return self.user == conversation.operator or conversation.operator is None
        except Conversation.DoesNotExist:
            return False
okey now give me a bootstrap and html css js file for front of this page
in this page at first in left sidebar user can see his active conversations and list conversations without operator and click on them

after click user can see messages in content area (now show only a test fake small chat)
if it was a conversation without operator user can't send message before pick it
if he pick it after that he can see input and send message
in content area in top left we have to see user1 name and profile pic in circle
and in left if it was a picked conversation by this user he have a red button for terminate chat
add a error handler with tostify to show to user error returnd from server

don't explaine just give me a perfect code