import json
import asyncio
from channels.generic.websocket import AsyncWebsocketConsumer
from channels.db import database_sync_to_async
from django.utils import timezone
from datetime import timedelta
from django.urls import reverse
from .models import QuizGame, QuizGameParticipant

class GameConsumer(AsyncWebsocketConsumer):
    async def connect(self):
        self.join_code = self.scope['url_route']['kwargs']['join_code']
        self.game_group_name = f'game_{self.join_code}'

        # Join game group
        await self.channel_layer.group_add(
            self.game_group_name,
            self.channel_name
        )

        await self.accept()

    async def disconnect(self, close_code):
        # Leave game group
        await self.channel_layer.group_discard(
            self.game_group_name,
            self.channel_name
        )

    async def receive(self, text_data):
        text_data_json = json.loads(text_data)
        message_type = text_data_json['type']

        if message_type == 'submit_answer':
            participant_id = text_data_json['participant_id']
            answer = text_data_json['answer']
            time_remaining = text_data_json.get('time_remaining', 0)
            
            # Save answer and update participant score
            await self.save_answer(participant_id, answer, time_remaining)
            
            # Notify host about the new answer
            await self.channel_layer.group_send(
                self.game_group_name,
                {
                    'type': 'participant_answer',
                    'answer': answer
                }
            )

        elif message_type == 'get_answer_stats':
            stats = await self.get_answer_stats()
            await self.send(text_data=json.dumps({
                'type': 'answer_stats',
                'stats': stats
            }))

        elif message_type == 'update_participants':
            participants = await self.get_participants()
            await self.channel_layer.group_send(
                self.game_group_name,
                {
                    'type': 'participant_list_update',
                    'participants': participants
                }
            )

        elif message_type == 'next_question':
            host_id = text_data_json['host_id']
            if await self.verify_host(host_id):
                await self.advance_to_next_question()

        elif message_type == 'finish_game':
            host_id = text_data_json['host_id']
            if await self.verify_host(host_id):
                await self.finish_game()

        elif message_type == 'advance_to_scores':
            host_id = text_data_json['host_id']
            if await self.verify_host(host_id):
                await self.show_scores()

    async def participant_answer(self, event):
        await self.send(text_data=json.dumps({
            'type': 'participant_answer',
            'answer': event['answer']
        }))

    async def participant_list_update(self, event):
        await self.send(text_data=json.dumps({
            'type': 'participant_list_update',
            'participants': event['participants']
        }))

    async def game_state_update(self, event):
        await self.send(text_data=json.dumps({
            'type': 'game_state_update',
            'action': event['action'],
            'redirect_url': event.get('redirect_url')
        }))

    @database_sync_to_async
    def verify_host(self, host_id):
        try:
            quiz_game = QuizGame.objects.get(join_code=self.join_code)
            return quiz_game.host_id == host_id
        except QuizGame.DoesNotExist:
            return False

    @database_sync_to_async
    def save_answer(self, participant_id, answer_index, time_remaining):
        try:
            participant = QuizGameParticipant.objects.get(
                quiz_game__join_code=self.join_code,
                participant_id=participant_id
            )
            quiz_game = participant.quiz_game
            current_question = quiz_game.quiz_id.questions.all()[quiz_game.current_question_index]
            
            # Check if answer is correct
            is_correct = False
            if answer_index >= 0:  # -1 means timeout
                is_correct = current_question.data['options'][answer_index]['is_correct']
            
            # Calculate score based on correctness and time
            score = 0
            if is_correct:
                base_score = 1000
                time_factor = time_remaining / 30000  # 30 seconds max
                score = int(base_score * (0.5 + 0.5 * time_factor))
            
            participant.last_score=score
            participant.score += score
            participant.last_answer_correct = is_correct
            participant.save()
            
            return True
        except (QuizGameParticipant.DoesNotExist, IndexError):
            return False

    @database_sync_to_async
    def get_answer_stats(self):
        try:
            quiz_game = QuizGame.objects.get(join_code=self.join_code)
            participants = QuizGameParticipant.objects.filter(quiz_game=quiz_game)
            
            # Count answers for each option
            stats = {}
            for participant in participants:
                if participant.last_answer is not None:
                    stats[participant.last_answer] = stats.get(participant.last_answer, 0) + 1
            
            return stats
        except QuizGame.DoesNotExist:
            return {}

    @database_sync_to_async
    def get_participants(self):
        try:
            quiz_game = QuizGame.objects.get(join_code=self.join_code)
            participants = QuizGameParticipant.objects.filter(quiz_game=quiz_game)
            return [
                {
                    'id': p.participant_id,
                    'display_name': p.display_name,
                    'score': p.score
                }
                for p in participants
            ]
        except QuizGame.DoesNotExist:
            return []

    @database_sync_to_async
    def _advance_to_next_question(self):
        try:
            quiz_game = QuizGame.objects.get(join_code=self.join_code)
            quiz = quiz_game.quiz_id
            
            # Move to next question
            quiz_game.current_question_index += 1
            
            # Check if we've reached the end
            if quiz_game.current_question_index >= quiz.questions.count():
                quiz_game.current_state = 'finished'
                quiz_game.save()
                return 'finished'
            else:
                # Update game state and start time
                quiz_game.current_state = 'question'
                quiz_game.question_start_time = timezone.now()
                quiz_game.save()
                return 'question'
        except QuizGame.DoesNotExist:
            return None

    async def advance_to_next_question(self):
        result = await self._advance_to_next_question()
        if result == 'finished':
            # Notify clients to redirect to finished page
            await self.channel_layer.group_send(
                self.game_group_name,
                {
                    'type': 'game_state_update',
                    'action': 'finish_game',
                    'redirect_url': reverse('play:finished', kwargs={'join_code': self.join_code})
                }
            )
        elif result == 'question':
            # Notify clients to redirect to next question
            await self.channel_layer.group_send(
                self.game_group_name,
                {
                    'type': 'game_state_update',
                    'action': 'next_question',
                    'redirect_url': reverse('play:question', kwargs={'join_code': self.join_code})
                }
            )

    @database_sync_to_async
    def _show_scores(self):
        try:
            quiz_game = QuizGame.objects.get(join_code=self.join_code)
            quiz_game.current_state = 'scores'
            quiz_game.save()
            return True
        except QuizGame.DoesNotExist:
            return False

    async def show_scores(self):
        success = await self._show_scores()
        if success:
            # Notify clients to redirect to scores page
            await self.channel_layer.group_send(
                self.game_group_name,
                {
                    'type': 'game_state_update',
                    'action': 'show_scores',
                    'redirect_url': reverse('play:scores', kwargs={'join_code': self.join_code})
                }
            )
    
    @database_sync_to_async
    def _finish_game(self):
        try:
            quiz_game = QuizGame.objects.get(join_code=self.join_code)
            quiz_game.current_state = 'finished'
            quiz_game.save()
            return True
        except QuizGame.DoesNotExist:
            return False

    async def finish_game(self):
        success = await self._finish_game()
        if success:
            # Notify clients to redirect to finished page
            await self.channel_layer.group_send(
                self.game_group_name,
                {
                    'type': 'game_state_update',
                    'action': 'finish_game',
                    'redirect_url': reverse('play:finished', kwargs={'join_code': self.join_code})
                }
            )

class LobbyConsumer(AsyncWebsocketConsumer):
    async def connect(self):
        self.join_code = self.scope['url_route']['kwargs']['join_code']
        self.room_group_name = f'game_{self.join_code}'
        self.heartbeat_task = None

        # Join room group
        await self.channel_layer.group_add(
            self.room_group_name,
            self.channel_name
        )
        await self.accept()

        # Start heartbeat check
        self.heartbeat_task = asyncio.create_task(self.check_participants_heartbeat())
        
        # Send current participants list
        participants = await self.get_participants()
        await self.send(text_data=json.dumps({
            'type': 'participant_list',
            'participants': participants
        }))

    async def disconnect(self, close_code):
        # Cancel heartbeat task
        if self.heartbeat_task:
            self.heartbeat_task.cancel()
            try:
                await self.heartbeat_task
            except asyncio.CancelledError:
                pass

        # Leave room group
        await self.channel_layer.group_discard(
            self.room_group_name,
            self.channel_name
        )

    async def receive(self, text_data):
        text_data_json = json.loads(text_data)
        message_type = text_data_json['type']

        if message_type == 'heartbeat':
            # Update participant's last activity timestamp
            participant_id = text_data_json.get('participant_id')
            if participant_id:
                await self._update_participant_heartbeat(participant_id)

        elif message_type == 'update_participants':
            participants = await self.get_participants()
            await self.channel_layer.group_send(
                self.room_group_name,
                {
                    'type': 'participant_list_update',
                    'participants': participants
                }
            )
            
        elif message_type == 'start_game':
            # Verify sender is host
            sender_id = text_data_json.get('host_id')
            if await self._is_host(sender_id):
                # Update game state and notify all participants
                await self._start_game()
                await self.channel_layer.group_send(
                    self.room_group_name,
                    {
                        'type': 'game_state_update',
                        'action': 'start_game',
                        'redirect_url': f'/play/game/{self.join_code}/0'
                    }
                )

    async def game_state_update(self, event):
        # Send game state update to WebSocket
        await self.send(text_data=json.dumps({
            'type': 'game_state_update',
            'action': event['action'],
            'redirect_url': event.get('redirect_url')
        }))

    @database_sync_to_async
    def _start_game(self):
        game = QuizGame.objects.get(join_code=self.join_code)
        game.current_state = 'question'
        game.current_question_index = 0
        game.question_start_time = timezone.now()
        game.save()
                
    async def receive(self, text_data):
        text_data_json = json.loads(text_data)
        message_type = text_data_json['type']

        if message_type == 'heartbeat':
            # Update participant's last activity timestamp
            participant_id = text_data_json.get('participant_id')
            if participant_id:
                await self._update_participant_heartbeat(participant_id)

        elif message_type == 'update_participants':
            participants = await self.get_participants()
            await self.channel_layer.group_send(
                self.room_group_name,
                {
                    'type': 'participant_list_update',
                    'participants': participants
                }
            )
            
        elif message_type == 'start_game':
            # Verify sender is host
            sender_id = text_data_json.get('host_id')
            if await self._is_host(sender_id):
                # Update game state and notify all participants
                await self._start_game()
                await self.channel_layer.group_send(
                    self.room_group_name,
                    {
                        'type': 'game_state_update',
                        'action': 'start_game',
                        'redirect_url': f'/play/game/{self.join_code}/0'
                    }
                )
                
        elif message_type == 'submit_answer':
            participant_id = text_data_json.get('participant_id')
            answer = text_data_json.get('answer')
            time_remaining = text_data_json.get('time_remaining')
            if participant_id:
                score = await self._process_answer(participant_id, answer, time_remaining)
                await self.send(text_data=json.dumps({
                    'type': 'answer_processed',
                    'score': score
                }))
                
        elif message_type == 'next_question':
            sender_id = text_data_json.get('host_id')
            if await self._is_host(sender_id):
                next_state = await self._advance_game_state()
                await self.channel_layer.group_send(
                    self.room_group_name,
                    {
                        'type': 'game_state_update',
                        'action': next_state['action'],
                        'redirect_url': next_state['redirect_url']
                    }
                )

    async def participant_list_update(self, event):
        participants = event['participants']
        await self.send(text_data=json.dumps({
            'type': 'participant_list',
            'participants': participants
        }))

    @database_sync_to_async
    def get_participants(self):
        game = QuizGame.objects.get(join_code=self.join_code)
        participants = QuizGameParticipant.objects.filter(quiz_game=game)
        return [{'id': str(p.participant_id), 'name': p.display_name} for p in participants]

    @database_sync_to_async
    def _update_participant_heartbeat(self, participant_id):
        try:
            participant = QuizGameParticipant.objects.get(participant_id=participant_id)
            participant.save()  # This will update last_heartbeat due to auto_now=True
        except QuizGameParticipant.DoesNotExist:
            pass
            
    @database_sync_to_async
    def _is_host(self, host_id):
        try:
            return QuizGame.objects.filter(join_code=self.join_code, host_id=host_id).exists()
        except QuizGame.DoesNotExist:
            return False
            
    @database_sync_to_async
    def _start_game(self):
        game = QuizGame.objects.get(join_code=self.join_code)
        game.current_state = 'question'
        game.current_question_index = 0
        game.question_start_time = timezone.now()
        game.save()
        
    @database_sync_to_async
    def _process_answer(self, participant_id, answer, time_remaining):
        game = QuizGame.objects.get(join_code=self.join_code)
        participant = QuizGameParticipant.objects.get(participant_id=participant_id)
        question = game.quiz_id.questions.all()[game.current_question_index]
        
        # Load question data
        question_data = json.loads(question.data)
        correct_answer = question_data.get('correct_answer')
        
        # Calculate score based on correctness and time
        score = 0
        if answer == correct_answer:
            # Base score for correct answer + bonus for speed
            score = 1000 + int(time_remaining * 10)  # 10 points per remaining second
        participant.last_score= score 
        participant.score += score
        participant.save()
        return score
        
    @database_sync_to_async
    def _advance_game_state(self):
        game = QuizGame.objects.get(join_code=self.join_code)
        total_questions = game.quiz_id.questions.count()
        
        if game.current_state == 'question':
            game.current_state = 'scores'
            game.save()
            return {
                'action': 'show_scores',
                'redirect_url': f'/play/game/{self.join_code}/scores'
            }
        elif game.current_state == 'scores':
            if game.current_question_index + 1 < total_questions:
                game.current_state = 'question'
                game.current_question_index += 1
                game.question_start_time = timezone.now()
                game.save()
                return {
                    'action': 'next_question',
                    'redirect_url': f'/play/game/{self.join_code}/{game.current_question_index}'
                }
            else:
                game.current_state = 'finished'
                game.save()
                return {
                    'action': 'game_finished',
                    'redirect_url': f'/play/game/{self.join_code}/finished'
                }

    @database_sync_to_async
    def _remove_inactive_participants(self):
        # Remove participants who haven't sent a heartbeat in the last 30 seconds
        timeout = timezone.now() - timedelta(seconds=30)
        quiz_game = QuizGame.objects.get(join_code=self.join_code)
        QuizGameParticipant.objects.filter(
            quiz_game=quiz_game,
            last_heartbeat__lt=timeout
        ).delete()

    async def check_participants_heartbeat(self):
        while True:
            try:
                await asyncio.sleep(10)  # Check every 10 seconds
                await self._remove_inactive_participants()
                
                # Send updated participant list
                participants = await self.get_participants()
                await self.channel_layer.group_send(
                    self.room_group_name,
                    {
                        'type': 'participant_list_update',
                        'participants': participants
                    }
                )
            except asyncio.CancelledError:
                break
            except Exception as e:
                print(f'Error in heartbeat check: {e}')
                await asyncio.sleep(10)  # Wait before retrying
