"""Various helper utility for Hue bridge discovery."""
from __future__ import annotations

import contextlib
from dataclasses import dataclass

from aiohttp import ClientConnectionError, ClientError, ClientSession

from .util import normalize_bridge_id

URL_NUPNP = "https://discovery.meethue.com/"


@dataclass(frozen=True)
class DiscoveredHueBridge:
    """Model for a discovered Hue bridge."""

    host: str
    id: str
    supports_v2: bool


async def discover_bridge(
    host: str,
    websession: ClientSession | None = None,
) -> DiscoveredHueBridge:
    """
    Discover bridge details from given hostname/ip.

    Raises exception from aiohttp if there is no bridge alive on given ip/host.
    """
    websession_provided = websession is not None
    if websession is None:
        websession = ClientSession()
    try:
        bridge_id = await is_hue_bridge(host, websession)
        supports_v2 = await is_v2_bridge(host, websession)
        return DiscoveredHueBridge(host, bridge_id, supports_v2)
    finally:
        if not websession_provided:
            await websession.close()


async def discover_nupnp(
    websession: ClientSession | None = None,
) -> list[DiscoveredHueBridge]:
    """Discover bridges via NUPNP."""
    result = []
    websession_provided = websession is not None
    if websession is None:
        websession = ClientSession()
    try:
        async with websession.get(URL_NUPNP, timeout=30) as res:
            for item in await res.json():
                host = item["internalipaddress"]
                # the nupnp discovery might return items that are not in local network
                # connect to each bridge to find out if it's alive.
                with contextlib.suppress(Exception):
                    result.append(await discover_bridge(host, websession))
        return result
    except ClientError:
        return result
    finally:
        if not websession_provided:
            await websession.close()


async def is_hue_bridge(host: str, websession: ClientSession | None = None) -> str:
    """
    Check if there is a bridge alive on given ip and return bridge ID.

    Raises exception from aiohttp if the bridge can not be reached on given hostname.
    """
    websession_provided = websession is not None
    if websession is None:
        websession = ClientSession()
    try:
        # every hue bridge returns discovery info on this endpoint
        url = f"http://{host}/api/config"
        async with websession.get(url, timeout=30) as res:
            res.raise_for_status()
            data = await res.json()
            if "bridgeid" not in data:
                # there are some emulator projects out there that emulate a Hue bridge
                # in a sloppy way, ignore them.
                # https://github.com/home-assistant-libs/aiohue/issues/134
                raise ClientConnectionError("Invalid API response, not a real Hue bridge?")
            return normalize_bridge_id(data["bridgeid"])
    finally:
        if not websession_provided:
            await websession.close()


async def is_v2_bridge(host: str, websession: ClientSession | None = None) -> bool:
    """Check if the bridge has support for the new V2 api."""
    websession_provided = websession is not None
    if websession is None:
        websession = ClientSession()
    try:
        # v2 api is https only and returns a 403 forbidden when no key provided
        url = f"https://{host}/clip/v2/resource"
        async with websession.get(url, ssl=False, raise_for_status=False, timeout=30) as res:
            return res.status == 403
    except Exception:  # pylint: disable=broad-except
        # all other status/exceptions means the bridge is not v2 or not reachable at this time
        return False
    finally:
        if not websession_provided:
            await websession.close()
