"""Service implementations for AdGuard Control Hub integration.""" import asyncio import logging from typing import Any, Dict import voluptuous as vol from homeassistant.core import HomeAssistant, ServiceCall from homeassistant.helpers import config_validation as cv from .api import AdGuardHomeAPI from .const import ( DOMAIN, BLOCKED_SERVICES, ATTR_CLIENT_NAME, ATTR_SERVICES, ATTR_DURATION, ATTR_CLIENTS, ) _LOGGER = logging.getLogger(__name__) # Service schemas SCHEMA_BLOCK_SERVICES = vol.Schema({ vol.Required(ATTR_CLIENT_NAME): cv.string, vol.Required(ATTR_SERVICES): vol.All(cv.ensure_list, [vol.In(BLOCKED_SERVICES.keys())]), }) SCHEMA_EMERGENCY_UNBLOCK = vol.Schema({ vol.Required(ATTR_DURATION): cv.positive_int, vol.Optional(ATTR_CLIENTS, default=["all"]): vol.All(cv.ensure_list, [cv.string]), }) SERVICE_BLOCK_SERVICES = "block_services" SERVICE_UNBLOCK_SERVICES = "unblock_services" SERVICE_EMERGENCY_UNBLOCK = "emergency_unblock" SERVICE_ADD_CLIENT = "add_client" SERVICE_REMOVE_CLIENT = "remove_client" SERVICE_BULK_UPDATE_CLIENTS = "bulk_update_clients" class AdGuardControlHubServices: """Handle services for AdGuard Control Hub.""" def __init__(self, hass: HomeAssistant): """Initialize the services.""" self.hass = hass self._emergency_unblock_tasks: Dict[str, asyncio.Task] = {} def register_services(self) -> None: """Register all services.""" self.hass.services.register( DOMAIN, SERVICE_BLOCK_SERVICES, self.block_services, schema=SCHEMA_BLOCK_SERVICES, ) self.hass.services.register( DOMAIN, SERVICE_UNBLOCK_SERVICES, self.unblock_services, schema=SCHEMA_BLOCK_SERVICES, ) self.hass.services.register( DOMAIN, SERVICE_EMERGENCY_UNBLOCK, self.emergency_unblock, schema=SCHEMA_EMERGENCY_UNBLOCK, ) # Additional services would go here self.hass.services.register(DOMAIN, SERVICE_ADD_CLIENT, self.add_client) self.hass.services.register(DOMAIN, SERVICE_REMOVE_CLIENT, self.remove_client) self.hass.services.register(DOMAIN, SERVICE_BULK_UPDATE_CLIENTS, self.bulk_update_clients) def unregister_services(self) -> None: """Unregister all services.""" services = [ SERVICE_BLOCK_SERVICES, SERVICE_UNBLOCK_SERVICES, SERVICE_EMERGENCY_UNBLOCK, SERVICE_ADD_CLIENT, SERVICE_REMOVE_CLIENT, SERVICE_BULK_UPDATE_CLIENTS, ] for service in services: if self.hass.services.has_service(DOMAIN, service): self.hass.services.remove(DOMAIN, service) async def block_services(self, call: ServiceCall) -> None: """Block services for a specific client.""" client_name = call.data[ATTR_CLIENT_NAME] services = call.data[ATTR_SERVICES] _LOGGER.info("Blocking services %s for client %s", services, client_name) for entry_data in self.hass.data[DOMAIN].values(): api: AdGuardHomeAPI = entry_data["api"] try: client = await api.get_client_by_name(client_name) if client: current_blocked = client.get("blocked_services", {}) current_services = current_blocked.get("ids", []) if isinstance(current_blocked, dict) else current_blocked or [] updated_services = list(set(current_services + services)) await api.update_client_blocked_services(client_name, updated_services) _LOGGER.info("Successfully blocked services for %s", client_name) except Exception as err: _LOGGER.error("Failed to block services for %s: %s", client_name, err) async def unblock_services(self, call: ServiceCall) -> None: """Unblock services for a specific client.""" client_name = call.data[ATTR_CLIENT_NAME] services = call.data[ATTR_SERVICES] _LOGGER.info("Unblocking services %s for client %s", services, client_name) for entry_data in self.hass.data[DOMAIN].values(): api: AdGuardHomeAPI = entry_data["api"] try: client = await api.get_client_by_name(client_name) if client: current_blocked = client.get("blocked_services", {}) current_services = current_blocked.get("ids", []) if isinstance(current_blocked, dict) else current_blocked or [] updated_services = [s for s in current_services if s not in services] await api.update_client_blocked_services(client_name, updated_services) _LOGGER.info("Successfully unblocked services for %s", client_name) except Exception as err: _LOGGER.error("Failed to unblock services for %s: %s", client_name, err) async def emergency_unblock(self, call: ServiceCall) -> None: """Emergency unblock - temporarily disable protection.""" duration = call.data[ATTR_DURATION] clients = call.data[ATTR_CLIENTS] _LOGGER.warning("Emergency unblock activated for %s seconds", duration) for entry_data in self.hass.data[DOMAIN].values(): api: AdGuardHomeAPI = entry_data["api"] try: if "all" in clients: await api.set_protection(False) task = asyncio.create_task(self._delayed_enable_protection(api, duration)) self._emergency_unblock_tasks[f"{api.host}:{api.port}"] = task except Exception as err: _LOGGER.error("Failed to execute emergency unblock: %s", err) async def _delayed_enable_protection(self, api: AdGuardHomeAPI, delay: int) -> None: """Re-enable protection after delay.""" await asyncio.sleep(delay) try: await api.set_protection(True) _LOGGER.info("Emergency unblock expired - protection re-enabled") except Exception as err: _LOGGER.error("Failed to re-enable protection: %s", err) async def add_client(self, call: ServiceCall) -> None: """Add a new client.""" client_data = dict(call.data) _LOGGER.info("Adding new client: %s", client_data.get("name")) for entry_data in self.hass.data[DOMAIN].values(): api: AdGuardHomeAPI = entry_data["api"] try: await api.add_client(client_data) _LOGGER.info("Successfully added client: %s", client_data.get("name")) except Exception as err: _LOGGER.error("Failed to add client: %s", err) async def remove_client(self, call: ServiceCall) -> None: """Remove a client.""" client_name = call.data.get("name") _LOGGER.info("Removing client: %s", client_name) for entry_data in self.hass.data[DOMAIN].values(): api: AdGuardHomeAPI = entry_data["api"] try: await api.delete_client(client_name) _LOGGER.info("Successfully removed client: %s", client_name) except Exception as err: _LOGGER.error("Failed to remove client %s: %s", client_name, err) async def bulk_update_clients(self, call: ServiceCall) -> None: """Update multiple clients matching a pattern.""" _LOGGER.info("Bulk update clients called") # Implementation would go here