from __future__ import annotations import logging from typing import Any import aiohttp from homeassistant.core import HomeAssistant from homeassistant.helpers.aiohttp_client import async_get_clientsession from .const import ( API_URL_MAP_LOCATIONS, API_URL_SEARCH, BASE_URL, DEFAULT_USER_AGENT, DOMAIN, ) from .parser import parse_location_page, validate_location_path _LOGGER = logging.getLogger(__name__) _MAP_LOCATIONS_CACHE = "map_locations_cache" def parse_search_results(data: Any) -> list[dict[str, str]]: """Parse search endpoint results into normalized location dictionaries.""" if not isinstance(data, dict): return [] results: list[dict[str, str]] = [] for item in data.get("results", []): if not isinstance(item, dict): continue path = item.get("path") name = item.get("name") if not isinstance(path, str) or not isinstance(name, str): continue try: normalized_path = validate_location_path(path) except ValueError: _LOGGER.debug("Ignoring search result with invalid path: %s", path) continue results.append( { "name": name, "country": item.get("country", "") if isinstance(item.get("country"), str) else "", "region": item.get("region", "") if isinstance(item.get("region"), str) else "", "area": item.get("area", "") if isinstance(item.get("area"), str) else "", "path": normalized_path, } ) return results def parse_map_locations(data: Any) -> dict[str, dict[str, str]]: """Parse map-locations payload into a place_id -> location mapping.""" if isinstance(data, dict): raw_locations = data.get("locations", []) else: raw_locations = data if not isinstance(raw_locations, list): return {} mapping: dict[str, dict[str, str]] = {} for item in raw_locations: if not isinstance(item, list) or len(item) < 5: continue place_id, name, country, area, path = item[:5] if not isinstance(place_id, str) or not isinstance(name, str): continue if not isinstance(path, str): continue try: normalized_path = validate_location_path(path) except ValueError: _LOGGER.debug("Ignoring map location with invalid path: %s", path) continue mapping[place_id] = { "name": name, "country": country if isinstance(country, str) else "", "area": area if isinstance(area, str) else "", "path": normalized_path, } return mapping class SeaTemperatureAPI: """API to fetch sea temperature data.""" def __init__(self, hass: HomeAssistant) -> None: """Initialize the API.""" self.hass = hass self._headers = {"User-Agent": DEFAULT_USER_AGENT} async def search_locations(self, query: str) -> list[dict[str, str]] | None: """Search SeaTemperatures locations by query string.""" if not query.strip(): return [] session = async_get_clientsession(self.hass) try: async with session.get( API_URL_SEARCH, params={"q": query.strip()}, headers=self._headers, ) as response: response.raise_for_status() return parse_search_results(await response.json()) except (aiohttp.ClientError, TimeoutError) as err: _LOGGER.error("Error searching SeaTemperatures locations for %s: %s", query, err) return None async def get_location_by_place_id(self, place_id: str) -> dict[str, str] | None: """Resolve a legacy place ID to a current path-based location.""" if not place_id: return None cache = await self._get_map_locations() if cache is None: return None location = cache.get(place_id) if location is None and not place_id.startswith("sea-"): location = cache.get(f"sea-{place_id}") return location async def get_temperatures(self, location_path: str) -> dict[str, Any] | None: """Fetch temperature data for a specific location path.""" try: normalized_path = validate_location_path(location_path) except ValueError as err: _LOGGER.error("Invalid SeaTemperatures path %s: %s", location_path, err) return None url = f"{BASE_URL}{normalized_path}" session = async_get_clientsession(self.hass) try: async with session.get(url, headers=self._headers) as response: response.raise_for_status() html = await response.text() except (aiohttp.ClientError, TimeoutError) as err: _LOGGER.error( "Error fetching temperature data for path %s: %s", normalized_path, err, ) return None return parse_location_page(html).as_legacy_payload() async def _get_map_locations(self) -> dict[str, dict[str, str]] | None: """Fetch and cache the map-locations payload for legacy migration.""" domain_data = self.hass.data.setdefault(DOMAIN, {}) if _MAP_LOCATIONS_CACHE in domain_data: return domain_data[_MAP_LOCATIONS_CACHE] session = async_get_clientsession(self.hass) try: async with session.get(API_URL_MAP_LOCATIONS, headers=self._headers) as response: response.raise_for_status() mapping = parse_map_locations(await response.json()) except (aiohttp.ClientError, TimeoutError) as err: _LOGGER.error("Error fetching SeaTemperatures map locations: %s", err) return None domain_data[_MAP_LOCATIONS_CACHE] = mapping return mapping