"""cortex/cli/memory_cmds.py — remember, recall, ask, forget, cornerstones."""
from __future__ import annotations

import sys
from datetime import datetime, timezone
from typing import Optional

import click

import cortex.cli.app as _app

# Convenience aliases — keep code readable while allowing patching via _app._make_plugin
main = _app.main


# ---------------------------------------------------------------------------
# evaos remember
# ---------------------------------------------------------------------------

@main.command("remember")
@click.argument("text")
@click.option("--session", default=None, help="Session ID (auto-generated if omitted).")
@click.pass_context
def cmd_remember(ctx: click.Context, text: str, session: Optional[str]) -> None:
    """Store TEXT as a memory.

    TEXT is wrapped in a user/assistant conversation turn before being sent
    through the Cortex extraction pipeline.

    Example:

    \b
        evaos remember "Andrew prefers dark mode in all editors"
    """
    plugin = _app._make_plugin(ctx.obj["db"], ctx.obj["config"])
    sid = session or f"cli-{datetime.now(timezone.utc).strftime('%Y%m%d%H%M%S')}"

    messages = [
        {"role": "user", "content": text},
        {"role": "assistant", "content": "Understood, I'll remember that."},
    ]

    async def _store() -> None:
        if plugin.storage:
            await plugin.storage.initialize()
        await plugin.wake(session_id=sid)
        result = await plugin.remember(messages, session_id=sid)
        rows = [
            ("Session", result.session_id),
            ("Claims extracted", str(result.claims_extracted)),
            ("Claims stored", str(result.claims_stored)),
            ("Entities resolved", str(result.entities_resolved)),
            ("Noise filtered", str(result.noise_filtered)),
            ("OK", str(result.ok)),
        ]
        if result.errors:
            for e in result.errors:
                rows.append(("Warning", e))
        _app._kv_table(rows, title="Remember Result")
        if result.ok:
            _app._ok(f"Memory stored ({result.claims_stored} claim(s))")
        else:
            _app._warn("Memory pipeline completed with errors")

    _app._run(_store())


# ---------------------------------------------------------------------------
# evaos recall
# ---------------------------------------------------------------------------

@main.command("recall")
@click.argument("query")
@click.option("--limit", default=5, show_default=True, help="Max results to display.")
@click.pass_context
def cmd_recall(ctx: click.Context, query: str, limit: int) -> None:
    """Retrieve memories matching QUERY.

    Example:

    \b
        evaos recall "editor preferences"
    """
    plugin = _app._make_plugin(ctx.obj["db"], ctx.obj["config"])

    async def _retrieve() -> None:
        if plugin.storage:
            await plugin.storage.initialize()
        result = await plugin.retrieve(query)

        # RetrievalResult has .items (list of RetrievedItem)
        memories = getattr(result, "items", None) or getattr(result, "memories", None) or getattr(result, "claims", None) or []
        context_block = getattr(result, "context_block", None)
        token_count = getattr(result, "token_count", None)

        if not memories and context_block:
            _app._section("Retrieved Context")
            _app._print(context_block)
            if token_count is not None:
                _app._print(f"\n[dim]Tokens: {token_count}[/dim]" if _app._RICH
                            else f"\nTokens: {token_count}")
            return

        if not memories:
            _app._warn("No memories found for query.")
            return

        # Print as table
        if _app._RICH:
            from rich.table import Table
            table = Table(title=f'Recall: "{query}"', show_header=True,
                          header_style="bold cyan")
            table.add_column("#", style="dim", width=4)
            table.add_column("Memory")
            table.add_column("Score", width=8)
            for i, m in enumerate(memories[:limit], 1):
                content = (getattr(m, "content", None)
                           or getattr(m, "object_value", None)
                           or str(m))
                score = getattr(m, "score", getattr(m, "relevance", "—"))
                table.add_row(str(i), str(content), f"{score:.3f}" if isinstance(score, float) else str(score))
            _app._console.print(table)  # type: ignore[union-attr]
        else:
            click.echo(f'\nRecall: "{query}"')
            for i, m in enumerate(memories[:limit], 1):
                content = (getattr(m, "content", None)
                           or getattr(m, "object_value", None)
                           or str(m))
                click.echo(f"  {i}. {content}")

        if token_count is not None:
            _app._print(f"\n[dim]Tokens used: {token_count}[/dim]" if _app._RICH
                        else f"\nTokens used: {token_count}")

    _app._run(_retrieve())


# ---------------------------------------------------------------------------
# evaos ask — Dialectic Engine fast path (M14)
# ---------------------------------------------------------------------------

@main.command("ask")
@click.argument("query")
@click.option(
    "--owner-id",
    default=None,
    help="Memory owner namespace (defaults to config owner_id).",
)
@click.option(
    "--agentic",
    is_flag=True,
    default=False,
    help=(
        "Use the agentic path: multi-step retrieval loop for complex questions. "
        "Decomposes the question, retrieves per sub-query, and synthesises a final answer."
    ),
)
@click.option(
    "--max-steps",
    default=5,
    show_default=True,
    type=int,
    help="Maximum retrieval steps for --agentic mode.",
)
@click.pass_context
def cmd_ask(
    ctx: click.Context,
    query: str,
    owner_id: Optional[str],
    agentic: bool,
    max_steps: int,
) -> None:
    """Ask a question and get an answer synthesised from memory.

    Uses the Dialectic Engine (M14): retrieves relevant memories then calls
    an LLM to compose a grounded answer.

    Two modes:
        Fast path (default) — single-shot retrieval + synthesis.
        Agentic path (--agentic) — multi-step loop for complex questions.

    Example:

    \b
        evaos ask "What does the user prefer?"
        evaos ask "What is the user working on?" --owner-id user-42
        evaos ask "Summarise everything about this user" --agentic
        evaos ask "Complex question" --agentic --max-steps 8
    """
    plugin = _app._make_plugin(ctx.obj["db"], ctx.obj["config"])
    mode = "agentic" if agentic else "fast"

    async def _ask() -> None:
        if plugin.storage:
            await plugin.storage.initialize()

        result = await plugin.ask(
            query=query,
            owner_id=owner_id,
            mode=mode,
            max_steps=max_steps,
        )

        if _app._RICH:
            from rich.panel import Panel
            from rich.text import Text

            # Answer panel
            mode_label = "agentic" if agentic else "fast"
            _app._console.print(  # type: ignore[union-attr]
                Panel(
                    result.answer,
                    title=f"[bold cyan]Answer[/bold cyan] [dim]({mode_label})[/dim]",
                    subtitle=f"confidence: {result.confidence:.2f}",
                    border_style="cyan",
                )
            )

            # Sources table
            if result.sources:
                from rich.table import Table
                table = Table(title="Sources", show_header=True, header_style="bold dim")
                table.add_column("#", style="dim", width=4)
                table.add_column("Memory")
                table.add_column("Score", width=8)
                for i, src in enumerate(result.sources, 1):
                    subj = src.get("subject", "")
                    pred = src.get("predicate", "")
                    obj = src.get("object_value", "")
                    content = f"{subj} {pred} {obj}".strip() or src.get("content", str(src))
                    score = src.get("score") or src.get("confidence")
                    score_str = f"{score:.3f}" if isinstance(score, float) else str(score or "—")
                    table.add_row(str(i), content, score_str)
                _app._console.print(table)  # type: ignore[union-attr]

            if result.reasoning:
                _app._print(f"\n[dim]{result.reasoning}[/dim]")

        else:
            click.echo(f'\nAnswer: {result.answer}')
            click.echo(f'Confidence: {result.confidence:.2f}')
            if result.sources:
                click.echo(f'\nSources ({len(result.sources)}):')
                for i, src in enumerate(result.sources, 1):
                    subj = src.get("subject", "")
                    pred = src.get("predicate", "")
                    obj = src.get("object_value", "")
                    content = f"{subj} {pred} {obj}".strip() or src.get("content", str(src))
                    click.echo(f"  {i}. {content}")

        if not result.ok:
            for err in result.errors:
                _app._err(err)

    _app._run(_ask())


# ---------------------------------------------------------------------------
# evaos cornerstones (subgroup)
# ---------------------------------------------------------------------------

@main.group("cornerstones")
@click.pass_context
def cornerstones(ctx: click.Context) -> None:
    """Manage cornerstone memories — the sealed identity anchors."""


@cornerstones.command("list")
@click.pass_context
def cornerstones_list(ctx: click.Context) -> None:
    """List all sealed cornerstone memories."""
    plugin = _app._make_plugin(ctx.obj["db"], ctx.obj["config"])

    async def _list() -> None:
        if plugin.storage:
            await plugin.storage.initialize()
        items = await plugin.get_cornerstones()
        if not items:
            _app._warn("No cornerstones found.")
            return

        if _app._RICH:
            from rich.table import Table
            table = Table(title="Cornerstones", show_header=True,
                          header_style="bold magenta")
            table.add_column("ID", style="dim", max_width=12)
            table.add_column("Label")
            table.add_column("Content", max_width=60)
            table.add_column("Sealed at", width=20)
            for cs in items:
                cs_id = str(getattr(cs, "id", getattr(cs, "cs_id", "?")))[:12]
                label = getattr(cs, "label", "")
                content = getattr(cs, "content", "")[:80]
                sealed = str(getattr(cs, "sealed_at", ""))
                table.add_row(cs_id, label, content, sealed)
            _app._console.print(table)  # type: ignore[union-attr]
        else:
            click.echo(f"\nCornerstones ({len(items)}):")
            for cs in items:
                label = getattr(cs, "label", "")
                content = getattr(cs, "content", "")[:80]
                click.echo(f"  [{label}] {content}")

    _app._run(_list())


@cornerstones.command("seal")
@click.option("--label", required=True, help="Short label for this cornerstone.")
@click.option("--content", required=True, help="Cornerstone content text.")
@click.pass_context
def cornerstones_seal(ctx: click.Context, label: str, content: str) -> None:
    """Seal a new cornerstone memory.

    Example:

    \b
        evaos cornerstones seal --label "identity" --content "I am Eva, engineering AI"
    """
    plugin = _app._make_plugin(ctx.obj["db"], ctx.obj["config"])

    async def _seal() -> None:
        if plugin.storage:
            await plugin.storage.initialize()
        cs = await plugin.seal_cornerstone(label=label, content=content)
        cs_id = getattr(cs, "id", getattr(cs, "cs_id", "?"))
        rows = [
            ("ID", str(cs_id)),
            ("Label", label),
            ("Content", content[:100]),
            ("Status", "sealed"),
        ]
        _app._kv_table(rows, title="Cornerstone Sealed")
        _app._ok(f"Cornerstone sealed: {label}")

    _app._run(_seal())


# ---------------------------------------------------------------------------
# GDPR forget command (#108)
# ---------------------------------------------------------------------------

@main.command("forget")
@click.option(
    "--entity-id", default=None,
    help="Entity ID to forget (cascade-delete all claims and brain nodes).",
)
@click.option(
    "--owner-id", default=None,
    help="Owner ID scope (required for entity forget; used alone for nuclear owner forget).",
)
@click.option(
    "--confirm-delete-all", is_flag=True, default=False,
    help="Required flag when using --owner-id alone (nuclear owner forget).",
)
@click.option(
    "--dry-run", is_flag=True, default=False,
    help="Show what would be deleted without touching the database.",
)
@click.pass_context
def cmd_forget(
    ctx: click.Context,
    entity_id: Optional[str],
    owner_id: Optional[str],
    confirm_delete_all: bool,
    dry_run: bool,
) -> None:
    """GDPR-compliant entity or owner forget.

    \b
    Entity forget (cascade-delete one entity + its claims):
        evaos forget --entity-id <ID> --owner-id <OWNER>
        evaos forget --entity-id <ID> --owner-id <OWNER> --dry-run

    \b
    Owner forget (delete ALL data for an owner):
        evaos forget --owner-id <OWNER> --confirm-delete-all
        evaos forget --owner-id <OWNER> --confirm-delete-all --dry-run
    """
    from cortex.tools.gdpr import forget_entity, forget_owner

    if entity_id and owner_id:
        # Single-entity forget
        async def _run_entity():
            plugin = _app._make_plugin(ctx.obj["db"], ctx.obj.get("config"))
            async with plugin:
                result = await forget_entity(
                    plugin.storage,
                    entity_id=entity_id,
                    owner_id=owner_id,
                    dry_run=dry_run,
                    emitter=getattr(plugin, "emitter", None),
                )
            return result

        result = _app._run(_run_entity())
        label = "[DRY RUN] " if dry_run else ""
        _app._ok(
            f"{label}Entity forget complete: "
            f"claims={result.claims_deleted}, "
            f"entities={result.entities_deleted}, "
            f"brain_nodes={result.brain_nodes_deleted}"
        )
        if result.entity_name:
            _app._print(f"  Entity name: {result.entity_name}")

    elif owner_id and confirm_delete_all:
        # Nuclear owner forget
        async def _run_owner():
            plugin = _app._make_plugin(ctx.obj["db"], ctx.obj.get("config"))
            async with plugin:
                result = await forget_owner(
                    plugin.storage,
                    owner_id=owner_id,
                    dry_run=dry_run,
                    emitter=getattr(plugin, "emitter", None),
                )
            return result

        result = _app._run(_run_owner())
        label = "[DRY RUN] " if dry_run else ""
        _app._ok(
            f"{label}Owner forget complete for {owner_id!r}: "
            f"claims={result.claims_deleted}, "
            f"entities={result.entities_deleted}, "
            f"sessions={result.sessions_deleted}, "
            f"events={result.events_deleted}, "
            f"request_logs={result.request_logs_deleted}"
        )

    elif owner_id and not confirm_delete_all:
        _app._err(
            "Owner forget requires --confirm-delete-all flag. "
            "This operation deletes ALL data for the owner."
        )
        sys.exit(1)

    else:
        _app._err("Provide --entity-id (with --owner-id) or --owner-id (with --confirm-delete-all).")
        sys.exit(1)
