"""Structured audit logging for kv.

Every daemon operation that touches secrets gets an audit event.
JSONL format at ~/.kv/audit.jsonl — one JSON object per line.

Design principles:
  - NEVER log secret values, request bodies, or response bodies
  - Log key names, provider names, paths, status codes, durations
  - Security events (blocked commands, leaks) are first-class events
  - Each entry has a prev_hash for tamper detection
  - Human-readable via `kv audit`, machine-readable via jq
"""

import hashlib
import json
import os
import time


AUDIT_DIR = os.path.expanduser("~/.kv")
AUDIT_FILE = "audit.jsonl"


def _audit_path():
    return os.path.join(AUDIT_DIR, AUDIT_FILE)


def _last_hash():
    """Read the hash of the last audit entry for chain integrity."""
    path = _audit_path()
    if not os.path.isfile(path):
        return "genesis"
    try:
        with open(path, "r", encoding="utf-8") as f:
            last_line = None
            for line in f:
                line = line.strip()
                if line:
                    last_line = line
            if last_line:
                entry = json.loads(last_line)
                return entry.get("hash", "genesis")
    except Exception:
        pass
    return "genesis"


def _compute_hash(entry_json, prev_hash):
    """SHA-256 of prev_hash + entry content. Forms tamper-evident chain."""
    payload = f"{prev_hash}:{entry_json}"
    return hashlib.sha256(payload.encode("utf-8")).hexdigest()[:16]


def emit(event, **fields):
    """Write one audit event to the JSONL log.

    Args:
        event: event type string (see EVENT TYPES below)
        **fields: event-specific fields (key names, NOT values)

    EVENT TYPES:
        agent_start    — daemon started (pid, envs, secrets, policy)
        agent_stop     — daemon stopped (pid, uptime_s)
        api_call       — kv_api call (provider, path, method, status, duration_ms, secret_name, env)
        run_exec       — kv_run command (program, exit_code, duration_ms, env)
        run_blocked    — command blocked by sandbox (program, reason)
        leak_detected  — secret found in output (count)
        leak_file      — secret found in file, deleted (path, secret_name)
        list_query     — kv_list called (env, count)
        status_query   — kv_status called
    """
    os.makedirs(AUDIT_DIR, mode=0o700, exist_ok=True)

    prev_hash = _last_hash()

    entry = {
        "ts": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
        "event": event,
        **fields,
    }

    entry_json = json.dumps(entry, ensure_ascii=False, separators=(",", ":"))
    entry["hash"] = _compute_hash(entry_json, prev_hash)
    entry["prev"] = prev_hash

    final = json.dumps(entry, ensure_ascii=False, separators=(",", ":"))

    try:
        path = _audit_path()
        with open(path, "a", encoding="utf-8") as f:
            f.write(final + "\n")
        # Restrict permissions — audit contains key names (not values)
        if os.name != "nt":
            os.chmod(path, 0o600)
    except Exception:
        pass


def read_events(limit=50, event_filter=None, provider_filter=None,
                security_only=False):
    """Read audit events from the log.

    Args:
        limit: max events to return (most recent first)
        event_filter: only return events of this type
        provider_filter: only return events for this provider
        security_only: only return security events (blocked, leak)

    Returns list of dicts, most recent first.
    """
    path = _audit_path()
    if not os.path.isfile(path):
        return []

    security_events = {"run_blocked", "leak_detected", "leak_file"}
    events = []

    try:
        with open(path, "r", encoding="utf-8") as f:
            for line in f:
                line = line.strip()
                if not line:
                    continue
                try:
                    entry = json.loads(line)
                except json.JSONDecodeError:
                    continue

                if event_filter and entry.get("event") != event_filter:
                    continue
                if provider_filter and entry.get("provider") != provider_filter:
                    continue
                if security_only and entry.get("event") not in security_events:
                    continue

                events.append(entry)
    except Exception:
        pass

    # Most recent first, limited
    return events[-limit:][::-1]


def verify_chain():
    """Verify the audit log hash chain for tampering.

    Returns (ok, details):
        ok: True if chain is intact
        details: human-readable summary
    """
    path = _audit_path()
    if not os.path.isfile(path):
        return True, "no audit log"

    prev_hash = "genesis"
    count = 0
    broken_at = None

    try:
        with open(path, "r", encoding="utf-8") as f:
            for i, line in enumerate(f, 1):
                line = line.strip()
                if not line:
                    continue
                try:
                    entry = json.loads(line)
                except json.JSONDecodeError:
                    broken_at = i
                    break

                recorded_prev = entry.get("prev", "")
                if recorded_prev != prev_hash:
                    broken_at = i
                    break

                # Recompute hash: remove hash and prev fields, then hash
                recorded_hash = entry.pop("hash", "")
                entry.pop("prev", None)
                entry_json = json.dumps(entry, ensure_ascii=False,
                                        separators=(",", ":"))
                expected_hash = _compute_hash(entry_json, prev_hash)

                if recorded_hash != expected_hash:
                    broken_at = i
                    break

                prev_hash = recorded_hash
                count += 1
    except Exception as e:
        return False, f"read error: {e}"

    if broken_at:
        return False, f"chain broken at entry {broken_at} ({count} valid before it)"

    return True, f"{count} entries, chain intact"


def summary_stats():
    """Compute summary statistics from the audit log.

    Returns dict with counts by event type, provider usage, security events.
    """
    events = read_events(limit=10000)
    if not events:
        return {"total": 0}

    by_event = {}
    by_provider = {}
    security_count = 0
    total_api_ms = 0
    api_count = 0

    security_types = {"run_blocked", "leak_detected", "leak_file"}

    for e in events:
        evt = e.get("event", "unknown")
        by_event[evt] = by_event.get(evt, 0) + 1

        if evt in security_types:
            security_count += 1

        prov = e.get("provider")
        if prov:
            by_provider[prov] = by_provider.get(prov, 0) + 1

        if evt == "api_call" and "duration_ms" in e:
            total_api_ms += e["duration_ms"]
            api_count += 1

    first_ts = events[-1].get("ts", "?") if events else "?"
    last_ts = events[0].get("ts", "?") if events else "?"

    return {
        "total": len(events),
        "by_event": by_event,
        "by_provider": by_provider,
        "security_events": security_count,
        "avg_api_ms": int(total_api_ms / api_count) if api_count else 0,
        "first": first_ts,
        "last": last_ts,
    }
