import copy
import datetime
import json
import threading
import time
from timeit import default_timer

from asgiref.sync import sync_to_async
from channels.generic.websocket import AsyncWebsocketConsumer

from .models import OngoingTimer

running_timer_threads = []


def tick_timer(timer, iteration: int, total_seconds: int) -> None:
    second_compensation = 0

    start_time = default_timer()

    timer.refresh_from_db()

    end_time = default_timer()
    second_compensation = end_time - start_time

    while not timer.is_running:
        time.sleep(0.05)

        timer.refresh_from_db()

        if iteration != timer.iteration:
            # Stop the thread if there is a new timer

            running_timer_threads.remove(timer.id)
            return

    start_time = default_timer()

    if iteration != timer.iteration:
        # Stop the thread if there is a new timer

        running_timer_threads.remove(timer.id)
        return

    if total_seconds < 1:
        return

    total_seconds -= 1

    seconds = total_seconds % 60
    minutes = round((total_seconds - seconds) / 60)

    timer.minutes = minutes
    timer.seconds = seconds

    timer.save()

    end_time = default_timer()
    second_compensation += end_time - start_time

    threading.Timer(
        interval=1 - second_compensation,
        function=tick_timer,
        args=(
            timer,
            iteration,
            total_seconds,
        ),
    ).start()


class TimerConsumer(AsyncWebsocketConsumer):
    async def connect(self) -> None:
        timer_id = self.scope["url_route"]["kwargs"]["id"]
        timer = await sync_to_async(OngoingTimer.objects.filter(id=timer_id).first)()

        if timer is None:
            # Not found

            await self.close()
            return

        self.timer = timer

        await self.channel_layer.group_add(str(self.timer.id), self.channel_name)
        await self.accept()

        if self.timer.is_running and self.timer.id not in running_timer_threads:
            await self.run_timer()

    async def disconnect(self, close_code) -> None:
        if hasattr(self, "timer"):
            await self.channel_layer.group_discard(
                str(self.timer.id), self.channel_name
            )

    async def run_timer(self, minutes: int = None, seconds: int = None) -> None:
        self.timer.iteration += 1
        await sync_to_async(self.timer.save)()

        while self.timer.id in running_timer_threads:
            # Wait until the thread is freed
            time.sleep(0.05)

        running_timer_threads.append(self.timer.id)

        total_seconds = (self.timer.minutes * 60) + self.timer.seconds

        time.sleep(1)

        threading.Thread(
            target=tick_timer,
            args=(
                self.timer,
                self.timer.iteration,
                total_seconds,
            ),
        ).start()

    async def receive(self, text_data: str) -> None:
        await sync_to_async(self.timer.refresh_from_db)()

        json_data = json.loads(text_data)

        response = {}

        reset_timer = False

        if "is_running" in json_data:
            if json_data["is_running"]:
                self.timer.is_running = True
                await sync_to_async(self.timer.save)()
            else:
                self.timer.is_running = False
                await sync_to_async(self.timer.save)()

        if "time" in json_data:
            self.timer.minutes = json_data["time"]["minutes"]
            self.timer.seconds = json_data["time"]["seconds"]

            reset_timer = True

        response.update(
            {
                "sync_time": {
                    "minutes": self.timer.minutes,
                    "seconds": self.timer.seconds,
                },
                "is_running": self.timer.is_running,
            }
        )

        if reset_timer:
            await self.run_timer()

        await self.channel_layer.group_send(
            str(self.timer.id), {"type": "timer_message", "text": json.dumps(response)}
        )

    async def timer_message(self, event: dict) -> None:
        await self.send(text_data=event["text"])