#!/usr/bin/env python3
# Copyright (c) 2026 PULSE Contributors. Proprietary. All rights reserved.
"""D9: Epistemic Foraging Daemon — Curiosity-Driven Knowledge Validation.

Pipeline: AUDIT → RANK → SEARCH → ANALYZE → PROPOSE. 24h interval, 5min timeout.
"""
from __future__ import annotations

import io, json, os, re, sys, time
from datetime import datetime, timezone
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")

from pulse.core.paths import get_root_dir
from pulse.core.brain_io import _acquire
from pulse.daemons.synthesis.epistemic_search import web_search

ROOT = get_root_dir()
STATE_FILE = ROOT / "state" / "epistemic_forager_state.json"
PROPOSALS_DIR = ROOT / "Prefrontal_Cortex" / "Working_Memory" / "Proposals"
SEARCH_DELAY, MAX_ITEMS, HARD_DEADLINE = 5, 10, 270

ANALYSIS_PROMPT = """Verify whether this brain knowledge entry is still accurate.
BRAIN ENTRY — Title: {title} | Content: {body}
WEB RESULTS:
{web_snippets}
Output ONLY JSON: {{"verdict": "<confirm|update|prune|inconclusive>", "confidence": <0.0-1.0>, "reasoning": "<1-2 sentences>", "suggested_text": "<new text if update, else empty>"}}
confirm=still valid, update=partially outdated, prune=obsolete, inconclusive=can't determine."""


def _formulate_query(item: dict) -> str:
    """Use LLM (fast) to create a search query, fallback to title cleanup."""
    title, body = item.get("title", ""), item.get("body_preview", "")
    try:
        from pulse.core.llm_router import dispatch
        raw = dispatch(
            f"Given this brain knowledge entry, formulate a concise web search query "
            f"(3-8 words) to verify if it is still current and accurate.\n"
            f"Title: {title}\nExcerpt: {body}\nOutput ONLY the search query.",
            task_type="fast",
        )
        query = raw.strip().strip('"').strip("'")
        if 3 <= len(query) <= 120:
            return query
    except Exception:
        pass
    cleaned = re.sub(r"\[.*?\]", "", title)
    cleaned = re.sub(r"\d{4}-\d{2}-\d{2}", "", cleaned)
    return cleaned.strip()[:80] or "PULSE knowledge validation"


def _analyze(item: dict, search_results: list[dict]) -> dict:
    """LLM compares brain entry vs web snippets. Returns verdict dict."""
    inconclusive = {"verdict": "inconclusive", "confidence": 0.0,
                    "reasoning": "No web results found.", "suggested_text": ""}
    if not search_results:
        return inconclusive
    snippets_text = "\n".join(
        f"  [{i+1}] {r['title']}: {r['snippet'][:200]}" for i, r in enumerate(search_results)
    )
    prompt = ANALYSIS_PROMPT.format(
        title=item.get("title", ""),
        body=item.get("body_preview", item.get("name", "")),
        web_snippets=snippets_text,
    )
    try:
        from pulse.core.llm_router import dispatch, extract_json
        parsed = extract_json(dispatch(prompt, task_type="fast"))
        verdict = parsed.get("verdict", "inconclusive")
        if verdict not in ("confirm", "update", "prune", "inconclusive"):
            verdict = "inconclusive"
        return {
            "verdict": verdict,
            "confidence": min(1.0, max(0.0, float(parsed.get("confidence", 0.0)))),
            "reasoning": str(parsed.get("reasoning", ""))[:300],
            "suggested_text": str(parsed.get("suggested_text", ""))[:500],
        }
    except Exception as e:
        return {"verdict": "inconclusive", "confidence": 0.0,
                "reasoning": f"LLM failed: {e}", "suggested_text": ""}


def _score_item(item: dict, category: str) -> float:
    """Compute priority score for an audit finding."""
    score = 0.0
    date_str = item.get("date", "")
    if date_str:
        try:
            age = (datetime.now(timezone.utc) - datetime.fromisoformat(
                date_str).replace(tzinfo=timezone.utc)).days
            score += min((age - 30) / 30.0, 3.0)
        except (ValueError, TypeError):
            score += 1.0
    else:
        score += 1.0
    if item.get("disputed"):
        score += 2.0
    if category == "weak_schema":
        score += len(item.get("issues", [])) * 1.0
        score += (1.0 - item.get("confidence", 1.0)) * 2.0
    elif category == "failing_procedure":
        score += (1.0 - item.get("success_rate", 1.0)) * 2.0
    return round(score, 2)


def _rank_items(audit: dict) -> list[dict]:
    """Flatten audit findings, score, return top MAX_ITEMS."""
    items = []
    for e in audit.get("stale_lessons", []):
        items.append({**e, "category": "stale_lesson", "score": _score_item(e, "stale_lesson")})
    for e in audit.get("stale_failures", []):
        items.append({**e, "category": "stale_failure", "score": _score_item(e, "stale_failure")})
    for e in audit.get("weak_schemas", []):
        items.append({**e, "category": "weak_schema", "score": _score_item(e, "weak_schema"),
                      "title": e.get("name", e.get("schema_id", "?")),
                      "body_preview": f"confidence={e.get('confidence')}, issues={e.get('issues')}"})
    for e in audit.get("failing_procedures", []):
        items.append({**e, "category": "failing_procedure", "score": _score_item(e, "failing_procedure"),
                      "title": e.get("name", e.get("procedure_id", "?")),
                      "body_preview": f"success_rate={e.get('success_rate')}, domain={e.get('domain')}"})
    items.sort(key=lambda x: x["score"], reverse=True)
    return items[:MAX_ITEMS]


_VERDICT_PREFIX = {"confirm": "[CONFIRM]", "update": "[UPDATE]",
                   "prune": "[PRUNE]", "inconclusive": "[INCONCLUSIVE]"}


def _build_session(results: list[dict]) -> dict | None:
    """Build a proposals session from forage results."""
    actionable = [r for r in results if r["verdict"] != "inconclusive"]
    if not actionable:
        return None
    now = datetime.now(timezone.utc)
    ideas = []
    for r in actionable:
        idea_text = r["reasoning"]
        if r.get("suggested_text"):
            idea_text += f"\n\nSuggested update: {r['suggested_text']}"
        ideas.append({
            "title": f"{_VERDICT_PREFIX.get(r['verdict'], '[?]')} {r['title']}",
            "idea": idea_text, "category": r["category"], "verdict": r["verdict"],
            "confidence": r["confidence"], "search_query": r.get("search_query", ""),
            "web_results_count": r.get("web_results_count", 0), "status": "pending",
        })
    return {"id": f"session_{now.strftime('%Y%m%dT%H%M%S')}", "created": now.isoformat(),
            "mode": "epistemic_foraging", "items_audited": len(results),
            "actionable": len(actionable), "ideas": ideas}


def _save_session(session: dict) -> Path:
    PROPOSALS_DIR.mkdir(parents=True, exist_ok=True)
    fp = PROPOSALS_DIR / f"{session['id']}.json"
    fp.write_text(json.dumps(session, indent=2, ensure_ascii=False), encoding="utf-8")
    return fp


def _load_state() -> dict:
    with _acquire(STATE_FILE):
        if STATE_FILE.exists():
            try:
                return json.loads(STATE_FILE.read_text(encoding="utf-8"))
            except (json.JSONDecodeError, OSError):
                pass
    return {"runs": 0, "last_run": None, "total_items": 0,
            "verdicts": {"confirm": 0, "update": 0, "prune": 0, "inconclusive": 0}}


def _save_state(state: dict):
    with _acquire(STATE_FILE):
        STATE_FILE.parent.mkdir(parents=True, exist_ok=True)
        STATE_FILE.write_text(json.dumps(state, indent=2), encoding="utf-8")


def _forage_item(item: dict, verbose: bool = False) -> dict:
    """Run SEARCH → ANALYZE for one item."""
    title = item.get("title", "unknown")
    query = _formulate_query(item)
    if verbose:
        print(f"  [{item['category']}] {title}\n    Query: {query}")
    results = web_search(query)
    if verbose:
        print(f"    Results: {len(results)}")
    analysis = _analyze(item, results)
    if verbose:
        print(f"    Verdict: {analysis['verdict']} ({analysis['confidence']:.1f})")
    return {"title": title, "category": item["category"], "search_query": query,
            "web_results_count": len(results), **analysis}


def run_once(verbose: bool = False, dry_run: bool = False) -> dict | None:
    """Execute one full epistemic foraging cycle."""
    start = time.time()
    if verbose:
        print("\n[D9] Phase 1: AUDIT")
    from pulse.admin.audit.metacognitive_audit import run_audit
    audit = run_audit(verbose=False)
    total = audit.get("summary", {}).get("total_issues", 0)
    if total == 0:
        if verbose:
            print("  No audit findings. Brain is clean.")
        return None

    if verbose:
        print(f"\n[D9] Phase 2: RANK ({total} findings)")
    ranked = _rank_items(audit)
    if verbose:
        for i, it in enumerate(ranked):
            print(f"  {i+1}. [{it['category']}] {it.get('title', '?')[:50]} (score={it['score']})")
    if dry_run:
        print(f"\n[D9] Dry run complete. {len(ranked)} items ranked.")
        return None

    if verbose:
        print(f"\n[D9] Phase 3+4: SEARCH + ANALYZE ({len(ranked)} items)")
    results = []
    for item in ranked:
        if time.time() - start > HARD_DEADLINE:
            if verbose:
                print("  Hard deadline reached.")
            break
        results.append(_forage_item(item, verbose=verbose))
        time.sleep(SEARCH_DELAY)

    if verbose:
        print("\n[D9] Phase 5: PROPOSE")
    session = _build_session(results)
    state = _load_state()
    state["runs"] += 1
    state["last_run"] = datetime.now(timezone.utc).isoformat()
    state["total_items"] += len(results)
    for r in results:
        v = r.get("verdict", "inconclusive")
        state["verdicts"][v] = state["verdicts"].get(v, 0) + 1
    if session:
        fp = _save_session(session)
        state["last_session"] = session["id"]
        if verbose:
            print(f"  Session: {fp.name} — {session['actionable']}/{session['items_audited']} actionable")
            for idea in session["ideas"]:
                print(f"    {idea['title']}")
    elif verbose:
        print("  No actionable findings — all inconclusive.")
    _save_state(state)
    if verbose:
        print(f"\n[D9] Done in {time.time() - start:.1f}s. Review: pulse proposals")
    return session


def show_status():
    state = _load_state()
    v = state.get("verdicts", {})
    print(f"\n[D9] Epistemic Forager — Runs: {state['runs']}, Last: {state.get('last_run', 'never')}")
    print(f"  Items: {state['total_items']} | confirm={v.get('confirm', 0)} update={v.get('update', 0)} "
          f"prune={v.get('prune', 0)} inconclusive={v.get('inconclusive', 0)}")
    print(f"  Last session: {state.get('last_session', 'none')}")


def cmd_forage(args: list):
    """CLI entry: pulse forage [--dry-run] [--verbose|-v] [status]."""
    if args and args[0] == "status":
        return show_status()
    dry_run = "--dry-run" in args
    verbose = "--verbose" in args or "-v" in args
    run_once(verbose=verbose, dry_run=dry_run)


def main():
    cmd_forage(sys.argv[1:])


if __name__ == "__main__":
    main()
