#!/usr/bin/env python3
"""
Simple HTTP server that works on both IPv4 and IPv6.
It prints the client address (src) and the local address (dst) for each request.
"""

import logging
import socket
from http.server import BaseHTTPRequestHandler, HTTPServer
from socketserver import TCPServer


## wrangled together by stackoverflow & duck.ai GPT-OSS 120B to add dualstack support
class S(BaseHTTPRequestHandler):
    def _set_response(self):
        self.send_response(200)
        self.send_header('Content-type', 'text/html')
        self.end_headers()

    def do_GET(self):
        logging.info(
            "GET request,\nPath: %s\nHeaders:\n%s\n",
            self.path,
            self.headers,
        )
        self._set_response()
        self.wfile.write("<body><pre>".encode())

        # echo request info
        self.wfile.write(
            f"GET request for {self.path} from {self.headers}\n".encode()
        )
        self.wfile.write(
            f"client (src_ip, src_port)   = {self.client_address}\n".encode()
        )
        self.wfile.write(
            f"local  (dst_ip, dst_port)   = {self.request.getsockname()}\n".encode()
        )
        self.wfile.write("</pre></body>".encode())

    def do_POST(self):
        length = int(self.headers.get('Content-Length', 0))
        post_data = self.rfile.read(length)

        logging.info(
            "POST request,\nPath: %s\nHeaders:\n%s\n\nBody:\n%s\n",
            self.path,
            self.headers,
            post_data.decode('utf-8', errors='replace')
        )
        self._set_response()
        self.wfile.write(f"POST request for {self.path}".encode())

def make_dualstack_server(host: str, port: int, handler):
    """
    Return a TCPServer that listens on both IPv4 and IPv6.
    The socket is created, configured and bound **once** by us,
    then handed to TCPServer.  We only call `server_activate()`.
    """
    # 1️⃣ IPv6 socket
    sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)

    # 2️⃣ Enable dual‑stack (IPv4‑mapped IPv6) on platforms where the
    #    default is IPv6‑only (macOS, Windows)
    sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0)

    # 3️⃣ Reuse address – convenient during development
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

    # 4️⃣ Bind the socket ourselves
    sock.bind((host, port))

    # 5️⃣ Build the server **without** the automatic bind/activate step
    server = TCPServer(
        server_address=None,          # not used because we bind manually
        RequestHandlerClass=handler,
        bind_and_activate=False,
    )
    server.socket = sock            # replace the internal socket
    # server.server_address is optional; we set it for nice repr()
    server.server_address = (host, port)

    # 6️⃣ Only activate (listen) – do NOT call server_bind() again
    server.server_activate()
    return server



# ----------------------------------------------------------------------
# Main entry point
# ----------------------------------------------------------------------
def run(host: str = "", port: int = 8080):
    """
    Starts the HTTP server.
    * host = ''  → listen on all interfaces (IPv4 & IPv6)
    * host = '::' → same as '' but explicit IPv6
    * host = '0.0.0.0' → IPv4 only (still works with dual‑stack socket)
    """
    logging.basicConfig(level=logging.INFO,
                        format='%(asctime)s %(levelname)s %(message)s')
    httpd = make_dualstack_server(host, port, S)

    logging.info(f"Starting HTTP server on {host or '::'}:{port} (dual‑stack)")
    try:
        httpd.serve_forever()
    except KeyboardInterrupt:
        logging.info("Keyboard interrupt – shutting down")
    finally:
        httpd.server_close()
        logging.info("Server stopped")


if __name__ == '__main__':
    import sys
    if len(sys.argv) == 3:
        # usage: script.py <host> <port>
        run(host=sys.argv[1], port=int(sys.argv[2]))
    elif len(sys.argv) == 2:
        # usage: script.py <port>
        run(port=int(sys.argv[1]))
    else:
        run()