"""Inbox Processor — reads inbox JSON, writes raw shared context doc.

Reads JSON files from state/memory-inbox/{mind}/plans/ and
state/memory-inbox/{mind}/neurons/, compiles them into a single raw
context document at docs/shared/{mind}/context.md.

Processed files are moved to a .processed/ subfolder to avoid re-processing.
"""

from __future__ import annotations

import json
import logging
import shutil
from datetime import UTC, datetime
from pathlib import Path

logger = logging.getLogger(__name__)

_PROJECT_ROOT = Path(__file__).resolve().parents[3]
INBOX_ROOT = _PROJECT_ROOT / "state" / "memory-inbox"
DOCS_SHARED = _PROJECT_ROOT / "docs" / "shared"


class InboxProcessor:
    """Process memory inbox files into raw shared context documents."""

    def __init__(self, project_root: Path | None = None):
        self.project_root = project_root or _PROJECT_ROOT
        self.inbox_root = self.project_root / "state" / "memory-inbox"
        self.docs_shared = self.project_root / "docs" / "shared"

    def get_minds(self) -> list[str]:
        """Return all minds that have inbox directories."""
        if not self.inbox_root.exists():
            return []
        return sorted(
            d.name for d in self.inbox_root.iterdir() if d.is_dir() and not d.name.startswith(".")
        )

    def _read_inbox_files(self, mind: str, kind: str) -> list[dict]:
        """Read all unprocessed JSON files from a mind's inbox subdirectory.

        Args:
            mind: The mind namespace (e.g., "work").
            kind: Subdirectory — "plans" or "neurons".

        Returns:
            List of parsed JSON dicts.
        """
        inbox_dir = self.inbox_root / mind / kind
        if not inbox_dir.exists():
            return []

        results: list[dict] = []
        for path in sorted(inbox_dir.glob("*.json")):
            if path.name.startswith("."):
                continue
            try:
                data = json.loads(path.read_text(encoding="utf-8"))
                data["_source_file"] = str(path.name)
                data["_kind"] = kind
                results.append(data)
            except (json.JSONDecodeError, OSError) as e:
                logger.warning("Skipping malformed inbox file %s: %s", path, e)
        return results

    def _archive_processed(self, mind: str, kind: str) -> int:
        """Move processed JSON files to .processed/ subfolder.

        Returns count of files moved.
        """
        inbox_dir = self.inbox_root / mind / kind
        processed_dir = inbox_dir / ".processed"
        processed_dir.mkdir(parents=True, exist_ok=True)

        count = 0
        for path in sorted(inbox_dir.glob("*.json")):
            if path.name.startswith("."):
                continue
            dest = processed_dir / path.name
            try:
                shutil.move(str(path), str(dest))
                count += 1
            except OSError as e:
                logger.warning("Could not archive %s: %s", path, e)
        return count

    def _compile_context(self, plans: list[dict], neurons: list[dict], mind: str) -> str:
        """Compile plans and neurons into a raw context markdown document."""
        now = datetime.now(UTC).strftime("%Y-%m-%d %H:%M UTC")
        lines = [
            f"# External Context: {mind}",
            "",
            f"*Pulled at {now}*",
            "",
        ]

        if plans:
            lines.append("## Plans")
            lines.append("")
            for p in plans:
                key = p.get("key_name", p.get("title", "untitled"))
                content = p.get("content", "")
                lines.append(f"### {key}")
                lines.append("")
                # Truncate very long plan content for the raw doc
                if len(content) > 2000:
                    lines.append(content[:2000])
                    lines.append(f"\n*... truncated ({len(content)} chars total)*")
                else:
                    lines.append(content)
                lines.append("")

        if neurons:
            lines.append("## Neurons")
            lines.append("")
            for n in neurons:
                title = n.get("title", "untitled")
                body = n.get("body", "")
                selector = n.get("selector_nl", "")
                lines.append(f"### {title}")
                if selector:
                    lines.append(f"*Relevance: {selector}*")
                lines.append("")
                lines.append(body)
                lines.append("")

        if not plans and not neurons:
            lines.append("*No new context available.*")
            lines.append("")

        return "\n".join(lines)

    def process(self, mind: str) -> dict:
        """Process all inbox files for a mind and write raw context doc.

        Returns:
            Dict with plan_count, neuron_count, raw_doc_path, and status.
        """
        plans = self._read_inbox_files(mind, "plans")
        neurons = self._read_inbox_files(mind, "neurons")

        total = len(plans) + len(neurons)
        if total == 0:
            logger.info("No inbox files for mind '%s'", mind)
            return {
                "mind": mind,
                "plan_count": 0,
                "neuron_count": 0,
                "raw_doc_path": None,
                "status": "empty",
            }

        # Write raw context doc
        context_md = self._compile_context(plans, neurons, mind)
        raw_doc_dir = self.docs_shared / mind
        raw_doc_dir.mkdir(parents=True, exist_ok=True)
        raw_doc_path = raw_doc_dir / "context.md"
        raw_doc_path.write_text(context_md, encoding="utf-8")
        logger.info(
            "Wrote raw context for mind '%s': %d plans, %d neurons -> %s",
            mind,
            len(plans),
            len(neurons),
            raw_doc_path,
        )

        # Archive processed files
        archived_plans = self._archive_processed(mind, "plans")
        archived_neurons = self._archive_processed(mind, "neurons")
        logger.debug(
            "Archived %d plan files, %d neuron files for mind '%s'",
            archived_plans,
            archived_neurons,
            mind,
        )

        return {
            "mind": mind,
            "plan_count": len(plans),
            "neuron_count": len(neurons),
            "raw_doc_path": str(raw_doc_path),
            "status": "processed",
        }

    def status(self) -> dict:
        """Return current inbox status across all minds.

        Returns:
            Dict mapping mind names to file counts.
        """
        result: dict = {}
        for mind in self.get_minds():
            plans_dir = self.inbox_root / mind / "plans"
            neurons_dir = self.inbox_root / mind / "neurons"
            plan_count = len(list(plans_dir.glob("*.json"))) if plans_dir.exists() else 0
            neuron_count = len(list(neurons_dir.glob("*.json"))) if neurons_dir.exists() else 0

            # Count processed
            plans_proc = plans_dir / ".processed"
            neurons_proc = neurons_dir / ".processed"
            plan_processed = len(list(plans_proc.glob("*.json"))) if plans_proc.exists() else 0
            neuron_processed = (
                len(list(neurons_proc.glob("*.json"))) if neurons_proc.exists() else 0
            )

            # Check if raw doc exists
            raw_doc = self.docs_shared / mind / "context.md"

            result[mind] = {
                "plans_pending": plan_count,
                "neurons_pending": neuron_count,
                "plans_processed": plan_processed,
                "neurons_processed": neuron_processed,
                "raw_doc_exists": raw_doc.exists(),
            }
        return result
