import argparse
import ipaddress
import pathlib

from .types import HostPortPair


def port_no(s: str) -> int:
    try:
        port = int(s)
        if port <= 0:
            raise ValueError("Port must be greater than 0")
        if port >= 65536:
            raise ValueError("Port must be less than 65536")
    except (ValueError, AssertionError) as e:
        msg = f"{s!r} is not a valid port number."
        raise argparse.ArgumentTypeError(msg) from e
    return port


def port_range(s: str) -> tuple[int, int]:
    try:
        port_range = tuple(map(int, s.split("-")))
    except (TypeError, ValueError) as e:
        msg = f"{s!r} should be a hyphen-separated pair of integers."
        raise argparse.ArgumentTypeError(msg) from e
    if len(port_range) != 2:
        msg = f"{s!r} should have exactly two integers."
        raise argparse.ArgumentTypeError(msg)
    if not (0 < port_range[0] < 65536):
        msg = f"{port_range[0]} is not a valid port number."
        raise argparse.ArgumentTypeError(msg)
    if not (0 < port_range[1] < 65536):
        msg = f"{port_range[1]} is not a valid port number."
        raise argparse.ArgumentTypeError(msg)
    if not (port_range[0] < port_range[1]):
        msg = f"{port_range[0]} should be less than {port_range[1]}."
        raise argparse.ArgumentTypeError(msg)
    return port_range


def positive_int(s: str) -> int:
    try:
        val = int(s)
        if val <= 0:
            raise ValueError("Value must be positive")
    except (ValueError, AssertionError) as e:
        msg = f"{s!r} is not a positive integer."
        raise argparse.ArgumentTypeError(msg) from e
    return val


def non_negative_int(s: str) -> int:
    try:
        val = int(s)
        if val < 0:
            raise ValueError("Value must be non-negative")
    except (ValueError, AssertionError) as e:
        msg = f"{s!r} is not a non-negative integer."
        raise argparse.ArgumentTypeError(msg) from e
    return val


def host_port_pair(s: str) -> HostPortPair:
    host: str | ipaddress._BaseAddress
    port: int
    pieces = s.rsplit(":", maxsplit=1)
    if len(pieces) == 1:
        msg = f"{s!r} should contain both IP address and port number."
        raise argparse.ArgumentTypeError(msg)
    # strip potential brackets in IPv6 hostname-port strings (RFC 3986).
    host = pieces[0].strip("[]")
    try:
        host = ipaddress.ip_address(host)
    except ValueError:
        # Let it be just a hostname.
        host = host
    try:
        port = int(pieces[1])
        if port <= 0:
            raise ValueError("Port must be greater than 0")
        if port >= 65536:
            raise ValueError("Port must be less than 65536")
    except (ValueError, AssertionError) as e:
        msg = f"{pieces[1]!r} is not a valid port number."
        raise argparse.ArgumentTypeError(msg) from e
    return HostPortPair(host, port)


def ipaddr(s: str) -> ipaddress._BaseAddress:
    try:
        ip = ipaddress.ip_address(s.strip("[]"))
    except ValueError as e:
        msg = f"{s!r} is not a valid IP address."
        raise argparse.ArgumentTypeError(msg) from e
    return ip


def path(val: str | pathlib.Path | None) -> pathlib.Path | None:
    if val is None:
        return None
    p = pathlib.Path(val)
    if not p.exists():
        msg = f"{val!r} is not a valid file/dir path."
        raise argparse.ArgumentTypeError(msg)
    return p
