#!/usr/bin/env python3
"""Independent VARION Zero-Risk verifier.

Calls the selected provider directly twice and VARION Zero-Loss Cache twice,
then compares exact request hashes, provider/model evidence, fresh-generation
IDs and provider-reported usage locally.

API keys and prompt text are never written to the report.
"""
from __future__ import annotations

import argparse
import getpass
import hashlib
import json
import os
import sys
import time
import urllib.error
import urllib.request
from typing import Any

VERSION = "1.1.0"


def request_json(
    url: str,
    headers: dict[str, str],
    payload: bytes,
    timeout: int,
) -> tuple[int, dict[str, Any], dict[str, str]]:
    req = urllib.request.Request(url, data=payload, headers=headers, method="POST")
    try:
        with urllib.request.urlopen(req, timeout=timeout) as response:
            raw = response.read()
            try:
                body = json.loads(raw)
            except Exception:
                body = {"error": raw.decode("utf-8", "replace")[:2000]}
            return response.status, body, {k.lower(): v for k, v in response.headers.items()}
    except urllib.error.HTTPError as exc:
        raw = exc.read()
        try:
            body = json.loads(raw)
        except Exception:
            body = {"error": raw.decode("utf-8", "replace")[:2000]}
        return exc.code, body, {k.lower(): v for k, v in exc.headers.items()}


def usage(provider: str, body: dict[str, Any]) -> dict[str, int]:
    u = body.get("usage") if isinstance(body, dict) else {}
    if not isinstance(u, dict):
        u = {}
    if provider == "anthropic":
        return {
            "input": int(u.get("input_tokens") or 0),
            "cached": int(u.get("cache_read_input_tokens") or 0),
            "cache_write": int(u.get("cache_creation_input_tokens") or 0),
            "output": int(u.get("output_tokens") or 0),
        }
    details = u.get("prompt_tokens_details") if isinstance(u.get("prompt_tokens_details"), dict) else {}
    return {
        "input": int(u.get("prompt_tokens") or u.get("input_tokens") or 0),
        "cached": int(details.get("cached_tokens") or 0),
        "cache_write": 0,
        "output": int(u.get("completion_tokens") or u.get("output_tokens") or 0),
    }


def provider_response_shape(provider: str, body: dict[str, Any]) -> bool:
    if not isinstance(body, dict):
        return False
    if provider == "anthropic":
        return body.get("type") == "message" and isinstance(body.get("content"), list) and isinstance(body.get("usage"), dict)
    return str(body.get("object") or "").startswith("chat.completion") and isinstance(body.get("choices"), list) and isinstance(body.get("usage"), dict)


def visible_varion_headers(headers: dict[str, str]) -> dict[str, str]:
    return {k: v for k, v in headers.items() if k.startswith("x-varion-")}


def main() -> int:
    parser = argparse.ArgumentParser(description="Independently verify VARION Zero-Loss Cache behaviour.")
    parser.add_argument("--provider", choices=["openai", "anthropic"], required=True)
    parser.add_argument("--model", required=True)
    parser.add_argument("--system", default="")
    parser.add_argument("--prompt", required=True)
    parser.add_argument("--max-tokens", type=int, default=64)
    parser.add_argument("--varion-base", default="https://api.varion.tech")
    parser.add_argument("--provider-key", default="")
    parser.add_argument("--varion-key", default="")
    parser.add_argument("--timeout", type=int, default=180)
    parser.add_argument("--output", default="", help="Optional JSON report path. Keys and prompt text are never included.")
    args = parser.parse_args()

    provider_key = args.provider_key or os.environ.get("VARION_PROVIDER_API_KEY", "") or getpass.getpass("Provider API key: ")
    varion_key = args.varion_key or os.environ.get("VARION_API_KEY", "") or getpass.getpass("VARION API key: ")

    if args.provider == "anthropic":
        body: dict[str, Any] = {
            "model": args.model,
            "max_tokens": args.max_tokens,
            "messages": [{"role": "user", "content": args.prompt}],
        }
        if args.system:
            body["system"] = args.system
        direct_url = "https://api.anthropic.com/v1/messages"
        direct_headers = {
            "content-type": "application/json",
            "x-api-key": provider_key,
            "anthropic-version": "2023-06-01",
        }
        varion_url = args.varion_base.rstrip("/") + "/v1/messages"
        varion_headers = {
            "content-type": "application/json",
            "authorization": "Bearer " + varion_key,
            "x-upstream-api-key": provider_key,
            "anthropic-version": "2023-06-01",
            "x-varion-mode": "zero_loss_cache",
        }
    else:
        messages = ([{"role": "system", "content": args.system}] if args.system else []) + [
            {"role": "user", "content": args.prompt}
        ]
        body = {"model": args.model, "messages": messages, "max_tokens": args.max_tokens, "stream": False}
        direct_url = "https://api.openai.com/v1/chat/completions"
        direct_headers = {"content-type": "application/json", "authorization": "Bearer " + provider_key}
        varion_url = args.varion_base.rstrip("/") + "/openai/v1/chat/completions"
        varion_headers = {
            "content-type": "application/json",
            "authorization": "Bearer " + varion_key,
            "x-upstream-api-key": provider_key,
            "x-varion-provider": "openai",
            "x-varion-mode": "zero_loss_cache",
        }

    payload = json.dumps(body, ensure_ascii=False, separators=(",", ":")).encode("utf-8")
    local_hash = hashlib.sha256(payload).hexdigest()
    calls: list[dict[str, Any]] = []

    for name, url, headers in (
        ("direct_cold", direct_url, direct_headers),
        ("direct_warm", direct_url, direct_headers),
        ("varion_cold", varion_url, varion_headers),
        ("varion_warm", varion_url, varion_headers),
    ):
        print(f"Running {name}…", flush=True)
        status, response, response_headers = request_json(url, headers, payload, args.timeout)
        if status < 200 or status >= 300:
            print(json.dumps(response, indent=2), file=sys.stderr)
            return 2
        calls.append(
            {
                "name": name,
                "status": status,
                "response_id": str(response.get("id") or ""),
                "response_model": str(response.get("model") or ""),
                "provider_response_shape_valid": provider_response_shape(args.provider, response),
                "usage": usage(args.provider, response),
                "headers": visible_varion_headers(response_headers),
            }
        )
        time.sleep(0.4)

    warm = calls[-1]
    warm_headers = warm["headers"]
    reported_hash = str(warm_headers.get("x-varion-prompt-content-hash") or "")
    upstream_hash = str(warm_headers.get("x-varion-upstream-content-hash") or "")
    all_ids = [str(call["response_id"]) for call in calls]
    all_models = [str(call["response_model"]) for call in calls]
    direct_models = [item for item in all_models[:2] if item]
    varion_models = [item for item in all_models[2:] if item]
    reference_model = direct_models[0] if direct_models else ""

    prompt_content_unchanged = bool(
        reported_hash == local_hash
        and upstream_hash == local_hash
        and str(warm_headers.get("x-varion-prompt-content-unchanged") or "").lower() == "true"
        and str(warm_headers.get("x-varion-mode") or "") == "zero_loss_cache"
        and float(warm_headers.get("x-varion-context-reduction-percent") or 0) == 0.0
    )
    provider_unchanged = bool(
        all(call["provider_response_shape_valid"] for call in calls)
        and ((args.provider == "anthropic" and varion_url.endswith("/v1/messages")) or (args.provider == "openai" and "/openai/v1/chat/completions" in varion_url))
    )
    model_unchanged = bool(
        reference_model
        and len(direct_models) == 2
        and len(varion_models) == 2
        and all(item == reference_model for item in direct_models + varion_models)
    )
    fresh_generation = bool(all(all_ids) and len(set(all_ids)) == len(all_ids))

    report: dict[str, Any] = {
        "version": VERSION,
        "provider": args.provider,
        "requested_model": args.model,
        "provider_returned_model": reference_model,
        "prompt_content_included": False,
        "keys_included": False,
        "local_prompt_hash": local_hash,
        "varion_prompt_hash": reported_hash,
        "varion_upstream_hash": upstream_hash,
        "prompt_content_unchanged": prompt_content_unchanged,
        "provider_route_verified": provider_unchanged,
        "model_unchanged": model_unchanged,
        "fresh_generation": fresh_generation,
        "semantic_response_reuse_detected": not fresh_generation,
        "fallback_applied": str(warm_headers.get("x-varion-fallback-applied") or "false").lower() == "true",
        "varion_verification_status": str(warm_headers.get("x-varion-verification-status") or ""),
        "varion_verified_savings_percent": str(warm_headers.get("x-varion-verified-savings-percent") or ""),
        "varion_verified_savings_amount": str(warm_headers.get("x-varion-verified-savings-amount") or ""),
        "calls": calls,
    }
    report["verdict"] = (
        "PASS"
        if report["prompt_content_unchanged"]
        and report["provider_route_verified"]
        and report["model_unchanged"]
        and report["fresh_generation"]
        else "NOT VERIFIED"
    )

    print(json.dumps(report, indent=2))
    if args.output:
        with open(args.output, "w", encoding="utf-8") as handle:
            json.dump(report, handle, indent=2)
        print(f"Saved {args.output}")
    return 0 if report["verdict"] == "PASS" else 3


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