"""Episodic memory via Reflexion pattern.

After each session the agent auto-generates a structured reflection from its
session_state.  On future sessions the most recent reflections are injected
into the system prompt so the agent can recall prior debugging context.

Storage: `.hikaflow/reflections.jsonl` — append-only JSONL, sliding window
of MAX_REFLECTIONS entries.
"""

from __future__ import annotations

import json
import logging
from dataclasses import dataclass, field
from datetime import datetime, timezone
from pathlib import Path
from typing import List, Optional

logger = logging.getLogger(__name__)

MAX_REFLECTIONS = 50


@dataclass
class Reflection:
    """A single post-session reflection entry."""

    session_id: str
    timestamp: str
    task: str
    outcome: str  # "success" | "partial" | "failed"
    reflection: str
    key_files: List[str] = field(default_factory=list)
    turns: int = 0

    def to_dict(self) -> dict:
        return {
            "session_id": self.session_id,
            "timestamp": self.timestamp,
            "task": self.task,
            "outcome": self.outcome,
            "reflection": self.reflection,
            "key_files": self.key_files,
            "turns": self.turns,
        }

    @classmethod
    def from_dict(cls, d: dict) -> Reflection:
        return cls(
            session_id=d.get("session_id", ""),
            timestamp=d.get("timestamp", ""),
            task=d.get("task", ""),
            outcome=d.get("outcome", ""),
            reflection=d.get("reflection", ""),
            key_files=d.get("key_files", []),
            turns=d.get("turns", 0),
        )


def _reflections_path(project_path: str) -> Path:
    return Path(project_path) / ".hikaflow" / "reflections.jsonl"


def save_reflection(
    project_path: str,
    session_id: str,
    task: str,
    outcome: str,
    reflection: str,
    key_files: Optional[List[str]] = None,
    turns: int = 0,
) -> None:
    """Append a reflection and enforce the sliding window."""
    path = _reflections_path(project_path)
    path.parent.mkdir(parents=True, exist_ok=True)

    entry = Reflection(
        session_id=session_id,
        timestamp=datetime.now(timezone.utc).isoformat(),
        task=task,
        outcome=outcome,
        reflection=reflection,
        key_files=key_files or [],
        turns=turns,
    )

    # Read existing, append, trim to MAX_REFLECTIONS
    existing = _read_all(path)
    existing.append(entry)
    if len(existing) > MAX_REFLECTIONS:
        existing = existing[-MAX_REFLECTIONS:]

    # Rewrite file
    with open(path, "w", encoding="utf-8") as f:
        for r in existing:
            f.write(json.dumps(r.to_dict()) + "\n")


def load_recent_reflections(project_path: str, limit: int = 3) -> List[Reflection]:
    """Load the N most recent reflections."""
    path = _reflections_path(project_path)
    all_refs = _read_all(path)
    return all_refs[-limit:] if all_refs else []


def search_reflections(
    project_path: str, query: str, limit: int = 5
) -> List[Reflection]:
    """Case-insensitive keyword search across task + reflection fields."""
    path = _reflections_path(project_path)
    all_refs = _read_all(path)
    query_lower = query.lower()
    matches = [
        r
        for r in all_refs
        if query_lower in r.task.lower() or query_lower in r.reflection.lower()
    ]
    return matches[-limit:]


def render_reflections_for_prompt(reflections: List[Reflection]) -> str:
    """Format reflections as a system prompt section."""
    if not reflections:
        return ""

    lines = [
        "## Past Session Reflections\n",
        "These are lessons from your recent debugging sessions. "
        "Use them to avoid repeating mistakes and to recall prior context.\n",
    ]
    for r in reflections:
        ts = r.timestamp[:10] if len(r.timestamp) >= 10 else r.timestamp
        lines.append(f"### {ts} — {r.task[:80]}")
        lines.append(f"Outcome: {r.outcome}")
        if r.key_files:
            lines.append(f"Files: {', '.join(r.key_files[:5])}")
        lines.append(r.reflection)
        lines.append("")

    return "\n".join(lines)


def auto_reflect_from_session(
    project_path: str,
    blocks: "MemoryBlocks",  # noqa: F821 — forward ref to avoid circular import
    conversation_summary: str = "",
) -> None:
    """Generate a reflection from session_state or conversation at session end.

    Uses session_state if available, falls back to conversation_summary.
    """
    session_state = blocks.get("session_state")
    content = session_state.strip() if session_state else ""

    # Fall back to conversation summary if session_state is empty
    if len(content) < 20 and conversation_summary:
        content = conversation_summary.strip()

    # Nothing to reflect on
    if len(content) < 10:
        return

    # Extract first line as task summary
    first_line = content.split("\n")[0][:120]

    # Infer outcome from keywords
    lower = content.lower()
    if any(w in lower for w in ["fixed", "resolved", "success", "done", "completed"]):
        outcome = "success"
    elif any(w in lower for w in ["failed", "error", "broken", "unable"]):
        outcome = "failed"
    else:
        outcome = "partial"

    try:
        save_reflection(
            project_path=project_path,
            session_id=blocks.session_id,
            task=first_line,
            outcome=outcome,
            reflection=content[:500],
            key_files=[],
            turns=0,
        )
    except Exception:
        logger.warning("Failed to auto-reflect from session", exc_info=True)


def _read_all(path: Path) -> List[Reflection]:
    """Read all reflections from JSONL file."""
    if not path.is_file():
        return []
    results = []
    try:
        for line in path.read_text(encoding="utf-8").splitlines():
            line = line.strip()
            if line:
                try:
                    results.append(Reflection.from_dict(json.loads(line)))
                except (json.JSONDecodeError, KeyError):
                    continue
    except Exception:
        logger.warning("Failed to read reflections.jsonl", exc_info=True)
    return results
