"""Giga dev bridge — tool handlers for agents to write plans/neurons to the outbox.

Agents call these tool handlers via the orchestrator's tool dispatch.
The handlers write JSON files to state/giga-outbox/ which Cursor then
syncs to Giga via MCP tools.

Core never imports this module directly — it's registered via loader.py
at startup (see __init__.py:register()).
"""

from __future__ import annotations

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

logger = logging.getLogger(__name__)

# Resolve outbox paths relative to project root
_PROJECT_ROOT = (
    Path(__file__).resolve().parents[4]
)  # src/vastian/bridges/giga_dev_bridge -> project root
OUTBOX_PLANS = _PROJECT_ROOT / "state" / "giga-outbox" / "plans"
OUTBOX_NEURONS = _PROJECT_ROOT / "state" / "giga-outbox" / "neurons"


def _slugify(text: str, max_len: int = 40) -> str:
    """Convert text to a filename-safe slug."""
    slug = re.sub(r"[^a-z0-9]+", "-", text.lower()).strip("-")
    return slug[:max_len]


def write_giga_plan(
    agent_id: str,
    key_name: str,
    content: str,
    mind: str = "vastian-network",
) -> str:
    """Write a plan to the Giga outbox for Cursor to sync.

    Args:
        agent_id: The agent writing the plan.
        key_name: Giga plan key (e.g., "cycle-review-feb").
        content: Markdown with task checkboxes.
        mind: Target Giga mind (default: vastian-network).

    Returns:
        Confirmation message or error string.
    """
    OUTBOX_PLANS.mkdir(parents=True, exist_ok=True)

    payload = {
        "key_name": key_name,
        "content": content,
        "mind": mind,
        "created_by": agent_id,
        "created_at": datetime.now(UTC).isoformat(),
    }

    outpath = OUTBOX_PLANS / f"{_slugify(key_name)}.json"
    try:
        outpath.write_text(json.dumps(payload, indent=2), encoding="utf-8")
        logger.info("Giga outbox plan written: %s by %s", key_name, agent_id)
        return (
            f"Plan '{key_name}' written to Giga outbox. "
            f"Cursor will sync it to the '{mind}' mind on next /giga-sync."
        )
    except Exception as e:
        logger.error("Failed to write Giga plan: %s", e)
        return f"Error writing plan to outbox: {e}"


def write_giga_neuron(
    agent_id: str,
    title: str,
    body: str,
    selector_nl: str = "",
    mind: str = "vastian-network",
) -> str:
    """Write a neuron to the Giga outbox for Cursor to sync.

    Args:
        agent_id: The agent writing the neuron.
        title: Neuron title (short label).
        body: Prompt-ready instruction or fact.
        selector_nl: When to surface this neuron.
        mind: Target Giga mind (default: vastian-network).

    Returns:
        Confirmation message or error string.
    """
    OUTBOX_NEURONS.mkdir(parents=True, exist_ok=True)

    timestamp = datetime.now(UTC).strftime("%Y%m%dT%H%M%S")
    payload = {
        "title": title,
        "body": body,
        "selector_nl": selector_nl,
        "mind": mind,
        "created_by": agent_id,
    }

    filename = f"{timestamp}_{_slugify(title)}.json"
    outpath = OUTBOX_NEURONS / filename
    try:
        outpath.write_text(json.dumps(payload, indent=2), encoding="utf-8")
        logger.info("Giga outbox neuron written: %s by %s", title, agent_id)
        return (
            f"Neuron '{title}' written to Giga outbox. "
            f"Cursor will sync it to the '{mind}' mind on next /giga-sync."
        )
    except Exception as e:
        logger.error("Failed to write Giga neuron: %s", e)
        return f"Error writing neuron to outbox: {e}"


INBOX_ROOT = _PROJECT_ROOT / "state" / "memory-inbox"


class GigaAdapter:
    """Adapter satisfying MemoryBridge protocol for Giga outbox writes + inbox pulls.

    Outbox (write): delegates to write_giga_plan / write_giga_neuron.
    Inbox (pull): reads from Giga MCP via placeholder JSON files in
    state/memory-inbox/{mind}/ — actual MCP calls are made by Cursor
    (the coordinator), but the adapter structures and persists results.

    Note: isinstance(GigaAdapter(), MemoryBridge) is True at runtime.
    """

    # -- Outbox (write) methods -----------------------------------------------

    def write_plan(
        self,
        agent_id: str,
        key_name: str,
        content: str,
        mind: str = "vastian-network",
    ) -> str:
        return write_giga_plan(agent_id, key_name, content, mind)

    def write_neuron(
        self,
        agent_id: str,
        title: str,
        body: str,
        mind: str = "vastian-network",
    ) -> str:
        return write_giga_neuron(agent_id, title, body, mind=mind)

    # -- Inbox (pull) methods -------------------------------------------------

    @staticmethod
    def _inbox_dir(mind: str, kind: str) -> Path:
        """Return the inbox directory for a mind + kind (plans|neurons)."""
        d = INBOX_ROOT / mind / kind
        d.mkdir(parents=True, exist_ok=True)
        return d

    def pull_plans(self, mind: str) -> list[dict]:
        """Pull plans from Giga for the given mind.

        In the dev bridge, this reads any JSON files already dropped into
        state/memory-inbox/{mind}/plans/ by the coordinator (Cursor).
        The coordinator calls Giga MCP (giga plan view) and writes the
        result here before invoking this method.
        """
        plans_dir = self._inbox_dir(mind, "plans")
        results: list[dict] = []
        for path in sorted(plans_dir.glob("*.json")):
            try:
                data = json.loads(path.read_text(encoding="utf-8"))
                if "pulled_at" not in data:
                    data["pulled_at"] = datetime.now(UTC).isoformat()
                data["source"] = "giga"
                results.append(data)
                logger.debug("Read inbox plan: %s", path.name)
            except (json.JSONDecodeError, OSError) as e:
                logger.warning("Skipping malformed inbox plan %s: %s", path.name, e)
        return results

    def pull_neurons(self, mind: str, task: str = "") -> list[dict]:
        """Pull neurons from Giga for the given mind.

        Reads JSON files from state/memory-inbox/{mind}/neurons/.
        The coordinator writes them here after calling giga evoke.
        """
        neurons_dir = self._inbox_dir(mind, "neurons")
        results: list[dict] = []
        for path in sorted(neurons_dir.glob("*.json")):
            try:
                data = json.loads(path.read_text(encoding="utf-8"))
                if "pulled_at" not in data:
                    data["pulled_at"] = datetime.now(UTC).isoformat()
                data["source"] = "giga"
                results.append(data)
                logger.debug("Read inbox neuron: %s", path.name)
            except (json.JSONDecodeError, OSError) as e:
                logger.warning("Skipping malformed inbox neuron %s: %s", path.name, e)
        return results

    def sync_inbox(self, mind: str) -> int:
        """Pull plans + neurons and return the total count.

        Reads all JSON files currently in the inbox directories.
        The coordinator is responsible for populating these dirs
        by calling Giga MCP before invoking sync_inbox.
        """
        plans = self.pull_plans(mind)
        neurons = self.pull_neurons(mind)
        total = len(plans) + len(neurons)
        logger.info(
            "Synced inbox for mind '%s': %d plans, %d neurons",
            mind,
            len(plans),
            len(neurons),
        )
        return total
