# --- START OF FILE network.py ---

# network.py
"""
THIS SOFTWARE IS PROVIDED “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.

Handles basic UDP socket operations like creation, configuration (buffer size),
and closing for the application. Uses standardized logging prefixes.
"""

# Standard library imports
import socket
import logging
import sys  # Keep sys import if needed for platform-specific checks (not currently used)

# No local application imports needed in this module typically


def create_udp_socket(local_ip, local_port):
    """
    Creates a UDP socket, attempts to increase its receive buffer size,
    and binds it to the specified local IP address and port.

    Args:
        local_ip (str): The local IP address to bind to.
        local_port (int): The local port to bind to.

    Returns:
        socket.socket: The created and bound UDP socket, or None on error.
    """
    log_prefix = "[Network]"  # Standard prefix for this module
    logging.debug(
        f"{log_prefix} Attempting to create UDP socket for {local_ip}:{local_port}"
    )

    try:
        # Create the UDP socket (IPv4)
        sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        logging.debug(f"{log_prefix} Socket object created.")

        # --- Receive Buffer Size Adjustment ---
        try:
            # Get the default buffer size (INFO level is okay for this setup detail)
            default_rcvbuf = sock.getsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF)
            logging.info(
                f"{log_prefix} Default socket receive buffer size: {default_rcvbuf} bytes"
            )

            # Set a larger buffer size (adjust value as needed)
            desired_rcvbuf = 8 * 1024 * 1024  # Request 8MB
            logging.debug(
                f"{log_prefix} Requesting receive buffer size: {desired_rcvbuf} bytes"
            )
            sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, desired_rcvbuf)

            # Verify the actual size set by the OS (INFO level okay)
            actual_rcvbuf = sock.getsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF)
            logging.info(
                f"{log_prefix} Actual receive buffer size set: {actual_rcvbuf} bytes"
            )
            if actual_rcvbuf < desired_rcvbuf:
                # WARNING if OS limited the size
                logging.warning(
                    f"{log_prefix} OS capped the receive buffer size lower than requested."
                )
        except OSError as buf_e:
            # ERROR if setting buffer fails (might indicate permissions or OS limits issue)
            logging.error(
                f"{log_prefix} Failed to set receive buffer size: {buf_e}. Check OS limits (e.g., sysctl net.core.rmem_max)."
            )
        except Exception as buf_e:
            # Keep EXCEPTION for unexpected errors during buffer setting
            logging.exception(
                f"{log_prefix} Unexpected error setting receive buffer: {buf_e}"
            )

        # --- Bind the Socket ---
        logging.debug(f"{log_prefix} Binding socket to ('{local_ip}', {local_port})...")
        sock.bind((local_ip, local_port))
        # INFO for successful bind confirmation
        logging.info(
            f"{log_prefix} UDP socket created and bound successfully to {local_ip}:{local_port}"
        )
        return sock

    except socket.error as e:
        # ERROR for critical socket creation/binding failure
        logging.error(
            f"{log_prefix} Error creating/binding UDP socket for {local_ip}:{local_port}: {e}"
        )
        return None
    except Exception as e:
        # Keep EXCEPTION for any other unexpected errors
        logging.exception(
            f"{log_prefix} Unexpected error during UDP socket creation for {local_ip}:{local_port}: {e}"
        )
        return None


def receive_udp_packet(sock, buffer_size=65535):
    """
    Receives a single UDP packet from the specified socket.
    Note: Currently unused by the UdpReceiver class which uses sock.recvfrom directly.

    Args:
        sock (socket.socket): The UDP socket to receive from.
        buffer_size (int): The maximum buffer size for receiving data.

    Returns:
        tuple: A tuple containing (data, sender_address), or (None, None) on error.
    """
    log_prefix = "[Network]"  # Standard prefix
    # DEBUG for function call (if used)
    logging.debug(
        f"{log_prefix} Attempting to receive UDP packet (buffer size: {buffer_size})."
    )
    try:
        data, addr = sock.recvfrom(buffer_size)
        logging.debug(f"{log_prefix} Received {len(data)} bytes from {addr}.")
        return data, addr
    except socket.timeout:
        # DEBUG for expected timeout (if timeout is set on socket)
        logging.debug(f"{log_prefix} Socket recvfrom timeout.")
        return None, None
    except socket.error as e:
        # ERROR for socket errors during receive
        logging.error(f"{log_prefix} Error receiving UDP packet: {e}")
        return None, None
    except Exception as e:
        # Keep EXCEPTION for unexpected errors
        logging.exception(f"{log_prefix} Unexpected error receiving UDP packet: {e}")
        return None, None


def close_udp_socket(sock):
    """
    Closes the specified UDP socket gracefully.

    Args:
        sock (socket.socket): The UDP socket object to close.
    """
    log_prefix = "[Network]"  # Standard prefix
    # Check if socket is valid before attempting close
    if sock and hasattr(sock, "fileno") and sock.fileno() != -1:
        try:
            # INFO for significant action: closing the main socket
            logging.info(f"{log_prefix} Closing UDP socket (fd={sock.fileno()})...")
            sock.close()
            logging.info(f"{log_prefix} UDP socket closed successfully.")
        except socket.error as e:
            # ERROR if closing fails
            logging.error(f"{log_prefix} Error closing UDP socket: {e}")
        except Exception as e:
            # Keep EXCEPTION for unexpected errors
            logging.exception(f"{log_prefix} Unexpected error closing UDP socket: {e}")
    else:
        # DEBUG if socket is already closed or invalid
        logging.debug(
            f"{log_prefix} Socket close requested, but socket was already closed or invalid."
        )


# --- END OF FILE network.py ---
