#!/usr/bin/env python3
"""
mDNS Hostname Broadcaster - Advertise a hostname on the local network.
"""
import socket
import argparse
import signal
import sys
import logging
import time
import os
from typing import Optional
from contextlib import contextmanager

try:
    from zeroconf import Zeroconf, ServiceInfo
except ImportError:
    print("Error: zeroconf library not installed. Install with: pip install zeroconf")
    sys.exit(1)


# Configure logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    datefmt='%H:%M:%S'
)
logger = logging.getLogger(__name__)


def get_local_ip(default: str = "127.0.0.1") -> str:
    """
    Return the primary local IPv4 address.

    Args:
        default: Fallback IP if detection fails

    Returns:
        Local IPv4 address as string
    """
    try:
        with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
            # Doesn't actually connect, just determines routing
            s.connect(("10.255.255.255", 1))
            return s.getsockname()[0]
    except OSError as e:
        logger.warning(f"Could not detect local IP: {e}. Using {default}")
        return default


def normalize_hostname(hostname: str) -> str:
    """
    Ensure hostname has no .local suffix and no trailing dots.

    Args:
        hostname: Raw hostname string

    Returns:
        Normalized hostname
    """
    return hostname.rstrip(".").removesuffix(".local")


def validate_hostname(hostname: str) -> bool:
    """
    Validate hostname format (basic check).

    Args:
        hostname: Hostname to validate

    Returns:
        True if valid, False otherwise
    """
    if not hostname or len(hostname) > 63:
        return False
    # Basic check: alphanumeric and hyphens, can't start/end with hyphen
    return all(c.isalnum() or c == '-' for c in hostname) and \
           not hostname.startswith('-') and not hostname.endswith('-')


def validate_port(port: int) -> bool:
    """
    Validate port number is in valid range.

    Args:
        port: Port number to validate

    Returns:
        True if valid, False otherwise
    """
    return 1 <= port <= 65535


@contextmanager
def zeroconf_service(service_info: ServiceInfo):
    """
    Context manager for Zeroconf service registration.

    Args:
        service_info: ServiceInfo object to register

    Yields:
        Zeroconf instance
    """
    zc = Zeroconf()
    try:
        zc.register_service(service_info)
        yield zc
    finally:
        logger.info("Unregistering service...")
        try:
            zc.unregister_service(service_info)
        except Exception as e:
            logger.error(f"Error unregistering service: {e}")
        finally:
            zc.close()


def broadcast_hostname(hostname: str, port: int, service_type: str) -> None:
    """
    Broadcast hostname via mDNS.

    Args:
        hostname: Hostname to broadcast
        port: Service port
        service_type: mDNS service type

    Raises:
        ValueError: If hostname or port is invalid
        RuntimeError: If service registration fails
    """
    # Validate inputs
    hostname = normalize_hostname(hostname)
    if not validate_hostname(hostname):
        raise ValueError(f"Invalid hostname: {hostname}")

    if not validate_port(port):
        raise ValueError(f"Invalid port: {port} (must be 1-65535)")

    # Get network info
    local_ip = get_local_ip()
    try:
        ip_bytes = socket.inet_aton(local_ip)
    except OSError as e:
        raise RuntimeError(f"Invalid IP address {local_ip}: {e}")

    # Prepare service info
    service_type = service_type.rstrip(".") + ".local."
    service_name = f"{hostname}.{service_type}"
    server_name = f"{hostname}.local."

    # Properties must be bytes (zeroconf requirement)
    properties = {
        b"description": f"mDNS broadcast for {hostname}".encode('utf-8')
    }

    try:
        info = ServiceInfo(
            type_=service_type,
            name=service_name,
            addresses=[ip_bytes],
            port=port,
            properties=properties,
            server=server_name,
        )
    except Exception as e:
        raise RuntimeError(f"Failed to create ServiceInfo: {e}")

    # Setup signal handlers
    shutdown_requested = False

    def signal_handler(signum, frame):
        nonlocal shutdown_requested
        logger.info(f"Received signal {signum}")
        shutdown_requested = True

    signal.signal(signal.SIGINT, signal_handler)
    signal.signal(signal.SIGTERM, signal_handler)

    # Register and run
    try:
        with zeroconf_service(info) as zc:
            print("=" * 60)
            print("mDNS Hostname Broadcaster")
            print("=" * 60)
            print(f"Hostname : {hostname}.local")
            print(f"IP       : {local_ip}")
            print(f"Port     : {port}")
            print(f"Service  : {service_type}")
            print("\nPress Ctrl+C to stop...\n")
            logger.info("Service registered successfully")

            # Cross-platform wait loop (instead of signal.pause())
            while not shutdown_requested:
                time.sleep(0.5)

    except Exception as e:
        raise RuntimeError(f"Service registration failed: {e}")

    logger.info("Shutdown complete")


def main() -> None:
    """Main entry point."""

    # Get default hostname from environment or system
    default_hostname = os.environ.get('MDNS_HOSTNAME')
    if not default_hostname:
        default_hostname = 'wp-server'
        # default_hostname = socket.gethostname().split('.')[0]  # Remove domain if present

    # Get default port from environment
    default_port = int(os.environ.get('MDNS_PORT', '80'))

    # Get default service type from environment
    default_service = os.environ.get('MDNS_SERVICE', '_discard._tcp')


    parser = argparse.ArgumentParser(
        description="mDNS Hostname Broadcaster - Advertise a hostname on your local network",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog=f"""
Examples:
  %(prog)s                           # Use system hostname ({default_hostname})
  %(prog)s myserver                  # Broadcast custom hostname
  %(prog)s myserver -p 8080          # Custom port
  %(prog)s myserver -p 443 -t _https._tcp

Environment Variables:
  MDNS_HOSTNAME    Set custom default hostname (current: {default_hostname})
  MDNS_PORT        Set default port (current: {default_port})
  MDNS_SERVICE     Set default service type (current: {default_service})

  Example:
    export MDNS_HOSTNAME=myserver
    export MDNS_PORT=8080
    %(prog)s                         # Uses myserver:8080
"""
    )

    parser.add_argument(
        "hostname",
        nargs='?',  # Make optional
        default=default_hostname,
        help=f"Hostname to broadcast (default: {default_hostname})"
    )

    parser.add_argument(
        "-p", "--port",
        type=int,
        default=default_port,
        help=f"Service port (default: {default_port})"
    )

    parser.add_argument(
        "-t", "--type",
        default=default_service,
        help=f"""Service type (default: {default_service}) 
        use (_discard | _http | _https | _ssh | _ftp)._tcp
        """
    )

    parser.add_argument(
        "-v", "--verbose",
        action="store_true",
        help="Enable verbose logging"
    )

    args = parser.parse_args()
    if args.verbose:
        logger.setLevel(logging.DEBUG)


    try:
        broadcast_hostname(args.hostname, args.port, args.type)
    except KeyboardInterrupt:
        logger.info("Interrupted by user")
        sys.exit(0)
    except (ValueError, RuntimeError) as e:
        logger.error(str(e))
        sys.exit(1)
    except Exception as e:
        logger.exception(f"Unexpected error: {e}")
        sys.exit(1)


if __name__ == "__main__":
    main()
