import asyncio
import logging
from datetime import datetime

from snek.model.user import UserModel
from snek.system.service import BaseService

logger = logging.getLogger(__name__)
from snek.system.model import now


class SocketService(BaseService):

    class Socket:
        def __init__(self, ws, user: UserModel):
            self.ws = ws
            self.is_connected = True
            self.user = user

        async def send_json(self, data):
            if not self.is_connected:
                return False
            try:
                await self.ws.send_json(data)
            except Exception:
                self.is_connected = False
            return self.is_connected

        async def close(self):
            if not self.is_connected:
                return True

            await self.ws.close()
            self.is_connected = False

            return True

    def __init__(self, app):
        super().__init__(app)
        self.sockets = set()
        self.users = {}
        self.subscriptions = {}
        self.last_update = str(datetime.now())

    async def user_availability_service(self):
        logger.info("User availability update service started.")
        while True:
            logger.info("Updating user availability...")
            users_updated = []
            for s in self.sockets:
                if not s.is_connected:
                    continue
                if s.user not in users_updated:
                    s.user["last_ping"] = now()
                    await self.app.services.user.save(s.user)
                    users_updated.append(s.user)
            logger.info(
                f"Updated user availability for {len(users_updated)} online users."
            )
            await asyncio.sleep(60)

    async def add(self, ws, user_uid):
        s = self.Socket(ws, await self.app.services.user.get(uid=user_uid))
        self.sockets.add(s)
        s.user["last_ping"] = now()
        await self.app.services.user.save(s.user)
        logger.info(f"Added socket for user {s.user['username']}")
        if not self.users.get(user_uid):
            self.users[user_uid] = set()
        self.users[user_uid].add(s)

    async def subscribe(self, ws, channel_uid, user_uid):
        if channel_uid not in self.subscriptions:
            self.subscriptions[channel_uid] = set()
        s = self.Socket(ws, await self.app.services.user.get(uid=user_uid))
        self.subscriptions[channel_uid].add(s)

    async def send_to_user(self, user_uid, message):
        count = 0
        for s in self.users.get(user_uid, []):
            if await s.send_json(message):
                count += 1
        return count

    async def broadcast(self, channel_uid, message):
        await self._broadcast(channel_uid, message)

    async def _broadcast(self, channel_uid, message):
        sent = 0
        try:
            async for user_uid in self.services.channel_member.get_user_uids(
                channel_uid
            ):
                sent += await self.send_to_user(user_uid, message)
        except Exception as ex:
            print(ex, flush=True)
        logger.info(f"Broadcasted a message to {sent} users.")
        return True

    async def delete(self, ws):
        for s in [sock for sock in self.sockets if sock.ws == ws]:
            await s.close()
            logger.info(f"Removed socket for user {s.user['username']}")
            self.sockets.remove(s)
