import asyncio
import json
import logging
from pathlib import Path

import websockets


class Engine:
    def __init__(self, config_path="config.json"):
        self.config = self._load_config(config_path)

        websocket_config = self.config["websocket"]

        self.url = websocket_config["url"]
        self.namespace = websocket_config["namespace"]

        self.data_dir = Path(
            self.config["capture"]["data_directory"]
        )
        self.data_dir.mkdir(parents=True, exist_ok=True)

        self.logger = logging.getLogger("Engine")

        self.websocket = None
        self.connected = False

    def _load_config(self, path):
        with open(path, "r", encoding="utf-8") as file:
            return json.load(file)

    async def connect(self):
        self.logger.info("Connecting to Crash WebSocket...")

        self.websocket = await websockets.connect(
            self.url,
            origin="https://bc.game",
            ping_interval=20,
            ping_timeout=20,
            max_size=None
        )

        self.connected = True

        self.logger.info("WebSocket connection established.")

        await self.initialize()

    async def initialize(self):
        if not self.websocket:
            return

        # Socket.IO namespace connection.
        namespace_packet = "40" + self.namespace

        await self.websocket.send(namespace_packet)

        self.logger.info(
            "Socket.IO namespace requested: %s",
            self.namespace
        )

    async def receive(self):
        if not self.websocket:
            return None

        try:
            message = await self.websocket.recv()
            return message

        except websockets.ConnectionClosed:
            self.connected = False
            self.logger.warning("WebSocket connection closed.")
            return None

    async def disconnect(self):
        if self.websocket:
            await self.websocket.close()

        self.websocket = None
        self.connected = False

        self.logger.info("WebSocket disconnected.")

    async def run(self, message_handler=None):
        while True:
            try:
                await self.connect()

                while self.connected:
                    message = await self.receive()

                    if message is None:
                        break

                    if message_handler:
                        await message_handler(message)

            except asyncio.CancelledError:
                await self.disconnect()
                raise

            except Exception as error:
                self.connected = False

                self.logger.error(
                    "Engine error: %s",
                    error
                )

            finally:
                await self.disconnect()

            await asyncio.sleep(5)
``