"""
Module for serialization and deserialization of KNX HPAI (Host Protocol Address Information) information.

A HPAI contains an IP address and a port.
"""

from __future__ import annotations

import socket

from xknx.exceptions import ConversionError, CouldNotParseKNXIP

from .knxip_enum import HostProtocol


class HPAI:
    """Class for Module for Serialization and Deserialization."""

    LENGTH = 0x08

    def __init__(
        self,
        ip_addr: str = "0.0.0.0",
        port: int = 0,
        protocol: HostProtocol = HostProtocol.IPV4_UDP,
    ) -> None:
        """Initialize HPAI object."""
        self.ip_addr = ip_addr
        self.port = port
        self.protocol = protocol

    @property
    def route_back(self) -> bool:
        """Return True if HPAI is a route back address information."""
        return self.ip_addr == "0.0.0.0"

    @property
    def addr_tuple(self) -> tuple[str, int]:
        """Return tuple of ip address and port."""
        return self.ip_addr, self.port

    def from_knx(self, raw: bytes) -> int:
        """Parse/deserialize from KNX/IP raw data."""
        if len(raw) < HPAI.LENGTH:
            raise CouldNotParseKNXIP("wrong HPAI length")
        if raw[0] != HPAI.LENGTH:
            raise CouldNotParseKNXIP("wrong HPAI length")
        try:
            self.protocol = HostProtocol(raw[1])
        except ValueError as err:
            raise CouldNotParseKNXIP("unsupported host protocol code") from err
        self.ip_addr = socket.inet_ntoa(raw[2:6])
        self.port = raw[6] * 256 + raw[7]
        return HPAI.LENGTH

    def to_knx(self) -> bytes:
        """Serialize to KNX/IP raw data."""
        try:
            return (
                bytes((HPAI.LENGTH, self.protocol.value))
                + socket.inet_aton(self.ip_addr)
                + self.port.to_bytes(2, "big")
            )
        except (OSError, TypeError) as err:
            # OSError for invalid address strings; TypeError for non-strings
            raise ConversionError(f"Invalid IPv4 address: {self.ip_addr}") from err
        except (OverflowError, AttributeError) as err:
            # OverflowError for int < 0 or int > 65535; AttributeError for non-integers
            raise ConversionError(f"Port is not valid: {self.port}") from err

    def __repr__(self) -> str:
        """Representation of object."""
        return f"HPAI('{self.ip_addr}', {self.port}, {self.protocol})"

    def __str__(self) -> str:
        """Return object as readable string."""
        return f"{self.ip_addr}:{self.port}/{self.protocol.name[-3:].lower()}"

    def __eq__(self, other: object) -> bool:
        """Equal operator."""
        return self.__dict__ == other.__dict__

    def __hash__(self) -> int:
        """Hash function."""
        return hash((self.ip_addr, self.port, self.protocol))
