#!/usr/bin/env python3
# Copyright (c) 2026 PULSE Contributors. Proprietary. All rights reserved.
# See LICENSE file for details.
"""
Metacognitive Audit: Find stale, unused, or low-quality knowledge for pruning.

Audits:
1. Stale lessons (>30 days, never referenced in sessions)
2. Stale failures (>30 days, never referenced)
3. Low-evidence schemas (confidence < 0.5 or evidence_count < 2)
4. Failing procedures (success_rate < 50%)

Output: Evidence/Metrics/metacognitive_audit.json

Usage:
    python metacognitive_audit.py [--verbose] [--json]

Dependencies: Python stdlib + brain_utils (internal)
"""
import argparse
import io
import json
import os
import re
import sys
from datetime import datetime, timezone, timedelta
from pathlib import Path

if os.name == 'nt' and __name__ == '__main__':
    sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
    sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8')

SCRIPT_DIR = Path(__file__).resolve().parent
ROOT_DIR = SCRIPT_DIR.parent.parent.parent

from pulse.core.brain_utils import (
    HIPPOCAMPUS_DIR, load_json_dict, load_json, save_json,
    now_iso as _now_iso,
)

AUDIT_FILE = HIPPOCAMPUS_DIR / "Evidence" / "Metrics" / "metacognitive_audit.json"
LESSONS_FILE = HIPPOCAMPUS_DIR / "Lessons" / "auto_lessons.md"
FAILS_FILE = HIPPOCAMPUS_DIR / "Failures" / "auto_fails.md"
SCHEMAS_FILE = HIPPOCAMPUS_DIR / "Patterns" / "schemas.json"
PROCEDURAL_LOG = HIPPOCAMPUS_DIR / "Evidence" / "Metrics" / "procedural_log.json"
SESSIONS_FILE = HIPPOCAMPUS_DIR / "Evidence" / "Sessions" / "agent_sessions.json"


def _parse_md_entries(filepath):
    """Parse markdown entries, returning list of {title, date, body}."""
    if not filepath.exists():
        return []

    text = filepath.read_text(encoding="utf-8", errors="replace")
    sections = text.split("\n## ")
    entries = []

    for section in sections[1:]:  # Skip preamble
        lines = section.strip().split("\n")
        title = lines[0].strip()
        body = "\n".join(lines[1:]).strip()

        date_match = re.search(r"\*\*Date:\*\*\s*(\d{4}-\d{2}-\d{2})", body)
        date_str = date_match.group(1) if date_match else ""

        entries.append({
            "title": title[:80],
            "date": date_str,
            "body_preview": body[:100],
            "disputed": "[DISPUTED]" in title,
        })

    return entries


def _get_session_references():
    """Get all text referenced in agent sessions (learnings + failures)."""
    sessions = load_json(SESSIONS_FILE)
    if not isinstance(sessions, list):
        return set()

    refs = set()
    for s in sessions:
        for text in s.get("learnings", []):
            if text:
                refs.add(text.strip().lower()[:60])
        for text in s.get("failures", []):
            if text:
                refs.add(text.strip().lower()[:60])
    return refs


def audit_stale_lessons(days=30, verbose=False):
    """Find lessons older than N days that were never referenced in sessions."""
    entries = _parse_md_entries(LESSONS_FILE)
    refs = _get_session_references()
    cutoff = datetime.now(timezone.utc) - timedelta(days=days)
    stale = []

    for entry in entries:
        if entry["disputed"]:
            stale.append({**entry, "reason": "disputed"})
            continue

        date_str = entry["date"]
        if not date_str:
            continue

        try:
            entry_date = datetime.strptime(date_str, "%Y-%m-%d").replace(tzinfo=timezone.utc)
        except ValueError:
            continue

        if entry_date >= cutoff:
            continue  # Not old enough

        # Check if referenced
        title_key = entry["title"].strip().lower()[:60]
        if title_key not in refs:
            stale.append({**entry, "reason": f"unreferenced, {days}+ days old"})

    if verbose:
        print(f"[AUDIT] Stale lessons: {len(stale)} of {len(entries)}")

    return stale


def audit_stale_failures(days=30, verbose=False):
    """Find failures older than N days that were never referenced."""
    entries = _parse_md_entries(FAILS_FILE)
    refs = _get_session_references()
    cutoff = datetime.now(timezone.utc) - timedelta(days=days)
    stale = []

    for entry in entries:
        if entry["disputed"]:
            stale.append({**entry, "reason": "disputed"})
            continue

        date_str = entry["date"]
        if not date_str:
            continue

        try:
            entry_date = datetime.strptime(date_str, "%Y-%m-%d").replace(tzinfo=timezone.utc)
        except ValueError:
            continue

        if entry_date >= cutoff:
            continue

        title_key = entry["title"].strip().lower()[:60]
        if title_key not in refs:
            stale.append({**entry, "reason": f"unreferenced, {days}+ days old"})

    if verbose:
        print(f"[AUDIT] Stale failures: {len(stale)} of {len(entries)}")

    return stale


def audit_low_evidence_schemas(verbose=False):
    """Find schemas with low confidence or insufficient evidence."""
    data = load_json_dict(SCHEMAS_FILE)
    schemas = data.get("schemas", [])
    weak = []

    for s in schemas:
        issues = []
        if s.get("confidence", 0) < 0.5:
            issues.append(f"low confidence ({s.get('confidence', 0):.2f})")
        if s.get("evidence_count", 0) < 2:
            issues.append(f"low evidence ({s.get('evidence_count', 0)})")

        if issues:
            weak.append({
                "schema_id": s.get("schema_id", "?"),
                "name": s.get("name", "?")[:60],
                "confidence": s.get("confidence", 0),
                "evidence_count": s.get("evidence_count", 0),
                "issues": issues,
            })

    if verbose:
        print(f"[AUDIT] Weak schemas: {len(weak)} of {len(schemas)}")

    return weak


def audit_failing_procedures(verbose=False):
    """Find procedures with <50% success rate."""
    data = load_json_dict(PROCEDURAL_LOG)
    procedures = data.get("procedures", [])
    failing = []

    for p in procedures:
        executions = p.get("executions", 0)
        if executions < 2:
            continue  # Not enough data

        success_rate = p.get("success_rate", 0)
        if success_rate < 0.5:
            failing.append({
                "procedure_id": p.get("procedure_id", "?"),
                "name": p.get("name", "?"),
                "success_rate": success_rate,
                "executions": executions,
                "domain": p.get("domain", "general"),
            })

    if verbose:
        print(f"[AUDIT] Failing procedures: {len(failing)} of {len(procedures)}")

    return failing


def run_audit(verbose=False):
    """Run full metacognitive audit and save results."""
    print("[AUDIT] Running metacognitive audit...")

    stale_lessons = audit_stale_lessons(verbose=verbose)
    stale_failures = audit_stale_failures(verbose=verbose)
    weak_schemas = audit_low_evidence_schemas(verbose=verbose)
    failing_procs = audit_failing_procedures(verbose=verbose)

    report = {
        "timestamp": _now_iso(),
        "summary": {
            "stale_lessons": len(stale_lessons),
            "stale_failures": len(stale_failures),
            "weak_schemas": len(weak_schemas),
            "failing_procedures": len(failing_procs),
            "total_issues": len(stale_lessons) + len(stale_failures) + len(weak_schemas) + len(failing_procs),
        },
        "stale_lessons": stale_lessons[:20],
        "stale_failures": stale_failures[:20],
        "weak_schemas": weak_schemas[:10],
        "failing_procedures": failing_procs[:10],
    }

    AUDIT_FILE.parent.mkdir(parents=True, exist_ok=True)
    save_json(AUDIT_FILE, report)

    print(f"\n[AUDIT] Complete:")
    print(f"  Stale lessons:      {len(stale_lessons)}")
    print(f"  Stale failures:     {len(stale_failures)}")
    print(f"  Weak schemas:       {len(weak_schemas)}")
    print(f"  Failing procedures: {len(failing_procs)}")
    print(f"  Report: {AUDIT_FILE}")

    return report


def main():
    parser = argparse.ArgumentParser(description="PULSE Metacognitive Audit")
    parser.add_argument("--verbose", "-v", action="store_true", help="Verbose output")
    parser.add_argument("--json", action="store_true", help="Output as JSON")
    args = parser.parse_args()

    report = run_audit(verbose=args.verbose)

    if args.json:
        print(json.dumps(report, indent=2))


if __name__ == "__main__":
    main()
