import t, c
from stdint import *
import string
import socket
import memhub
import stdio


# ============================================================
# requests.py — HTTP client library (Python original style)
#
# Usage:
#     requests._mbuddy = my_mbuddy
#     resp = requests.get("http://example.com/path")
#     if resp is not None:
#         print(resp.status_code)
#         print(resp.text)
#     requests.release(resp)
# ============================================================

# Module-level global MBuddy pointer (assigned by the user in main)
_mbuddy: memhub.MemBuddy | t.CPtr

# Socket initialization flag
_socket_ready: bool = False

HTTP_PORT: t.CDefine = 80
HTTP_BUFSIZ: t.CDefine = 4096


# ============================================================
# Response — HTTP response object
# ============================================================

class Response:
    """HTTP response object, similar to how Python's requests.Response works.

    Allocated from _mbuddy, call release(resp) to free it after use.
    """
    status_code: INT       # HTTP status code (e.g., 200, 404)
    text: str              # Response body text (points into _raw_buf)
    body_len: INT          # Response body length
    _raw_buf: str          # Raw buffer pointer


# ============================================================
# Internal auxiliary functions
# ============================================================

def _EnsureSocketInit() -> INT:
    """Ensure Socket library is initialized (idempotent)."""
    global _socket_ready
    if _socket_ready: return 0
    result: INT = socket.SocketInit()
    if result == 0:
        _socket_ready = True
    return result


def _AllocResponse() -> Response | t.CPtr:
    """Allocate Response struct from _mbuddy and initialize it in-place."""
    if _mbuddy is None: return None
    resp: Response | t.CPtr = _mbuddy.alloc(Response.__sizeof__())
    if resp is None: return None
    resp.status_code = 0
    resp.text = None
    resp.body_len = 0
    resp._raw_buf = None
    return resp


def _ParseUrl(url: str, host_buf: str, host_size: INT, path_buf: str, path_size: INT, port: INT | t.CPtr) -> INT:
    """Parse URL and extract host, port, and path."""
    if url is None: return -1
    if host_buf is None or path_buf is None or port is None: return -1

    p: str = url

    # Skip "http://"
    if string.strncmp(p, "http://", 7) == 0:
        p += 7

    # Parse host
    host_start: str = p
    host_len: INT = 0
    while p[0] != '\0' and p[0] != ':' and p[0] != '/':
        host_len += 1
        p += 1

    if host_len == 0 or host_len >= host_size: return -1
    string.strncpy(host_buf, host_start, host_len)
    host_buf[host_len] = 0

    # Parse port
    if p[0] == ':':
        p += 1
        port_val: INT = 0
        while p[0] >= '0' and p[0] <= '9':
            port_val = port_val * 10 + INT(p[0] - '0')
            p += 1
        c.DerefAs(port, port_val)
    else:
        c.DerefAs(port, HTTP_PORT)

    # Parse path
    if p[0] == '/':
        path_start: str = p
        path_len: INT = 0
        while p[0] != '\0':
            path_len += 1
            p += 1
        if path_len >= path_size: return -1
        string.strncpy(path_buf, path_start, path_len)
        path_buf[path_len] = 0
    else:
        if path_size < 2: return -1
        path_buf[0] = '/'
        path_buf[1] = 0

    return 0


def _ParseStatusLine(resp_text: str, out_code: INT | t.CPtr) -> INT:
    """Parse HTTP status line and extract status code."""
    if resp_text is None or out_code is None: return -1

    p: str = resp_text

    # Skip "HTTP/x.x "
    while p[0] != '\0' and p[0] != ' ':
        p += 1
    if p[0] == ' ': p += 1

    # Parse status code
    code: INT = 0
    while p[0] >= '0' and p[0] <= '9':
        code = code * 10 + INT(p[0] - '0')
        p += 1

    c.DerefAs(out_code, code)

    # Find \n
    while p[0] != '\0' and p[0] != '\n':
        p += 1
    if p[0] == '\n': p += 1

    return INT(t.CUInt64T(p) - t.CUInt64T(resp_text))


def _FindBodyStart(resp_text: str, resp_len: INT) -> INT:
    """Find the start of the HTTP response body (after rnrn)."""
    if resp_text is None: return -1

    end_of_headers: str = string.strstr(resp_text, "\r\n\r\n")
    if end_of_headers is None:
        end_of_headers = string.strstr(resp_text, "\n\n")
        if end_of_headers is None:
            return -1
        return INT(t.CUInt64T(end_of_headers) - t.CUInt64T(resp_text)) + 2

    return INT(t.CUInt64T(end_of_headers) - t.CUInt64T(resp_text)) + 4


# ============================================================
# 公共 API
# ============================================================

def get(url: str) -> Response | t.CPtr:
    """HTTP GET request.

    Args:
        url: Full URL (e.g., "http://example.com/path")
    Returns:
        Pointer to a Response object (allocated from _mbuddy), returns None on failure.
    """
    if _mbuddy is None: return None

    resp: Response | t.CPtr = _AllocResponse()
    if resp is None: return None

    if _EnsureSocketInit() != 0:
        release(resp)
        return None

    # Parse URL
    host_buf: t.CArray[t.CChar, 256]
    path_buf: t.CArray[t.CChar, 1024]
    port: INT = 0

    if _ParseUrl(url, host_buf, 256, path_buf, 1024, c.Addr(port)) != 0:
        release(resp)
        return None

    # Create Socket
    sock = socket.Socket(socket.AF_INET, socket.SOCK_STREAM)
    if sock.fd == socket.INVALID_SOCKET:
        release(resp)
        return None

    if sock.connect(host_buf, port) != 0:
        sock.close()
        release(resp)
        return None

    # Build HTTP request
    req_buf: str = _mbuddy.alloc(HTTP_BUFSIZ)
    if req_buf is None:
        sock.close()
        release(resp)
        return None

    stdio.snprintf(req_buf, HTTP_BUFSIZ,
        "GET %s HTTP/1.1\r\nHost: %s\r\nConnection: close\r\n\r\n",
        path_buf, host_buf)

    req_len: INT = INT(string.strlen(req_buf))
    sent: INT = sock.send(req_buf, req_len)
    _mbuddy.free(req_buf)

    if sent <= 0:
        sock.close()
        release(resp)
        return None

    # Receive response body
    resp_buf: str = _mbuddy.alloc(HTTP_BUFSIZ)
    if resp_buf is None:
        sock.close()
        release(resp)
        return None

    total: INT = 0
    while total < HTTP_BUFSIZ - 1:
        n: INT = sock.recv(resp_buf + total, HTTP_BUFSIZ - 1 - total)
        if n <= 0: break
        total += n

    resp_buf[total] = 0
    sock.close()

    resp._raw_buf = resp_buf

    # Parse status line
    _ParseStatusLine(resp_buf, c.Addr(resp.status_code))

    # Find body start
    body_offset: INT = _FindBodyStart(resp_buf, total)
    if body_offset < 0 or body_offset >= total:
        resp.text = None
        resp.body_len = 0
    else:
        resp.text = resp_buf + body_offset
        resp.body_len = total - body_offset

    return resp


def post(url: str, data: str = None, content_type: str = "application/x-www-form-urlencoded") -> Response | t.CPtr:
    """HTTP POST request.

    Args:
        url: Full URL
        data: Request body data
        content_type: Content-Type header (default is "application/x-www-form-urlencoded")
    Returns:
        Pointer to Response object (allocated from _mbuddy), returns None on failure.
    """
    if _mbuddy is None: return None

    resp: Response | t.CPtr = _AllocResponse()
    if resp is None: return None

    if _EnsureSocketInit() != 0:
        release(resp)
        return None

    # Parse URL
    host_buf: t.CArray[t.CChar, 256]
    path_buf: t.CArray[t.CChar, 1024]
    port: INT = 0

    if _ParseUrl(url, host_buf, 256, path_buf, 1024, c.Addr(port)) != 0:
        release(resp)
        return None

    body_len: INT = INT(string.strlen(data)) if data is not None else 0
    ct: str = content_type if content_type is not None else "application/x-www-form-urlencoded"

    # Create Socket
    sock = socket.Socket(socket.AF_INET, socket.SOCK_STREAM)
    if sock.fd == socket.INVALID_SOCKET:
        release(resp)
        return None

    if sock.connect(host_buf, port) != 0:
        sock.close()
        release(resp)
        return None

    # Build HTTP request
    req_buf: str = _mbuddy.alloc(HTTP_BUFSIZ)
    if req_buf is None:
        sock.close()
        release(resp)
        return None

    stdio.snprintf(req_buf, HTTP_BUFSIZ,
        "POST %s HTTP/1.1\r\nHost: %s\r\nContent-Type: %s\r\nContent-Length: %d\r\nConnection: close\r\n\r\n",
        path_buf, host_buf, ct, body_len)

    hdr_len: INT = INT(string.strlen(req_buf))

    # Send headers + body
    sent: INT = sock.send(req_buf, hdr_len)
    if sent > 0 and body_len > 0 and data is not None:
        sock.send(data, body_len)

    _mbuddy.free(req_buf)

    # Receive response body
    resp_buf: str = _mbuddy.alloc(HTTP_BUFSIZ)
    if resp_buf is None:
        sock.close()
        release(resp)
        return None

    total: INT = 0
    while total < HTTP_BUFSIZ - 1:
        n: INT = sock.recv(resp_buf + total, HTTP_BUFSIZ - 1 - total)
        if n <= 0: break
        total += n

    resp_buf[total] = 0
    sock.close()

    resp._raw_buf = resp_buf

    _ParseStatusLine(resp_buf, c.Addr(resp.status_code))
    body_offset: INT = _FindBodyStart(resp_buf, total)
    if body_offset < 0 or body_offset >= total:
        resp.text = None
        resp.body_len = 0
    else:
        resp.text = resp_buf + body_offset
        resp.body_len = total - body_offset

    return resp


def release(resp: Response | t.CPtr):
    """Release Response object and its internal buffers.

    Args:
        resp: The Response object to release
    """
    if resp is None: return
    if _mbuddy is None: return
    if resp._raw_buf is not None:
        _mbuddy.free(resp._raw_buf)
    _mbuddy.free(t.CVoid(t.CUInt64T(resp), t.CPtr))
