"""Asynchronous Python client for Netgear routers."""

from __future__ import annotations

import asyncio
from dataclasses import dataclass
from importlib import metadata
import logging
from typing import Any, Self
import xml.etree.ElementTree as ET

from aiohttp import ClientSession
from aiohttp.hdrs import METH_GET, METH_POST
from yarl import URL

from netgearpy.const import (
    ENVELOPE,
    GET_ATTACHED_DEVICES_BODY,
    GET_BLOCK_DEVICE_ENABLE_BODY,
    GET_ETHERNET_LINK_STATUS_BODY,
    GET_INFO_BODY,
    GET_SYSTEM_INFO_BODY,
    GET_TRAFFIC_METER_STATISTICS_BODY,
    IS_5G_GUEST_ACCESS_ENABLED_BODY,
    IS_GUEST_ACCESS_ENABLED_BODY,
    IS_PARENTAL_CONTROL_ENABLED_BODY,
    IS_QOS_ENABLED_BODY,
    IS_SMART_CONNECT_ENABLED,
    IS_TRAFFIC_METER_ENABLED_BODY,
    LOGIN_BODY,
)
from netgearpy.models import (
    AdvancedQoSAction,
    AttachedDevice,
    CurrentSettings,
    DeviceConfigAction,
    DeviceInfo,
    DeviceInfoAction,
    ParentalControlAction,
    Service,
    SystemInfo,
    TrafficMeterStatistics,
    WanEthernetLinkConfigAction,
    WlanConfigurationAction,
)

VERSION = metadata.version(__package__)

_LOGGER = logging.getLogger(__package__)


@dataclass
class NetgearClient:
    """Main class for handling connections with a Netgear router."""

    host: str
    session: ClientSession | None = None
    request_timeout: int = 10
    _close_session: bool = False
    _soap_port: int | None = None
    _login_method: int | None = None
    _lock = asyncio.Lock()

    async def _request(
        self,
        url: str,
        *,
        method: str = METH_GET,
        data: str | None = None,
        headers: dict[str, Any] | None = None,
    ) -> str:
        """Handle a request to a Netgear router."""
        base_headers = {
            "User-Agent": f"Netgearpy/{VERSION}",
            "Accept": "application/xml",
        }

        if self.session is None:
            self.session = ClientSession()
            self._close_session = True

        if headers is None:
            headers = {}

        async with asyncio.timeout(self.request_timeout):
            response = await self.session.request(
                method,
                url,
                headers=base_headers | headers,
                data=data,
            )

        return await response.text()

    async def _get(self, uri: str) -> str:
        """Handle a GET request to Netgear."""
        return await self._request(uri, method=METH_GET)

    async def get_current_setting(self) -> CurrentSettings:
        """Get the current settings from the Netgear router."""
        url = URL.build(scheme="http", host=self.host, path="/currentsetting.htm")
        response = await self._get(str(url))
        res: dict[str, Any] = {}
        for entry in response.split("\n"):
            if entry:
                split_entry = entry.split("=")
                res[split_entry[0]] = split_entry[1]
        result = CurrentSettings.from_dict(res)
        self._soap_port = result.https_port
        self._login_method = result.login_method
        return result

    async def _post_xml(
        self, service: str, action: str, body: str
    ) -> dict[str, str | None]:
        """Post XML data to the Netgear router."""
        if self._soap_port is None or self._login_method is None:
            await self.get_current_setting()
        https = self._soap_port in [443, 5555]
        url = URL.build(
            scheme="https" if https else "http",
            host=self.host,
            path="/soap/server_sa/",
            port=self._soap_port,
        )
        headers = {
            "SOAPAction": f"urn:NETGEAR-ROUTER:service:{service}:1#{action}",
        }
        body_str = ENVELOPE.format(body)
        async with self._lock:
            await asyncio.sleep(0.3)
            if service != DeviceConfigAction.LOGIN:
                _LOGGER.debug(
                    "Posting XML to %s with action `%s` and body: %s",
                    url,
                    f"urn:NETGEAR-ROUTER:service:{service}:1#{action}",
                    body_str,
                )
            response = await self._request(
                str(url), method=METH_POST, data=body_str, headers=headers
            )
        _LOGGER.debug("Response from %s: %s", url, response)
        response_dict = {}
        root = ET.fromstring(response)  # noqa: S314
        for item in root.iter():
            if item.tag != "{http://schemas.xmlsoap.org/soap/envelope/}Body":
                continue
            for child in item:
                for subchild in child:
                    response_dict[subchild.tag] = subchild.text
        return response_dict

    async def login(self, username: str, password: str) -> None:
        """Login to the Netgear router."""
        await self._post_xml(
            Service.DEVICE_CONFIG,
            DeviceConfigAction.LOGIN,
            LOGIN_BODY.format(username, password),
        )

    async def get_attached_devices(self) -> list[AttachedDevice]:
        """Get the attached devices from the Netgear router."""
        response = await self._post_xml(
            Service.DEVICE_INFO,
            DeviceInfoAction.GET_ATTACHED_DEVICES,
            GET_ATTACHED_DEVICES_BODY,
        )
        device_string = response["NewAttachDevice"]
        if not device_string:
            return []
        return [
            AttachedDevice.from_string(entry) for entry in device_string.split("@")[1::]
        ]

    async def is_parental_control_enabled(self) -> bool:
        """Check if parental control is enabled."""
        response = await self._post_xml(
            Service.PARENTAL_CONTROL,
            ParentalControlAction.GET_ENABLE_STATUS,
            IS_PARENTAL_CONTROL_ENABLED_BODY,
        )
        return response.get("ParentalControl") == "1"

    async def is_block_device_enabled(self) -> bool:
        """Check if block device is enabled."""
        response = await self._post_xml(
            Service.DEVICE_CONFIG,
            DeviceConfigAction.GET_BLOCK_DEVICE_ENABLE_STATUS,
            GET_BLOCK_DEVICE_ENABLE_BODY,
        )
        return response.get("ParentalControl") == "1"

    async def get_device_info(self) -> DeviceInfo:
        """Get device information from the Netgear router."""
        response = await self._post_xml(
            Service.DEVICE_INFO, DeviceInfoAction.GET_INFO, GET_INFO_BODY
        )
        return DeviceInfo.from_dict(response)

    async def get_traffic_meter_statistics(self) -> TrafficMeterStatistics:
        """Get traffic meter statistics from the Netgear router."""
        response = await self._post_xml(
            Service.DEVICE_CONFIG,
            DeviceConfigAction.GET_TRAFFIC_METER_STATISTICS,
            GET_TRAFFIC_METER_STATISTICS_BODY,
        )
        return TrafficMeterStatistics.from_dict(response)

    async def get_system_info(self) -> SystemInfo:
        """Get system information from the Netgear router."""
        response = await self._post_xml(
            Service.DEVICE_INFO, DeviceInfoAction.GET_SYSTEM_INFO, GET_SYSTEM_INFO_BODY
        )
        return SystemInfo.from_dict(response)

    async def is_traffic_meter_enabled(self) -> bool:
        """Check if the traffic meter is enabled."""
        response = await self._post_xml(
            Service.DEVICE_CONFIG,
            DeviceConfigAction.GET_TRAFFIC_METER_ENABLED,
            IS_TRAFFIC_METER_ENABLED_BODY,
        )
        return response.get("NewTrafficMeterEnable") == "1"

    async def is_guest_access_enabled(self) -> bool:
        """Check if guest access is enabled."""
        response = await self._post_xml(
            Service.WLAN_CONFIGURATION,
            WlanConfigurationAction.GET_GUEST_ACCESS_ENABLED,
            IS_GUEST_ACCESS_ENABLED_BODY,
        )
        return response.get("NewGuestAccessEnabled") == "1"

    async def is_5g_guest_access_enabled(self) -> bool:
        """Check if 5G guest access is enabled."""
        response = await self._post_xml(
            Service.WLAN_CONFIGURATION,
            WlanConfigurationAction.GET_5G1_GUEST_ACCESS_ENABLED_2,
            IS_5G_GUEST_ACCESS_ENABLED_BODY,
        )
        return response.get("NewGuestAccessEnabled") == "1"

    async def is_qos_enabled(self) -> bool:
        """Check if QoS is enabled."""
        response = await self._post_xml(
            Service.ADVANCED_QOS,
            AdvancedQoSAction.GET_QOS_ENABLE_STATUS,
            IS_QOS_ENABLED_BODY,
        )
        return response.get("NewQoSEnableStatus") == "1"

    async def is_smart_connect_enabled(self) -> bool:
        """Check if Smart Connect is enabled."""
        response = await self._post_xml(
            Service.WLAN_CONFIGURATION,
            WlanConfigurationAction.IS_SMART_CONNECT_ENABLED,
            IS_SMART_CONNECT_ENABLED,
        )
        return response.get("NewSmartConnectEnable") == "1"

    async def get_ethernet_link_status(self) -> str:
        """Get the ethernet link status from the Netgear router."""
        response = await self._post_xml(
            Service.WAN_ETHERNET_LINK_CONFIG,
            WanEthernetLinkConfigAction.GET_ETHERNET_LINK_STATUS,
            GET_ETHERNET_LINK_STATUS_BODY,
        )
        assert response["NewEthernetLinkStatus"] is not None  # noqa: S101
        return response["NewEthernetLinkStatus"]

    async def close(self) -> None:
        """Close open client session."""
        if self.session and self._close_session:
            await self.session.close()

    async def __aenter__(self) -> Self:
        """Async enter.

        Returns
        -------
            The NetgearClient object.

        """
        return self

    async def __aexit__(self, *_exc_info: object) -> None:
        """Async exit.

        Args:
        ----
            _exc_info: Exec type.

        """
        await self.close()
