from __future__ import annotations

import asyncio
from collections import deque
from datetime import datetime, timezone
from typing import Any

from fastapi import WebSocket

from .models import ConnectedDevice, DeviceHello, Platform


def _utc_now_iso() -> str:
    return datetime.now(timezone.utc).isoformat()


class ConnectionManager:
    def __init__(self) -> None:
        self._lock = asyncio.Lock()
        self._sockets: dict[str, WebSocket] = {}
        self._meta: dict[str, ConnectedDevice] = {}
        self._events: deque[dict[str, Any]] = deque(maxlen=500)

    async def connect(self, hello: DeviceHello, websocket: WebSocket) -> None:
        now = _utc_now_iso()
        async with self._lock:
            self._sockets[hello.device_id] = websocket
            self._meta[hello.device_id] = ConnectedDevice(
                device_id=hello.device_id,
                platform=hello.platform,
                version=hello.version,
                hostname=hello.hostname,
                capabilities=hello.capabilities,
                connected_at=now,
                last_seen_at=now,
            )
            self._events.appendleft(
                {
                    "time": now,
                    "event": "device_connected",
                    "device_id": hello.device_id,
                    "platform": hello.platform.value,
                    "version": hello.version,
                }
            )

    async def disconnect(self, device_id: str) -> None:
        now = _utc_now_iso()
        async with self._lock:
            had = device_id in self._sockets
            self._sockets.pop(device_id, None)
            if device_id in self._meta:
                self._meta[device_id].last_seen_at = now
            if had:
                self._events.appendleft(
                    {
                        "time": now,
                        "event": "device_disconnected",
                        "device_id": device_id,
                    }
                )

    async def touch(self, device_id: str) -> None:
        async with self._lock:
            meta = self._meta.get(device_id)
            if meta:
                meta.last_seen_at = _utc_now_iso()

    async def list_devices(self) -> list[ConnectedDevice]:
        async with self._lock:
            return [self._meta[did] for did in self._sockets.keys() if did in self._meta]

    async def get_logs(self) -> list[dict[str, Any]]:
        async with self._lock:
            return list(self._events)

    async def add_event(self, payload: dict[str, Any]) -> None:
        async with self._lock:
            payload = dict(payload)
            payload.setdefault("time", _utc_now_iso())
            self._events.appendleft(payload)

    async def dispatch_command(
        self,
        command_id: str,
        action: str,
        data: dict[str, Any],
        target_scope: str,
        device_ids: list[str],
    ) -> tuple[list[str], list[str]]:
        async with self._lock:
            targets: list[tuple[str, WebSocket, ConnectedDevice]] = []
            skipped: list[str] = []

            for device_id, ws in self._sockets.items():
                meta = self._meta.get(device_id)
                if not meta:
                    continue

                allowed = False
                if target_scope == "all":
                    allowed = True
                elif target_scope == "windows":
                    allowed = meta.platform == Platform.windows
                elif target_scope == "pwa":
                    allowed = meta.platform == Platform.pwa
                elif target_scope == "web":
                    allowed = meta.platform == Platform.web
                elif target_scope == "device_ids":
                    allowed = device_id in device_ids

                if allowed:
                    targets.append((device_id, ws, meta))
                else:
                    skipped.append(device_id)

        delivered: list[str] = []
        for device_id, ws, _meta in targets:
            try:
                await ws.send_json(
                    {
                        "type": "command",
                        "id": command_id,
                        "action": action,
                        "data": data,
                        "sent_at": _utc_now_iso(),
                    }
                )
                delivered.append(device_id)
            except Exception:
                skipped.append(device_id)
                await self.disconnect(device_id)

        await self.add_event(
            {
                "event": "command_dispatched",
                "command_id": command_id,
                "action": action,
                "target_scope": target_scope,
                "delivered_count": len(delivered),
                "delivered_to": delivered,
            }
        )
        return delivered, skipped
