"""CLI commands for episodic memory: remember and recall."""

from __future__ import annotations

import re
from datetime import datetime, timedelta, timezone
from typing import Optional

import typer
from rich.console import Console

from engram.models import MemoryType
from engram.utils import run_async

console = Console()


def _parse_duration(duration_str: str) -> datetime:
    """Parse duration string like '7d', '24h', '30m' → future datetime.

    Supported units: d (days), h (hours), m (minutes), s (seconds).
    """
    pattern = re.compile(r"^(\d+)([dhms])$")
    match = pattern.match(duration_str.strip().lower())
    if not match:
        raise typer.BadParameter(
            f"Invalid duration '{duration_str}'. Use format like '7d', '24h', '30m', '60s'."
        )
    value, unit = int(match.group(1)), match.group(2)
    delta_map = {"d": timedelta(days=value), "h": timedelta(hours=value),
                 "m": timedelta(minutes=value), "s": timedelta(seconds=value)}
    return datetime.now(timezone.utc) + delta_map[unit]


def _get_episodic(get_config, namespace: str | None = None):
    from engram.episodic.store import EpisodicStore
    cfg = get_config()
    try:
        return EpisodicStore(
            cfg.episodic,
            cfg.embedding,
            namespace=namespace,
            guard_enabled=cfg.ingestion.poisoning_guard,
        )
    except RuntimeError as e:
        if "already accessed" in str(e):
            console.print("[dim]Server is running — using HTTP API[/dim]")
            return _HttpEpisodicProxy(cfg)
        raise


class _HttpEpisodicProxy:
    """Thin proxy that forwards episodic operations to running HTTP server."""

    def __init__(self, cfg):
        self._base = f"http://{cfg.serve.host}:{cfg.serve.port}/api/v1"

    async def remember(self, content, **kwargs):
        import httpx
        payload = {"content": content}
        if kwargs.get("tags"):
            payload["tags"] = kwargs["tags"]
        if kwargs.get("memory_type"):
            payload["type"] = str(kwargs["memory_type"].value) if hasattr(kwargs["memory_type"], "value") else str(kwargs["memory_type"])
        if kwargs.get("priority"):
            payload["priority"] = kwargs["priority"]
        if kwargs.get("topic_key"):
            payload["topic_key"] = kwargs["topic_key"]
        async with httpx.AsyncClient(timeout=30) as client:
            resp = await client.post(f"{self._base}/remember", json=payload)
            data = resp.json()
            return data.get("id", "")

    async def recall(self, query, top_k=5, **kwargs):
        import httpx
        params = {"query": query, "limit": top_k}
        async with httpx.AsyncClient(timeout=30) as client:
            resp = await client.get(f"{self._base}/recall", params=params)
            return resp.json().get("memories", [])

    async def search(self, query, limit=5, **kwargs):
        """Search via HTTP API — returns list of Memory-like objects."""
        import httpx
        from types import SimpleNamespace
        from datetime import datetime, timezone
        params = {"query": query, "limit": limit}
        async with httpx.AsyncClient(timeout=30) as client:
            resp = await client.get(f"{self._base}/recall", params=params)
            data = resp.json()
        memories = []
        for m in data.get("results", data.get("memories", [])):
            ts = m.get("timestamp", "")
            if isinstance(ts, str) and ts:
                try:
                    ts = datetime.fromisoformat(ts)
                except (ValueError, TypeError):
                    ts = datetime.now(timezone.utc)
            elif not ts:
                ts = datetime.now(timezone.utc)
            from engram.models import MemoryType
            raw_type = m.get("memory_type", m.get("type", "fact"))
            try:
                mt = MemoryType(raw_type)
            except ValueError:
                mt = MemoryType.FACT
            mem = SimpleNamespace(
                id=m.get("id", ""),
                content=m.get("content", ""),
                memory_type=mt,
                priority=m.get("priority", 5),
                tags=m.get("tags", []),
                entities=m.get("entities", []),
                timestamp=ts,
                access_count=m.get("access_count", 0),
                score=m.get("score", 0.0),
            )
            memories.append(mem)
        return memories

    async def stats(self):
        import httpx
        async with httpx.AsyncClient(timeout=30) as client:
            resp = await client.get(f"{self._base}/status")
            return resp.json().get("episodic", {})


def _get_semantic(get_config):
    from engram.semantic import create_graph
    cfg = get_config()
    try:
        return create_graph(cfg.semantic)
    except Exception:
        return _NullGraph()


class _NullGraph:
    """No-op graph when semantic store is unavailable (server holds lock)."""
    async def query(self, *a, **kw): return []
    async def stats(self, *a, **kw): return {"node_count": 0, "edge_count": 0}


def register(app: typer.Typer, get_config, get_namespace=None) -> None:
    """Register episodic commands on the main Typer app."""

    def _resolve_namespace():
        return get_namespace() if get_namespace else None

    @app.command()
    def remember(
        content: str = typer.Argument(..., help="Content to remember"),
        type: str = typer.Option("fact", "--type", "-t", help="Memory type"),
        priority: int = typer.Option(5, "--priority", "-p", help="Priority (1-10)"),
        tags: Optional[str] = typer.Option(None, "--tags", help="Comma-separated tags, e.g. 'deploy,prod'"),
        expires: Optional[str] = typer.Option(None, "--expires", help="Expiry duration, e.g. '7d', '24h', '30m'"),
        topic_key: Optional[str] = typer.Option(None, "--topic-key", help="Unique key; updates existing memory if same key exists"),
    ):
        """Store a memory in episodic store."""
        if len(content) > 10_000:
            console.print("[red]Error: Content too long (max 10,000 chars)[/red]")
            raise typer.Exit(1)
        store = _get_episodic(get_config, _resolve_namespace())
        mem_type = MemoryType(type)

        tag_list = [t.strip() for t in tags.split(",") if t.strip()] if tags else []
        expires_at = _parse_duration(expires) if expires else None

        mem_id = run_async(store.remember(
            content,
            memory_type=mem_type,
            priority=priority,
            tags=tag_list,
            expires_at=expires_at,
            topic_key=topic_key,
        ))
        suffix = ""
        if tag_list:
            suffix += f", tags=[{', '.join(tag_list)}]"
        if expires_at:
            suffix += f", expires={expires_at.strftime('%Y-%m-%d %H:%M')}"
        console.print(f"[green]Remembered[/green] (id={mem_id[:8]}..., type={type}{suffix})")

    @app.command()
    def recall(
        query: str = typer.Argument(..., help="Search query"),
        limit: int = typer.Option(5, "--limit", "-l"),
        type: Optional[str] = typer.Option(None, "--type", "-t"),
        tags: Optional[str] = typer.Option(None, "--tags", help="Comma-separated tags to filter by"),
        no_federation: bool = typer.Option(False, "--no-federation", help="Skip federated provider search"),
        timeout: float = typer.Option(10.0, "--timeout", help="Federated search timeout in seconds"),
        resolve_entities: bool = typer.Option(False, "--resolve-entities", help="Resolve pronouns in query before searching"),
        resolve_temporal: bool = typer.Option(False, "--resolve-temporal", help="Resolve temporal references in query before searching"),
    ):
        """Search episodic memories and federated providers."""
        # Entity/temporal resolution pre-processing
        if resolve_entities or resolve_temporal:
            from engram.recall.entity_resolver import resolve as do_resolve
            resolved = run_async(do_resolve(
                query,
                context=None,
                resolve_temporal_refs=resolve_temporal,
                resolve_pronoun_refs=resolve_entities,
            ))
            if resolved.resolved != query:
                console.print(f"[dim]Resolved query:[/dim] {resolved.resolved}")
                query = resolved.resolved

        store = _get_episodic(get_config, _resolve_namespace())
        filters = {}
        if type:
            filters["memory_type"] = type

        tag_list = [t.strip() for t in tags.split(",") if t.strip()] if tags else None

        results = run_async(store.search(
            query,
            limit=limit,
            filters=filters if filters else None,
            tags=tag_list,
        ))

        # Search semantic graph and close pool in same event loop to prevent hang
        graph = _get_semantic(get_config)

        async def _graph_search_and_close():
            nodes = await graph.query(query)
            related_map = {}
            for node in nodes[:3]:
                rel = await graph.get_related([node.name])
                related_map[node.name] = rel.get(node.name, {}).get("edges", [])[:5]
            if hasattr(graph, 'close'):
                await graph.close()
            return nodes, related_map

        graph_nodes, graph_related = run_async(_graph_search_and_close())
        if graph_nodes:
            for node in graph_nodes[:3]:
                attrs = ", ".join(f"{k}={v}" for k, v in node.attributes.items()) if node.attributes else ""
                console.print(f"[yellow][graph][/yellow] {node.type}:{node.name}" + (f" ({attrs})" if attrs else ""))
                for edge in graph_related.get(node.name, []):
                    console.print(f"  [dim]{edge.from_node} --{edge.relation}--> {edge.to_node}[/dim]")

        # Federated search across external providers
        provider_results = []
        if not no_federation:
            from engram.providers.registry import ProviderRegistry
            from engram.providers.router import federated_search
            cfg = get_config()
            registry = ProviderRegistry()
            registry.load_from_config(cfg)
            providers = registry.get_active()
            if providers:
                provider_results = run_async(federated_search(query, providers, limit=limit, timeout_seconds=timeout))
                for r in provider_results:
                    console.print(f"[magenta]\\[{r.source}][/magenta] {r.content[:300]}")

        if not results and not graph_nodes and not provider_results:
            console.print("[dim]No memories found.[/dim]")
            return

        for mem in results:
            ts = mem.timestamp.strftime("%Y-%m-%d %H:%M")
            console.print(f"[cyan][{ts}][/cyan] ({mem.memory_type.value}) {mem.content}")
            if mem.entities:
                console.print(f"  [dim]entities: {', '.join(mem.entities)}[/dim]")
            if mem.tags:
                console.print(f"  [dim]tags: {', '.join(mem.tags)}[/dim]")
