"""Offline client-observation accounting; no inference or business-effect claims."""
from collections import Counter, defaultdict
from datetime import datetime
import json
from pathlib import Path
import sys

FIELDS = {"logical_request_id", "attempt_id", "started_at", "observed_at", "outcome", "observation"}
OUTCOMES = ("success_response", "error_response", "unknown")


def _timestamp(value):
    if not isinstance(value, str):
        raise ValueError("Timestamps must be timezone-aware ISO strings")
    try:
        parsed = datetime.fromisoformat(value.replace("Z", "+00:00"))
    except ValueError as exc:
        raise ValueError("Invalid ISO timestamp") from exc
    if parsed.tzinfo is None or parsed.utcoffset() is None:
        raise ValueError("Timestamps must include a timezone")
    return parsed


def summarize_attempts(records):
    """Count one frozen observation per attempt, without mutating input.

    'success_response' means the client received a protocol-level success, not
    that the answer is correct or that a remote business effect occurred once.
    Unknown attempts remain unknown even if a sibling has a success response.
    """
    if not isinstance(records, list):
        raise ValueError("Attempt records must be a list")
    seen = set()
    groups = defaultdict(Counter)
    attempts = Counter({outcome: 0 for outcome in OUTCOMES})
    for record in records:
        if not isinstance(record, dict) or set(record) != FIELDS:
            raise ValueError("Each attempt must have exactly the documented fields")
        for key in ("logical_request_id", "attempt_id"):
            if not isinstance(record[key], str) or not record[key].strip():
                raise ValueError("Request and attempt IDs must be non-empty strings")
        if record["attempt_id"] in seen:
            raise ValueError("Duplicate attempt ID: reconcile the log before counting")
        seen.add(record["attempt_id"])
        outcome = record["outcome"]
        if outcome not in OUTCOMES:
            raise ValueError("Unknown outcome vocabulary")
        observation = record["observation"]
        if observation not in ("response", "timeout", "connection_lost"):
            raise ValueError("Unknown observation vocabulary")
        if (outcome == "unknown") != (observation in ("timeout", "connection_lost")):
            raise ValueError("Response outcomes and transport observations disagree")
        if _timestamp(record["observed_at"]) < _timestamp(record["started_at"]):
            raise ValueError("Observation cannot precede attempt start")
        attempts[outcome] += 1
        groups[record["logical_request_id"]][outcome] += 1

    logical = {
        "total": len(groups),
        "with_success_response": 0,
        "without_success_with_unknown": 0,
        "only_error_responses": 0,
        "with_any_unknown_attempt": 0,
        "with_multiple_attempts": 0,
        "with_multiple_success_responses": 0,
    }
    for counts in groups.values():
        if counts["success_response"]:
            logical["with_success_response"] += 1
        elif counts["unknown"]:
            logical["without_success_with_unknown"] += 1
        else:
            logical["only_error_responses"] += 1
        logical["with_any_unknown_attempt"] += bool(counts["unknown"])
        logical["with_multiple_attempts"] += sum(counts.values()) > 1
        logical["with_multiple_success_responses"] += counts["success_response"] > 1
    return {"attempts": {"total": len(records), **attempts}, "logical_requests": logical}


def main():
    path = Path(sys.argv[1]) if len(sys.argv) == 2 else Path(__file__).with_name("synthetic-request-attempts.json")
    if len(sys.argv) > 2:
        raise SystemExit("Usage: python3 request_accounting.py [synthetic-fixture.json]")
    fixture = json.loads(path.read_text(encoding="utf-8"))
    if fixture.get("synthetic") is not True or fixture.get("status") != "synthetic_software_fixture_not_a_run":
        raise ValueError("This demonstration CLI accepts labelled synthetic fixtures only")
    print(json.dumps({"status": "synthetic_accounting_only", "summary": summarize_attempts(fixture["attempts"])}, indent=2))


if __name__ == "__main__":
    main()
