#!/usr/bin/env python3
"""Credential-free ResetPulse client for public Codex reset evidence.

Standard library only. The client keeps private account data out of the request,
uses ETags for efficient polling, and verifies the public SHA-256 receipt locally.
"""

import argparse
import hashlib
import json
import urllib.error
import urllib.parse
import urllib.request

DEFAULT_BASE_URL = "https://lydiahub.online"
RECORD_KEYS = (
    "eventId", "publishedAt", "firstObservedAt", "confirmedAt", "status",
    "confidence", "detectedSeconds", "title", "summary", "claimScope",
    "claimScopeNote", "eventType", "eventTypeNote", "timingQuality",
)


def canonicalize_evidence_record(record):
    return json.dumps({key: (record or {}).get(key) for key in RECORD_KEYS}, separators=(",", ":"), ensure_ascii=False)


def verify_packet(packet):
    receipt = packet.get("receipt") if isinstance(packet, dict) else None
    expected = receipt.get("value") if isinstance(receipt, dict) else None
    record = receipt.get("record") if isinstance(receipt, dict) else None
    if not isinstance(expected, str) or not isinstance(record, dict):
        return {"valid": False, "expected": expected, "actual": None}
    digest = hashlib.sha256(canonicalize_evidence_record(record).encode("utf-8")).hexdigest()
    actual = "sha256:" + digest
    return {"valid": expected == actual, "expected": expected, "actual": actual}


def _request_json(path, base_url, etag=None, params=None):
    base = str(base_url or DEFAULT_BASE_URL).rstrip("/")
    query = urllib.parse.urlencode({key: value for key, value in (params or {}).items() if value is not None})
    url = base + path + (("?" + query) if query else "")
    headers = {"Accept": "application/json"}
    if etag:
        headers["If-None-Match"] = str(etag)
    request = urllib.request.Request(url, headers=headers, method="GET")
    try:
        with urllib.request.urlopen(request, timeout=20) as response:
            payload = json.loads(response.read().decode("utf-8"))
            return {"changed": True, "status": response.status, "etag": response.headers.get("ETag"), "payload": payload}
    except urllib.error.HTTPError as error:
        if error.code == 304:
            return {"changed": False, "status": 304, "etag": error.headers.get("ETag"), "payload": None}
        raise


def get_verified_packet(base_url=DEFAULT_BASE_URL, etag=None):
    result = _request_json("/api/verified-packet", base_url, etag=etag)
    return {"changed": result["changed"], "status": result["status"], "etag": result["etag"], "packet": result["payload"], "receipt": verify_packet(result["payload"]) if result["payload"] else None}


def get_timeline(base_url=DEFAULT_BASE_URL, since=None, limit=None, etag=None):
    result = _request_json("/api/timeline", base_url, etag=etag, params={"since": since, "limit": limit})
    return {"changed": result["changed"], "status": result["status"], "etag": result["etag"], "timeline": result["payload"]}


def is_fresh(packet, max_age_seconds=180):
    freshness = packet.get("freshness") if isinstance(packet, dict) else None
    if not isinstance(freshness, dict):
        return False
    try:
        return freshness.get("label") == "fresh" and float(freshness.get("ageSeconds")) <= float(max_age_seconds)
    except (TypeError, ValueError):
        return False


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Fetch and verify the public ResetPulse Codex reset packet")
    parser.add_argument("--base-url", default=DEFAULT_BASE_URL)
    parser.add_argument("--timeline", action="store_true", help="also fetch the incremental public timeline")
    parser.add_argument("--since", help="ISO-8601 timestamp for incremental timeline polling")
    parser.add_argument("--limit", type=int, default=10)
    args = parser.parse_args()
    packet_result = get_verified_packet(args.base_url)
    output = {"packet": packet_result["packet"], "receipt": packet_result["receipt"], "fresh": is_fresh(packet_result["packet"])}
    if args.timeline:
        output["timeline"] = get_timeline(args.base_url, since=args.since, limit=args.limit)
    print(json.dumps(output, indent=2, ensure_ascii=False))
