"""
Phase 85: Crypto Identity — Cross-node cryptographic identity verification.

Each agent gets an HMAC-SHA256 signing key. Messages and attestations can be
signed and verified without requiring external PKI infrastructure.

Storage:
  state/broker/identities/
    {agent_id}.json        — identity record (with secret key)
    attestations.jsonl     — signed attestation log
"""

import hashlib
import hmac
import json
import logging
import os
import time
import uuid
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, _read_jsonl_sync, _append_jsonl_sync

logger = logging.getLogger(__name__)

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

IDENTITIES_DIR = BROKER_DIR / "identities"
ATTESTATIONS_FILE = IDENTITIES_DIR / "attestations.jsonl"

for _d in (IDENTITIES_DIR,):
    _d.mkdir(parents=True, exist_ok=True)


def reset() -> None:
    """Reset. Used by tests."""
    pass


def _now() -> str:
    return datetime.now(timezone.utc).isoformat()


def _identity_file(agent_id: str) -> Path:
    safe = agent_id.replace("/", "_").replace("\\", "_")
    return IDENTITIES_DIR / f"{safe}.json"


def _load_identity(agent_id: str) -> Optional[dict]:
    f = _identity_file(agent_id)
    if not f.exists():
        return None
    try:
        return json.loads(f.read_text("utf-8"))
    except (json.JSONDecodeError, OSError):
        return None


def _save_identity(identity: dict) -> None:
    _write_json_sync(_identity_file(identity["agent_id"]), identity)


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

def create_identity(agent_id: str, display_name: str) -> dict:
    """Generate a signing key pair (HMAC secret). Returns identity with public identifier."""
    existing = _load_identity(agent_id)
    if existing and existing.get("status") == "active":
        raise ValueError(f"Identity for '{agent_id}' already exists and is active")

    secret_key = os.urandom(32).hex()
    public_id = hashlib.sha256(secret_key.encode()).hexdigest()[:16]

    identity = {
        "agent_id": agent_id,
        "display_name": display_name,
        "public_id": public_id,
        "secret_key": secret_key,
        "created_at": _now(),
        "status": "active",
        "revoked_at": None,
        "revoke_reason": None,
    }
    _save_identity(identity)
    logger.info("Created identity for agent %s (public_id=%s)", agent_id, public_id)

    # Return without secret key for safety
    safe_copy = dict(identity)
    safe_copy.pop("secret_key", None)
    return safe_copy


def sign_message(agent_id: str, message: str) -> str:
    """Create HMAC-SHA256 signature for a message."""
    identity = _load_identity(agent_id)
    if not identity:
        raise KeyError(f"Identity for '{agent_id}' not found")
    if identity.get("status") != "active":
        raise ValueError(f"Identity for '{agent_id}' is {identity.get('status')}")

    sig = hmac.new(
        identity["secret_key"].encode(),
        message.encode(),
        hashlib.sha256,
    ).hexdigest()
    return sig


def verify_message(agent_id: str, message: str, signature: str) -> bool:
    """Verify a message was signed by the claimed agent."""
    identity = _load_identity(agent_id)
    if not identity:
        return False

    expected = hmac.new(
        identity["secret_key"].encode(),
        message.encode(),
        hashlib.sha256,
    ).hexdigest()
    return hmac.compare_digest(expected, signature)


def get_identity(agent_id: str) -> Optional[dict]:
    """Get identity (without secret key)."""
    identity = _load_identity(agent_id)
    if not identity:
        return None
    safe = dict(identity)
    safe.pop("secret_key", None)
    return safe


def list_identities() -> List[dict]:
    """List all identities (without secret keys)."""
    identities = []
    if not IDENTITIES_DIR.exists():
        return identities
    for f in sorted(IDENTITIES_DIR.glob("*.json")):
        try:
            ident = json.loads(f.read_text("utf-8"))
            if "agent_id" not in ident:
                continue
            safe = dict(ident)
            safe.pop("secret_key", None)
            identities.append(safe)
        except (json.JSONDecodeError, OSError):
            continue
    return identities


def revoke_identity(agent_id: str, reason: str = "") -> bool:
    """Revoke an identity. Signed messages will still verify but new signing is blocked."""
    identity = _load_identity(agent_id)
    if not identity:
        return False
    identity["status"] = "revoked"
    identity["revoked_at"] = _now()
    identity["revoke_reason"] = reason
    _save_identity(identity)
    logger.warning("Revoked identity for agent %s: %s", agent_id, reason)
    return True


def create_attestation(
    agent_id: str,
    claim: str,
    evidence: Optional[str] = None,
) -> dict:
    """Create a signed attestation ('I completed task X')."""
    identity = _load_identity(agent_id)
    if not identity:
        raise KeyError(f"Identity for '{agent_id}' not found")
    if identity.get("status") != "active":
        raise ValueError(f"Identity for '{agent_id}' is {identity.get('status')}")

    attestation_id = uuid.uuid4().hex[:12]
    payload = json.dumps({
        "attestation_id": attestation_id,
        "agent_id": agent_id,
        "claim": claim,
        "evidence": evidence,
        "ts": _now(),
    }, sort_keys=True)

    sig = hmac.new(
        identity["secret_key"].encode(),
        payload.encode(),
        hashlib.sha256,
    ).hexdigest()

    attestation = {
        "attestation_id": attestation_id,
        "agent_id": agent_id,
        "public_id": identity["public_id"],
        "claim": claim,
        "evidence": evidence,
        "ts": _now(),
        "payload": payload,
        "signature": sig,
    }
    _append_jsonl_sync(ATTESTATIONS_FILE, attestation)
    return attestation


def verify_attestation(attestation: dict) -> bool:
    """Verify attestation signature matches the claimed agent."""
    agent_id = attestation.get("agent_id")
    if not agent_id:
        return False

    identity = _load_identity(agent_id)
    if not identity:
        return False

    payload = attestation.get("payload", "")
    signature = attestation.get("signature", "")

    expected = hmac.new(
        identity["secret_key"].encode(),
        payload.encode(),
        hashlib.sha256,
    ).hexdigest()
    return hmac.compare_digest(expected, signature)
