#!/usr/bin/env python3
"""
Boundry Open-Source Tamper-Evident Log Verifier (CLI)
License: Apache-2.0 — Independent Audit Tool for Boundry SHA-256 Hash Chains

Usage:
    python boundry_verify.py logs/actions.jsonl
    python boundry_verify.py export.json --anchor-hash <expected_head_hash>
    python boundry_verify.py export.json --signing-key <org_signing_key>
"""

import argparse
import hashlib
import hmac
import json
import sys
from typing import List, Dict, Any, Optional


def load_entries(file_path: str) -> List[Dict[str, Any]]:
    with open(file_path, "r", encoding="utf-8") as f:
        raw = f.read().strip()
    if not raw:
        return []
    if raw.startswith("["):
        data = json.loads(raw)
        if isinstance(data, dict) and "logs" in data:
            return data["logs"]
        return data
    entries = []
    for idx, line in enumerate(raw.splitlines(), start=1):
        line = line.strip()
        if not line:
            continue
        try:
            entries.append(json.loads(line))
        except json.JSONDecodeError as exc:
            raise ValueError(f"Invalid JSON on line {idx}: {exc}") from exc
    return entries


def compute_canonical_sha256(entry: Dict[str, Any]) -> str:
    payload = {k: v for k, v in entry.items() if k not in ("hash", "signature")}
    canonical_json = json.dumps(payload, sort_keys=True)
    return hashlib.sha256(canonical_json.encode("utf-8")).hexdigest()


def verify_chain(
    entries: List[Dict[str, Any]],
    expected_anchor_hash: Optional[str] = None,
    signing_key: Optional[str] = None
) -> int:
    if not entries:
        print("[WARN] Log file contains 0 entries.")
        return 0

    print(f"[INFO] Verifying {len(entries)} log entries...")
    expected_prev = "0" * 64
    errors = 0

    for idx, entry in enumerate(entries):
        recorded_prev = entry.get("prev_hash", "")
        recorded_hash = entry.get("hash", "")

        if idx > 0 and recorded_prev != expected_prev:
            print(
                f"[FAIL] Entry #{idx + 1} (timestamp={entry.get('timestamp')}): "
                f"Chain break detected! prev_hash={recorded_prev[:16]}... != expected={expected_prev[:16]}..."
            )
            errors += 1

        recomputed = compute_canonical_sha256(entry)
        if recorded_hash and recomputed != recorded_hash:
            print(
                f"[FAIL] Entry #{idx + 1} (timestamp={entry.get('timestamp')}): "
                f"Payload tampering detected! recomputed={recomputed[:16]}... != recorded={recorded_hash[:16]}..."
            )
            errors += 1

        if signing_key and entry.get("signature"):
            expected_sig = hmac.new(
                signing_key.encode("utf-8"),
                recorded_hash.encode("utf-8"),
                hashlib.sha256
            ).hexdigest()
            if not hmac.compare_digest(expected_sig, entry["signature"]):
                print(f"[FAIL] Entry #{idx + 1}: Invalid cryptographic signature.")
                errors += 1

        expected_prev = recorded_hash

    head_hash = entries[-1].get("hash", "")
    if expected_anchor_hash and head_hash != expected_anchor_hash:
        print(
            f"[FAIL] Head hash mismatch against external anchor!\n"
            f"       Computed head : {head_hash}\n"
            f"       External anchor: {expected_anchor_hash}"
        )
        errors += 1

    if errors == 0:
        print(f"[OK] Integrity verified across {len(entries)} entries.")
        print(f"[OK] Head SHA-256 Anchor: {head_hash}")
        return 0
    else:
        print(f"[ERROR] Verification failed with {errors} integrity violation(s).")
        return 1


def main() -> int:
    parser = argparse.ArgumentParser(description="Boundry Tamper-Evident SHA-256 Log Verifier")
    parser.add_argument("file", help="Path to JSONL or JSON log export file")
    parser.add_argument("--anchor-hash", help="Expected external head hash from webhook/email anchor", default=None)
    parser.add_argument("--signing-key", help="Optional HMAC signing key to verify entry signatures", default=None)
    args = parser.parse_args()

    try:
        entries = load_entries(args.file)
    except Exception as exc:
        print(f"[ERROR] Could not read {args.file}: {exc}", file=sys.stderr)
        return 2

    return verify_chain(entries, expected_anchor_hash=args.anchor_hash, signing_key=args.signing_key)


if __name__ == "__main__":
    sys.exit(main())

