"""API routes for the Phase 6 memory review surfaces.

Exposes the KnowledgeGraph's dream-review and counterfactual-audit
helpers via REST so the UI and CLI can render the review queue and
the user can confirm / reject / restore beliefs.

Also exposes prediction stats and manual dream triggers.

All routes are read-only or user-initiated mutations (not agent
writes), so they are exempt from contract enforcement -- the user
is exercising their right to oversee the agent's auto-generated
beliefs, not triggering an agent action.
"""

from __future__ import annotations

from fastapi import APIRouter, Request
from fastapi.responses import JSONResponse

router = APIRouter(tags=["memory-review"])


@router.get("/memory/dream-review")
async def dream_review_queue(request: Request, min_weight: float = 0.5, limit: int = 50, layer: str = "neocortical"):
    """Return high-importance dream-extracted triples for user review."""
    graph = getattr(request.app.state, "knowledge_graph", None)
    if not graph:
        return JSONResponse({"error": "knowledge graph not available"}, status_code=503)
    try:
        queue = graph.dream_review_queue(
            min_effective_weight=min_weight,
            limit=limit,
            layer=layer,
        )
    except Exception as e:
        return JSONResponse({"error": str(e)}, status_code=500)
    return {"queue": queue, "count": len(queue)}


@router.get("/memory/counterfactual")
async def rejected_hypotheses(request: Request, limit: int = 50, since: str | None = None):
    """Return rejected hypotheses (the counterfactual audit layer)."""
    graph = getattr(request.app.state, "knowledge_graph", None)
    if not graph:
        return JSONResponse({"error": "knowledge graph not available"}, status_code=503)
    try:
        items = graph.list_rejected_hypotheses(limit=limit, since_iso=since)
    except Exception as e:
        return JSONResponse({"error": str(e)}, status_code=500)
    return {"rejected": items, "count": len(items)}


@router.post("/memory/dream-review/batch-confirm")
async def batch_confirm_beliefs(request: Request):
    """Batch confirm dream-extracted beliefs.

    Body: ``{"triple_ids": [1, 2, 3]}``
    If ``triple_ids`` is empty or omitted, confirms all items in the review queue.
    """
    graph = getattr(request.app.state, "knowledge_graph", None)
    if not graph:
        return JSONResponse({"error": "knowledge graph not available"}, status_code=503)

    try:
        body = await request.json()
    except Exception:
        body = {}
    triple_ids = body.get("triple_ids", []) if isinstance(body, dict) else []

    # If no IDs specified, get all from the review queue
    if not triple_ids:
        try:
            queue = graph.dream_review_queue(min_effective_weight=0.0, limit=500)
            triple_ids = [item.get("triple_id") for item in queue if item.get("triple_id") is not None]
        except Exception as e:
            return JSONResponse({"error": str(e)}, status_code=500)

    confirmed = 0
    skipped = 0
    errors: list[str] = []
    confirmed_ids: list[int] = []

    for tid in triple_ids:
        try:
            ok = graph.confirm_dream_belief(tid)
            if ok:
                confirmed += 1
                confirmed_ids.append(tid)
            else:
                skipped += 1
        except Exception as exc:
            errors.append(f"triple {tid}: {exc}")

    if confirmed:
        graph.save()

    # Record ONE batch feedback event
    activity = getattr(request.app.state, "activity_logger", None)
    if activity and confirmed:
        activity.record_feedback("approval", f"Batch confirmed {confirmed} dream beliefs")

    # Feed trust: batch approval = one "good" signal for Knowledge management
    trust_svc = getattr(request.app.state, "trust_service", None)
    if trust_svc and confirmed:
        try:
            trust_svc.record_feedback("Knowledge management", "good")
        except Exception:
            pass

    # Record in review history
    review_history = getattr(request.app.state, "review_history", None)
    if review_history and confirmed:
        await review_history.record(
            "dream", ",".join(str(i) for i in confirmed_ids), "batch_confirmed",
            details={"confirmed": confirmed, "skipped": skipped},
        )

    return {"confirmed": confirmed, "skipped": skipped, "errors": errors}


@router.post("/memory/dream-review/{triple_id}/confirm")
async def confirm_belief(request: Request, triple_id: int):
    """Promote a dream-extracted belief to user-confirmed (verified).

    Accepts an optional JSON body with ``subject``, ``predicate``, and/or
    ``object`` fields to edit the triple before confirming.
    """
    graph = getattr(request.app.state, "knowledge_graph", None)
    if not graph:
        return JSONResponse({"error": "knowledge graph not available"}, status_code=503)

    # Apply edits if provided
    try:
        body = await request.json()
    except Exception:
        body = {}
    if isinstance(body, dict):
        triple = next((t for t in graph._triples if t.get("id") == triple_id), None)
        if triple:
            for field in ("subject", "predicate", "object"):
                val = body.get(field)
                if val and isinstance(val, str) and val.strip():
                    triple[field] = val.strip()

    # Capture the triple's correlation_id BEFORE confirm mutates fields —
    # the dream phase that produced this triple stamped its scope's
    # correlation_id on the row at write time, and we need it to feed
    # positive signal back into the dream-phase run's outcome.
    _triple = next((t for t in graph._triples if t.get("id") == triple_id), None)
    _confirm_corr_id = str((_triple or {}).get("correlation_id", ""))

    ok = graph.confirm_dream_belief(triple_id)
    if ok:
        graph.save()
        review_history = getattr(request.app.state, "review_history", None)
        if review_history:
            await review_history.record("dream", str(triple_id), "approved")
        # Feed trust: single approval = "good" for Knowledge management
        trust_svc = getattr(request.app.state, "trust_service", None)
        if trust_svc:
            try:
                trust_svc.record_feedback("Knowledge management", "good")
            except Exception:
                pass
        # Evolution feedback — close the dream-phase run's loop.
        evolution = getattr(request.app.state, "evolution", None)
        if evolution is not None and _confirm_corr_id:
            await evolution.record_feedback_by_correlation(_confirm_corr_id, 1.0)
        return {"confirmed": True, "triple_id": triple_id}
    return JSONResponse({"confirmed": False, "reason": "not eligible"}, status_code=400)


@router.post("/memory/dream-review/{triple_id}/reject")
async def reject_belief(request: Request, triple_id: int):
    """Reject a dream-extracted belief (mark retracted)."""
    graph = getattr(request.app.state, "knowledge_graph", None)
    if not graph:
        return JSONResponse({"error": "knowledge graph not available"}, status_code=503)
    try:
        body = await request.json()
    except Exception:
        body = {}
    reason = body.get("reason", "")
    # Capture correlation_id BEFORE the reject path mutates the triple.
    _reject_triple = next((t for t in graph._triples if t.get("id") == triple_id), None)
    _reject_corr_id = str((_reject_triple or {}).get("correlation_id", ""))

    ok = graph.reject_dream_belief(triple_id, reason=reason)
    if ok:
        graph.save()
        review_history = getattr(request.app.state, "review_history", None)
        if review_history:
            await review_history.record("dream", str(triple_id), "rejected", reason=reason)
        # Feed trust: rejection = learning signal (not catastrophic redo)
        trust_svc = getattr(request.app.state, "trust_service", None)
        if trust_svc:
            try:
                trust_svc.record_feedback("Knowledge management", "needs_improvement")
            except Exception:
                pass
        # Evolution feedback — score 0.0 on the dream-phase run's outcome.
        evolution = getattr(request.app.state, "evolution", None)
        if evolution is not None and _reject_corr_id:
            await evolution.record_feedback_by_correlation(_reject_corr_id, 0.0)
        return {"rejected": True, "triple_id": triple_id}
    return JSONResponse({"rejected": False, "reason": "not eligible"}, status_code=400)


@router.post("/memory/counterfactual/{triple_id}/restore")
async def restore_hypothesis(request: Request, triple_id: int):
    """Restore a rejected hypothesis back to contested for reconsideration."""
    graph = getattr(request.app.state, "knowledge_graph", None)
    if not graph:
        return JSONResponse({"error": "knowledge graph not available"}, status_code=503)
    ok = graph.restore_rejected_hypothesis(triple_id)
    if ok:
        graph.save()
        review_history = getattr(request.app.state, "review_history", None)
        if review_history:
            await review_history.record("dream", str(triple_id), "restored")
        return {"restored": True, "triple_id": triple_id}
    return JSONResponse({"restored": False, "reason": "not a rejected hypothesis"}, status_code=400)


@router.get("/memory/predictions/stats")
async def prediction_stats(request: Request):
    """Return aggregate prediction-loop stats (pending/confirmed/surprising/accuracy)."""
    engine = getattr(request.app.state, "prediction_engine", None)
    if not engine:
        return JSONResponse({"error": "prediction engine not available"}, status_code=503)
    return engine.stats()


@router.get("/memory/predictions/history")
async def prediction_history(request: Request, limit: int = 30):
    """Return prediction accuracy history (recorded per deep-dream cycle).

    Each entry is a snapshot of the prediction stats at the time a deep
    dream completed. The history is stored in a simple JSONL file at
    ``memory/predictions-history.jsonl`` in the vault. Each line is one
    JSON object with ``{timestamp, confirmed, surprising, pending, accuracy}``.
    """
    graph = getattr(request.app.state, "knowledge_graph", None)
    if not graph or not hasattr(graph, "_vault"):
        return JSONResponse({"error": "knowledge graph not available"}, status_code=503)
    history_path = "memory/predictions-history.jsonl"
    try:
        import json
        if graph._vault.exists(history_path):
            vault_path = graph._vault.paths.resolve(history_path) if graph._vault.paths else graph._vault._vault_dir / history_path
            lines = vault_path.read_text(encoding="utf-8").strip().splitlines()
            entries = []
            for line in lines[-limit:]:
                try:
                    entries.append(json.loads(line))
                except json.JSONDecodeError:
                    continue
            return {"history": entries, "count": len(entries)}
        return {"history": [], "count": 0}
    except Exception as e:
        return JSONResponse({"error": str(e)}, status_code=500)


@router.get("/memory/affective-map")
async def affective_map(request: Request):
    """Return per-entity aggregate affective weight for heat map visualization."""
    graph = getattr(request.app.state, "knowledge_graph", None)
    if not graph:
        return JSONResponse({"error": "knowledge graph not available"}, status_code=503)
    try:
        data = graph.entity_affective_map()
        return {"entities": data, "count": len(data)}
    except Exception as e:
        return JSONResponse({"error": str(e)}, status_code=500)


@router.post("/memory/dream/{phase}")
async def dream_trigger(request: Request, phase: str):
    """Manually trigger a dream phase (light/deep/rem)."""
    handler = getattr(request.app.state, "dream_handler", None)
    if not handler:
        return JSONResponse({"error": "dream handler not available"}, status_code=503)

    if phase == "light":
        from agntspace.memory.dream import gather_recent_chat_turns, load_phase_schedule
        conv_mem = getattr(request.app.state, "memory", None)
        skills = getattr(request.app.state, "skills", None)
        schedule = load_phase_schedule(skills, "light")
        lookback = int(schedule.get("lookback_hours", 12) or 12)
        units = await gather_recent_chat_turns(conv_mem, lookback_hours=lookback) if conv_mem else []
        result = await handler.run_light(units)
    elif phase == "deep":
        from agntspace.memory.dream import gather_hippocampal_window, load_phase_schedule
        graph = getattr(request.app.state, "knowledge_graph", None)
        skills = getattr(request.app.state, "skills", None)
        schedule = load_phase_schedule(skills, "deep")
        lookback = int(schedule.get("lookback_hours", 24) or 24)
        triples = gather_hippocampal_window(graph, lookback_hours=lookback) if graph else []
        result = await handler.run_deep(triples)
    elif phase == "rem":
        from agntspace.memory.dream import gather_neocortical_window, load_phase_schedule
        graph = getattr(request.app.state, "knowledge_graph", None)
        skills = getattr(request.app.state, "skills", None)
        schedule = load_phase_schedule(skills, "rem")
        lookback = int(schedule.get("lookback_days", 7) or 7)
        triples = gather_neocortical_window(graph, lookback_days=lookback) if graph else []
        result = await handler.run_rem(triples)
    else:
        return JSONResponse(
            {"error": f"Unknown phase: {phase}. Use light, deep, or rem."},
            status_code=400,
        )
    return result
