"""HTTPX sync/async example. --check validates configuration without networking.

Set BP_PROXY_HOST, BP_PROXY_PORT, BP_PROXY_USER, BP_PROXY_PASSWORD.
Set BP_TARGET_URL to an HTTP(S) endpoint you control; no default external target.
Install httpx for HTTP or httpx[socks] for SOCKS5 before a network run.
"""
import argparse
import asyncio
import os
from urllib.parse import quote, urlsplit


def configuration(scheme):
    host = os.environ["BP_PROXY_HOST"]
    if not host or any(c in host for c in "/@?#:\\") or any(c.isspace() for c in host):
        raise ValueError("BP_PROXY_HOST must be an IPv4 address or hostname")
    port = int(os.environ["BP_PROXY_PORT"])
    if not 1 <= port <= 65535:
        raise ValueError("BP_PROXY_PORT must be between 1 and 65535")
    user = os.environ["BP_PROXY_USER"]
    password = os.environ["BP_PROXY_PASSWORD"]
    if ":" in user:
        raise ValueError("Basic authentication usernames cannot contain a colon")
    proxy = f"{scheme}://{quote(user, safe='')}:{quote(password, safe='')}@{host}:{port}"
    target = os.environ["BP_TARGET_URL"]
    parsed = urlsplit(target)
    if parsed.scheme not in {"http", "https"} or not parsed.hostname or parsed.username is not None:
        raise ValueError("BP_TARGET_URL must be an HTTP(S) URL without userinfo")
    return proxy, target


def report_error(exc):
    # Error messages and request URLs may contain secrets. Print only the type.
    print(f"request_failed type={type(exc).__name__}")


def run_sync(proxy, target):
    import httpx
    timeout = httpx.Timeout(20.0, connect=5.0, pool=5.0)
    try:
        with httpx.Client(proxy=proxy, timeout=timeout, trust_env=False,
                          follow_redirects=False) as client:
            response = client.get(target)
            response.raise_for_status()
            print(f"request_ok status={response.status_code} bytes={len(response.content)}")
        return 0
    except httpx.HTTPStatusError as exc:
        print(f"http_error status={exc.response.status_code}")
    except httpx.RequestError as exc:
        report_error(exc)
    return 1


async def run_async(proxy, target):
    import httpx
    timeout = httpx.Timeout(20.0, connect=5.0, pool=5.0)
    limits = httpx.Limits(max_connections=4, max_keepalive_connections=4)
    gate = asyncio.Semaphore(4)
    async with httpx.AsyncClient(proxy=proxy, timeout=timeout, limits=limits,
                                 trust_env=False, follow_redirects=False) as client:
        async def fetch(index):
            async with gate:
                try:
                    response = await client.get(target)
                    response.raise_for_status()
                    print(f"request_ok job={index} status={response.status_code}")
                    return True
                except httpx.HTTPStatusError as exc:
                    print(f"http_error job={index} status={exc.response.status_code}")
                except httpx.RequestError as exc:
                    report_error(exc)
                return False
        results = await asyncio.gather(*(fetch(i) for i in range(4)))
    return 0 if all(results) else 1


def main():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--scheme", choices=("http", "socks5"), default="http")
    parser.add_argument("--async", dest="asynchronous", action="store_true")
    parser.add_argument("--check", action="store_true")
    args = parser.parse_args()
    try:
        proxy, target = configuration(args.scheme)
    except (KeyError, ValueError):
        print("configuration_error: check required environment variables and URL format")
        return 2
    if args.check:
        print(f"configuration_ok scheme={args.scheme} credentials=redacted network=none")
        return 0
    return asyncio.run(run_async(proxy, target)) if args.asynchronous else run_sync(proxy, target)


if __name__ == "__main__":
    raise SystemExit(main())
