"""Get your own public IP address or that of any host."""

from __future__ import annotations

import asyncio
from datetime import timedelta
from ipaddress import IPv4Address, IPv6Address
import logging
from typing import Literal

import aiodns
from aiodns.error import DNSError

from homeassistant.components.sensor import SensorEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_NAME, CONF_PORT
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback

from .const import (
    CONF_HOSTNAME,
    CONF_IPV4,
    CONF_IPV6,
    CONF_PORT_IPV6,
    CONF_RESOLVER,
    CONF_RESOLVER_IPV6,
    DOMAIN,
)

DEFAULT_RETRIES = 2
MAX_RESULTS = 10

_LOGGER = logging.getLogger(__name__)

SCAN_INTERVAL = timedelta(seconds=120)


def sort_ips(ips: list, querytype: Literal["A", "AAAA"]) -> list:
    """Join IPs into a single string."""

    if querytype == "AAAA":
        ips = [IPv6Address(ip) for ip in ips]
    else:
        ips = [IPv4Address(ip) for ip in ips]
    return [str(ip) for ip in sorted(ips)][:MAX_RESULTS]


async def async_setup_entry(
    hass: HomeAssistant,
    entry: ConfigEntry,
    async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
    """Set up the dnsip sensor entry."""

    hostname = entry.data[CONF_HOSTNAME]
    name = entry.data[CONF_NAME]

    nameserver_ipv4 = entry.options[CONF_RESOLVER]
    nameserver_ipv6 = entry.options[CONF_RESOLVER_IPV6]
    port_ipv4 = entry.options[CONF_PORT]
    port_ipv6 = entry.options[CONF_PORT_IPV6]

    entities = []
    if entry.data[CONF_IPV4]:
        entities.append(WanIpSensor(name, hostname, nameserver_ipv4, False, port_ipv4))
    if entry.data[CONF_IPV6]:
        entities.append(WanIpSensor(name, hostname, nameserver_ipv6, True, port_ipv6))

    async_add_entities(entities, update_before_add=True)


class WanIpSensor(SensorEntity):
    """Implementation of a DNS IP sensor."""

    _attr_has_entity_name = True
    _attr_translation_key = "dnsip"
    _unrecorded_attributes = frozenset({"resolver", "querytype", "ip_addresses"})

    resolver: aiodns.DNSResolver

    def __init__(
        self,
        name: str,
        hostname: str,
        nameserver: str,
        ipv6: bool,
        port: int,
    ) -> None:
        """Initialize the DNS IP sensor."""
        self._attr_name = "IPv6" if ipv6 else None
        self._attr_unique_id = f"{hostname}_{ipv6}"
        self.hostname = hostname
        self.port = port
        self.nameserver = nameserver
        self.querytype: Literal["A", "AAAA"] = "AAAA" if ipv6 else "A"
        self._retries = DEFAULT_RETRIES
        self._attr_extra_state_attributes = {
            "resolver": nameserver,
            "querytype": self.querytype,
        }
        self._attr_device_info = DeviceInfo(
            entry_type=DeviceEntryType.SERVICE,
            identifiers={(DOMAIN, hostname)},
            manufacturer="DNS",
            model=aiodns.__version__,
            name=name,
        )
        self.create_dns_resolver()

    def create_dns_resolver(self) -> None:
        """Create the DNS resolver."""
        self.resolver = aiodns.DNSResolver(
            nameservers=[self.nameserver], tcp_port=self.port, udp_port=self.port
        )

    async def async_update(self) -> None:
        """Get the current DNS IP address for hostname."""
        if self.resolver._closed:  # noqa: SLF001
            self.create_dns_resolver()
        response = None
        try:
            async with asyncio.timeout(10):
                response = await self.resolver.query(self.hostname, self.querytype)
        except TimeoutError as err:
            _LOGGER.debug("Timeout while resolving host: %s", err)
            await self.resolver.close()
        except DNSError as err:
            _LOGGER.warning("Exception while resolving host: %s", err)
            await self.resolver.close()

        if response:
            sorted_ips = sort_ips(
                [res.host for res in response], querytype=self.querytype
            )
            self._attr_native_value = sorted_ips[0]
            self._attr_extra_state_attributes["ip_addresses"] = sorted_ips
            self._attr_available = True
            self._retries = DEFAULT_RETRIES
        elif self._retries > 0:
            self._retries -= 1
        else:
            self._attr_available = False
