"""
Phase 67: Knowledge Trust — Cryptographic verification of knowledge entries.

HMAC signatures for knowledge entries, trust scoring based on signer
count and reputation, and signature revocation.

Storage:
  state/broker/knowledge_trust/
    signatures.json       — all signatures indexed by knowledge_id
    signer_reputation.json — reputation scores per signer
"""

import hashlib
import hmac
import json
import time
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Dict, List, Optional

from brynq.server import BROKER_DIR, _write_json_sync

# ── Paths ──────────────────────────────────────────────────────────────────

TRUST_DIR = BROKER_DIR / "knowledge_trust"
TRUST_DIR.mkdir(parents=True, exist_ok=True)

SIGNATURES_PATH = TRUST_DIR / "signatures.json"
REPUTATION_PATH = TRUST_DIR / "signer_reputation.json"

# Shared secret for HMAC — in production this would be per-agent
_HMAC_SECRET = b"brynq-knowledge-trust-v1"


# ── Helpers ────────────────────────────────────────────────────────────────

def _load_signatures() -> dict:
    if not SIGNATURES_PATH.exists():
        return {}
    try:
        return json.loads(SIGNATURES_PATH.read_text("utf-8"))
    except (json.JSONDecodeError, OSError):
        return {}


def _save_signatures(sigs: dict) -> None:
    _write_json_sync(SIGNATURES_PATH, sigs)


def _load_reputation() -> dict:
    if not REPUTATION_PATH.exists():
        return {}
    try:
        return json.loads(REPUTATION_PATH.read_text("utf-8"))
    except (json.JSONDecodeError, OSError):
        return {}


def _save_reputation(rep: dict) -> None:
    _write_json_sync(REPUTATION_PATH, rep)


def _get_entry_content(knowledge_id: str) -> Optional[str]:
    """Load knowledge entry content for signing."""
    try:
        from brynq.knowledge_mcp import KNOWLEDGE_DIR
        path = KNOWLEDGE_DIR / f"{knowledge_id}.json"
        if path.exists():
            entry = json.loads(path.read_text("utf-8"))
            return json.dumps({
                "knowledge_id": entry.get("knowledge_id"),
                "topic": entry.get("topic"),
                "content": entry.get("content"),
            }, sort_keys=True)
    except (ImportError, json.JSONDecodeError, OSError):
        pass
    return None


def _compute_hmac(content: str, signer_id: str) -> str:
    """Compute HMAC-SHA256 for content + signer."""
    key = _HMAC_SECRET + signer_id.encode()
    return hmac.new(key, content.encode(), hashlib.sha256).hexdigest()


# ── Public API ─────────────────────────────────────────────────────────────

def sign_knowledge(knowledge_id: str, signer_id: str) -> dict:
    """Create an HMAC signature for a knowledge entry.

    Args:
        knowledge_id: ID of the knowledge entry.
        signer_id: ID of the signing agent.

    Returns:
        Signature record.
    """
    if not knowledge_id:
        raise ValueError("knowledge_id is required")
    if not signer_id:
        raise ValueError("signer_id is required")

    content = _get_entry_content(knowledge_id)
    if content is None:
        raise ValueError(f"Knowledge entry '{knowledge_id}' not found")

    signature = _compute_hmac(content, signer_id)
    now = datetime.now(timezone.utc).isoformat()

    sigs = _load_signatures()
    entry_sigs = sigs.get(knowledge_id, {"signatures": [], "verification_count": 0})

    # Check if already signed by this signer
    for s in entry_sigs["signatures"]:
        if s["signer_id"] == signer_id and not s.get("revoked"):
            return {"status": "already_signed", "knowledge_id": knowledge_id,
                    "signer_id": signer_id, "signature": s["signature"]}

    record = {
        "signer_id": signer_id,
        "signature": signature,
        "signed_at": now,
        "revoked": False,
    }
    entry_sigs["signatures"].append(record)
    sigs[knowledge_id] = entry_sigs
    _save_signatures(sigs)

    # Bump signer reputation
    rep = _load_reputation()
    r = rep.get(signer_id, {"signs": 0, "revocations": 0, "score": 1.0})
    r["signs"] += 1
    rep[signer_id] = r
    _save_reputation(rep)

    return {"status": "signed", "knowledge_id": knowledge_id,
            "signer_id": signer_id, "signature": signature}


def verify_signature(knowledge_id: str, signer_id: str) -> dict:
    """Verify a signature is valid (content hasn't been tampered with).

    Returns:
        Verification result with valid/invalid status.
    """
    content = _get_entry_content(knowledge_id)
    if content is None:
        return {"valid": False, "reason": "entry_not_found"}

    sigs = _load_signatures()
    entry_sigs = sigs.get(knowledge_id, {}).get("signatures", [])

    stored_sig = None
    for s in entry_sigs:
        if s["signer_id"] == signer_id and not s.get("revoked"):
            stored_sig = s
            break

    if stored_sig is None:
        return {"valid": False, "reason": "no_signature_found"}

    expected = _compute_hmac(content, signer_id)
    is_valid = hmac.compare_digest(stored_sig["signature"], expected)

    # Bump verification count
    entry_data = sigs.get(knowledge_id, {})
    entry_data["verification_count"] = entry_data.get("verification_count", 0) + 1
    sigs[knowledge_id] = entry_data
    _save_signatures(sigs)

    return {"valid": is_valid, "knowledge_id": knowledge_id,
            "signer_id": signer_id,
            "reason": "valid" if is_valid else "signature_mismatch"}


def get_trust_score(knowledge_id: str) -> dict:
    """Calculate trust score based on signers, reputation, and verifications.

    Score formula: (num_active_signers * avg_signer_reputation + verification_bonus) / normalizer
    """
    sigs = _load_signatures()
    entry_sigs = sigs.get(knowledge_id, {"signatures": [], "verification_count": 0})
    rep = _load_reputation()

    active_signers = [s for s in entry_sigs.get("signatures", []) if not s.get("revoked")]
    num_signers = len(active_signers)
    verification_count = entry_sigs.get("verification_count", 0)

    if num_signers == 0:
        return {"knowledge_id": knowledge_id, "trust_score": 0.0,
                "signers": 0, "verifications": verification_count}

    # Average signer reputation
    total_rep = 0.0
    for s in active_signers:
        r = rep.get(s["signer_id"], {"score": 1.0})
        total_rep += r.get("score", 1.0)
    avg_rep = total_rep / num_signers

    # Score: base from signers * reputation, bonus from verifications
    base = min(num_signers * avg_rep, 5.0) / 5.0  # cap at 5 signers
    verification_bonus = min(verification_count * 0.02, 0.2)  # cap at 0.2
    trust = min(round(base + verification_bonus, 4), 1.0)

    return {
        "knowledge_id": knowledge_id,
        "trust_score": trust,
        "signers": num_signers,
        "verifications": verification_count,
        "avg_signer_reputation": round(avg_rep, 4),
    }


def get_signed_entries(signer_id: Optional[str] = None) -> List[dict]:
    """List entries signed by an agent (or all signed entries)."""
    sigs = _load_signatures()
    results = []

    for kid, entry_data in sigs.items():
        for s in entry_data.get("signatures", []):
            if signer_id and s["signer_id"] != signer_id:
                continue
            results.append({
                "knowledge_id": kid,
                "signer_id": s["signer_id"],
                "signed_at": s.get("signed_at"),
                "revoked": s.get("revoked", False),
                "signature": s["signature"][:16] + "...",
            })

    return results


def revoke_signature(knowledge_id: str, signer_id: str, reason: str = "") -> dict:
    """Revoke a signature on a knowledge entry.

    Returns:
        Revocation result.
    """
    sigs = _load_signatures()
    entry_sigs = sigs.get(knowledge_id, {}).get("signatures", [])

    for s in entry_sigs:
        if s["signer_id"] == signer_id and not s.get("revoked"):
            s["revoked"] = True
            s["revoked_at"] = datetime.now(timezone.utc).isoformat()
            s["revoke_reason"] = reason
            _save_signatures(sigs)

            # Ding reputation
            rep = _load_reputation()
            r = rep.get(signer_id, {"signs": 0, "revocations": 0, "score": 1.0})
            r["revocations"] += 1
            r["score"] = max(0.0, r["score"] - 0.1)
            rep[signer_id] = r
            _save_reputation(rep)

            return {"status": "revoked", "knowledge_id": knowledge_id,
                    "signer_id": signer_id, "reason": reason}

    return {"status": "not_found", "knowledge_id": knowledge_id,
            "signer_id": signer_id}
