import json
from urllib.parse import urlencode
from urllib.request import Request, build_opener, ProxyHandler

BASE = "http://127.0.0.1:8766"
OPENER = build_opener(ProxyHandler({}))

def validate_page(page):
    if not isinstance(page, dict):
        raise ValueError("响应应当是对象")
    if not isinstance(page.get("items"), list):
        raise ValueError("items应当是列表")
    if type(page.get("total")) is not int or page["total"] < 0:
        raise ValueError("total应当是非负整数")
    if not isinstance(page.get("snapshot"), str) or not page["snapshot"]:
        raise ValueError("缺少有效快照标记")
    if "next_cursor" not in page:
        raise ValueError("缺少后续位置字段")
    cursor = page["next_cursor"]
    if cursor is not None and (not isinstance(cursor, str) or not cursor):
        raise ValueError("后续位置应当是非空文本或null")
    for item in page["items"]:
        if not isinstance(item, dict):
            raise ValueError("活动记录应当是对象")
        for key in ("activity_id", "title"):
            if not isinstance(item.get(key), str) or not item[key]:
                raise ValueError("活动编号和名称应当是非空文本")
        if type(item.get("remaining")) is not int or item["remaining"] < 0:
            raise ValueError("剩余名额应当是非负整数")

def get_page(params):
    url = BASE + "/activities?" + urlencode(params)
    request = Request(url, method="GET")
    with OPENER.open(request, timeout=3) as response:
        if response.status != 200:
            raise ValueError("活动查询未返回约定的200状态")
        if response.headers.get_content_type() != "application/json":
            raise ValueError("活动查询未返回约定的JSON类型")
        raw = response.read()
    page = json.loads(raw.decode("utf-8"))
    validate_page(page)
    return page

def get_all(q=""):
    params = {"q": q}
    items = []
    used_cursors = set()
    expected_total = None
    snapshot = None

    while True:
        page = get_page(params)
        if expected_total is None:
            expected_total = page["total"]
            snapshot = page["snapshot"]
        elif page["total"] != expected_total or page["snapshot"] != snapshot:
            raise ValueError("分页途中总数或快照发生变化")
        items.extend(page["items"])
        cursor = page["next_cursor"]
        if cursor is None:
            break
        if cursor in used_cursors:
            raise ValueError("服务重复给出了已使用的后续位置")
        used_cursors.add(cursor)
        params["cursor"] = cursor

    ids = [item["activity_id"] for item in items]
    if len(items) != expected_total or len(set(ids)) != len(ids):
        raise ValueError("收集数量与总数不符，或出现重复活动编号")
    return {"items": items, "total": expected_total, "snapshot": snapshot}
