"""
Real-Time WebSocket Alerts System
==================================

Provides real-time notification via WebSocket for:
- Transaction monitoring
- Security alerts
- Entity changes
- New wallet discoveries
"""

import asyncio
import json
import logging
from collections import defaultdict
from datetime import datetime
from typing import Any

from fastapi import WebSocket, WebSocketDisconnect

logger = logging.getLogger(__name__)


# ─── REAL-TIME ALERT TYPES ──────────────────────────────────────────────


class AlertType:
    """Supported alert types."""

    NEW_TRANSACTION = "new_transaction"
    SECURITY_ALERT = "security_alert"
    ENTITY_CHANGE = "entity_change"
    NEW_WALLET = "new_wallet"
    CROSS_CHAIN = "cross_chain_movement"
    RATE_LIMIT = "rate_limit_exceeded"
    SYSTEM_STATUS = "system_status"


class AlertSeverity:
    """Alert severity levels."""

    INFO = "info"
    WARNING = "warning"
    HIGH = "high"
    CRITICAL = "critical"


class RealTimeAlert:
    """Represents a real-time alert."""

    def __init__(
        self,
        alert_type: str,
        message: str,
        severity: str,
        data: dict[str, Any] | None = None,
        chain: str = "ethereum",
        wallet_address: str = "",
        related_entities: list[str] | None = None,
    ):
        self.alert_type = alert_type
        self.message = message
        self.severity = severity
        self.data = data or {}
        self.chain = chain
        self.wallet_address = wallet_address
        self.related_entities = related_entities or []
        self.timestamp = datetime.utcnow().isoformat()
        self.id = f"{alert_type}_{datetime.utcnow().timestamp()}"

    def to_dict(self) -> dict[str, Any]:
        """Convert to dictionary."""
        return {
            "id": self.id,
            "type": self.alert_type,
            "message": self.message,
            "severity": self.severity,
            "chain": self.chain,
            "wallet_address": self.wallet_address,
            "related_entities": self.related_entities,
            "data": self.data,
            "timestamp": self.timestamp,
        }

    def to_json(self) -> str:
        """Convert to JSON string."""
        return json.dumps(self.to_dict())


# ─── ALERT BROADCASTER ────────────────────────────────────────────────


class AlertBroadcaster:
    """Broadcasts alerts to connected WebSocket clients."""

    def __init__(self):
        self.clients: dict[str, WebSocket] = {}
        self.subscriptions: dict[str, set[str]] = defaultdict(set)  # wallet -> client_id
        self.alert_queue: list[RealTimeAlert] = []
        self.max_queue_size = 1000

    async def connect(self, websocket: WebSocket, client_id: str | None = None):
        """Accept a WebSocket connection."""
        await websocket.accept()
        if not client_id:
            client_id = f"client_{id(websocket)}"
        self.clients[client_id] = websocket
        logger.info(f"WebSocket client connected: {client_id}")
        return client_id

    async def disconnect(self, client_id: str):
        """Remove a client."""
        if client_id in self.clients:
            del self.clients[client_id]
            # Remove subscriptions
            for wallet in self.subscriptions:
                self.subscriptions[wallet].discard(client_id)
            logger.info(f"WebSocket client disconnected: {client_id}")

    def subscribe(self, client_id: str, wallet_address: str):
        """Subscribe a client to wallet alerts."""
        self.subscriptions[wallet_address].add(client_id)

    def unsubscribe(self, client_id: str, wallet_address: str):
        """Unsubscribe a client from wallet alerts."""
        self.subscriptions[wallet_address].discard(client_id)

    async def broadcast(self, alert: RealTimeAlert):
        """Broadcast an alert to relevant clients."""
        self.alert_queue.append(alert)

        # Keep queue size manageable
        if len(self.alert_queue) > self.max_queue_size:
            self.alert_queue = self.alert_queue[-self.max_queue_size :]

        # Find interested clients
        interested_clients = set()
        interested_clients.update(self.clients.keys())  # Broadcast to all

        # Also add clients subscribed to related wallets
        if alert.wallet_address:
            interested_clients.update(self.subscriptions.get(alert.wallet_address, set()))

        if alert.related_entities:
            for entity in alert.related_entities:
                interested_clients.update(self.subscriptions.get(entity, set()))

        # Send to interested clients
        for client_id in list(interested_clients):
            if client_id in self.clients:
                try:
                    await self.clients[client_id].send_text(alert.to_json())
                except Exception as e:
                    logger.error(f"Failed to send alert to {client_id}: {e}")
                    await self.disconnect(client_id)

    async def broadcast_system_status(self, status: str, details: dict[str, Any] | None = None):
        """Broadcast system status update."""
        alert = RealTimeAlert(
            alert_type=AlertType.SYSTEM_STATUS,
            message=f"System {status}",
            severity=AlertSeverity.INFO,
            data=details or {},
        )
        await self.broadcast(alert)

    async def broadcast_security_alert(self, wallet: str, severity: str, message: str, chain: str = "ethereum"):
        """Broadcast a security alert."""
        alert = RealTimeAlert(
            alert_type=AlertType.SECURITY_ALERT,
            message=message,
            severity=severity,
            chain=chain,
            wallet_address=wallet,
            data={"severity": severity, "category": "security"},
        )
        await self.broadcast(alert)

    async def broadcast_transaction(self, wallet: str, tx_hash: str, chain: str = "ethereum"):
        """Broadcast a new transaction."""
        alert = RealTimeAlert(
            alert_type=AlertType.NEW_TRANSACTION,
            message=f"New transaction for {wallet[:20]}... on {chain}",
            severity=AlertSeverity.INFO,
            chain=chain,
            wallet_address=wallet,
            data={"tx_hash": tx_hash, "category": "transaction"},
        )
        await self.broadcast(alert)

    async def broadcast_cross_chain(self, from_chain: str, to_chain: str, wallet: str):
        """Broadcast cross-chain movement."""
        alert = RealTimeAlert(
            alert_type=AlertType.CROSS_CHAIN,
            message=f"Cross-chain movement: {from_chain} -> {to_chain}",
            severity=AlertSeverity.WARNING,
            chain=to_chain,
            wallet_address=wallet,
            data={"from_chain": from_chain, "to_chain": to_chain, "category": "cross_chain"},
        )
        await self.broadcast(alert)

    def get_alerts_by_wallet(self, wallet: str) -> list[dict[str, Any]]:
        """Get recent alerts for a specific wallet."""
        return [alert.to_dict() for alert in self.alert_queue if alert.wallet_address == wallet]

    def get_alerts_by_type(self, alert_type: str) -> list[dict[str, Any]]:
        """Get alerts of a specific type."""
        return [alert.to_dict() for alert in self.alert_queue if alert.alert_type == alert_type]


# ─── WEBSOCKET HANDLER ────────────────────────────────────────────────


class WebSocketManager:
    """Manages WebSocket connections and events."""

    def __init__(self):
        self.broadcaster = AlertBroadcaster()
        self.active_connections: dict[str, WebSocket] = {}
        self.connection_lock = asyncio.Lock()

    async def connect(self, websocket: WebSocket, client_id: str | None = None):
        """Connect a new WebSocket client."""
        async with self.connection_lock:
            client_id = await self.broadcaster.connect(websocket, client_id)
            self.active_connections[client_id] = websocket
            await websocket.send_json({"type": "connected", "client_id": client_id})

    async def disconnect(self, client_id: str):
        """Disconnect a WebSocket client."""
        async with self.connection_lock:
            await self.broadcaster.disconnect(client_id)
            if client_id in self.active_connections:
                del self.active_connections[client_id]

    async def receive_message(self, client_id: str, message: str):
        """Handle incoming message from client."""
        try:
            data = json.loads(message)
            msg_type = data.get("type")

            if msg_type == "subscribe":
                wallet = data.get("wallet")
                if wallet:
                    self.broadcaster.subscribe(client_id, wallet)
                    await self.active_connections[client_id].send_json({"type": "subscribed", "wallet": wallet})

            elif msg_type == "unsubscribe":
                wallet = data.get("wallet")
                if wallet:
                    self.broadcaster.unsubscribe(client_id, wallet)

            elif msg_type == "ping":
                await self.active_connections[client_id].send_json(
                    {"type": "pong", "timestamp": datetime.utcnow().isoformat()}
                )

            else:
                logger.warning(f"Unknown message type: {msg_type}")

        except json.JSONDecodeError:
            logger.error(f"Invalid JSON from {client_id}")

    async def broadcast(self, alert: RealTimeAlert):
        """Broadcast an alert to all connected clients."""
        await self.broadcaster.broadcast(alert)

    async def broadcast_security_alert(self, wallet: str, severity: str, message: str, chain: str = "ethereum"):
        """Broadcast a security alert."""
        await self.broadcaster.broadcast_security_alert(wallet, severity, message, chain)

    async def broadcast_transaction(self, wallet: str, tx_hash: str, chain: str = "ethereum"):
        """Broadcast a new transaction."""
        await self.broadcaster.broadcast_transaction(wallet, tx_hash, chain)

    async def broadcast_cross_chain(self, from_chain: str, to_chain: str, wallet: str):
        """Broadcast cross-chain movement."""
        await self.broadcaster.broadcast_cross_chain(from_chain, to_chain, wallet)


# ─── FastAPI Integration ──────────────────────────────────────────────

# Global instance
ws_manager = WebSocketManager()


async def start_websocket_handler():
    """Start the WebSocket manager."""
    logger.info("WebSocket manager started")
    return ws_manager


def get_websocket_manager() -> WebSocketManager:
    """Get the global WebSocket manager instance."""
    return ws_manager


async def websocket_alert_endpoint(websocket: WebSocket, client_id: str = "default"):
    """WebSocket endpoint for real-time alerts."""
    await ws_manager.connect(websocket, client_id)

    try:
        while True:
            data = await websocket.receive_text()
            await ws_manager.receive_message(client_id, data)
    except WebSocketDisconnect:
        await ws_manager.disconnect(client_id)
    except Exception as e:
        logger.error(f"WebSocket error: {e}")
        await ws_manager.disconnect(client_id)


# ─── TEST HELPER ──────────────────────────────────────────────────────


async def test_websocket_alerts():
    """Test the WebSocket alert system."""
    import asyncio

    async def mock_client():
        """Simulate a WebSocket client."""
        import websockets

        uri = "ws://localhost:8000/ws/alerts"

        async with websockets.connect(uri) as ws:
            # Subscribe to a wallet
            await ws.send(json.dumps({"type": "subscribe", "wallet": "0x1234..."}))
            response = await ws.recv()
            print(f"Subscribed: {response}")

            # Wait for alerts
            for _ in range(3):
                try:
                    alert = await asyncio.wait_for(ws.recv(), timeout=5)
                    print(f"Alert received: {alert}")
                except TimeoutError:
                    print("No alert received")

    # Run test
    await mock_client()


if __name__ == "__main__":
    import asyncio

    asyncio.run(test_websocket_alerts())
