"""ReadOpsMixin — retrieve(), on_tool_result(), feedback(), and ask() operations."""
from __future__ import annotations

import logging
from typing import Any, Dict, List, Optional

from .models import AskResult

logger = logging.getLogger(__name__)


class ReadOpsMixin:
    """Mixin providing read-path memory operations."""

    async def retrieve(
        self,
        query: str,
        token_budget: Optional[int] = None,
        constraints: Any = None,
        mode: str = "auto",
        session_id: Optional[str] = None,
        owner_id: Optional[str] = None,
    ) -> Any:
        """
        Retrieve relevant memories for *query* within *token_budget*.

        Returns a RetrievalResult (from cortex.core.retrieval) or None on error.
        """
        if self.retrieval is None:
            logger.error("RetrievalPipeline not available")
            return None

        # Warn on first retrieve if embedding model changed
        if getattr(self, "_embedding_model_changed", False) and not getattr(
            self, "_embedding_model_warning_emitted", False
        ):
            logger.warning(
                "⚠️  Retrieving with changed embedding model. Results may be "
                "degraded — existing embeddings were generated with a different model."
            )
            self._embedding_model_warning_emitted = True

        # Validate query string — import via package for test patchability
        import cortex.api.plugin as _plugin_pkg
        query = _plugin_pkg.validate_retrieve_query(query)

        # Normalise legacy/alias mode names to engine-supported values
        _MODE_ALIASES = {"fast": "lightweight", "thorough": "agentic", "multi-hop": "chained"}
        mode = _MODE_ALIASES.get(mode, mode)

        budget = token_budget or self.config.retrieval_token_budget

        # Resolve effective owner — use request-supplied owner_id when provided
        effective_owner_id = owner_id or self.config.owner_id

        # Build constraints if token_budget was explicitly supplied
        if constraints is None and token_budget is not None:
            try:
                from cortex.core.retrieval import RetrievalConstraints
                constraints = RetrievalConstraints(token_budget=budget)
            except ImportError:
                pass

        try:
            result = await self.retrieval.retrieve(
                query=query,
                owner_id=effective_owner_id,
                mode=mode,
                constraints=constraints,
                session_id=session_id,
            )
            # Fix 1: Spaced repetition — trigger reinforcement for each retrieved claim
            if getattr(self, "deprecation_pipeline", None) is not None and result is not None:
                for item in getattr(result, "items", []):
                    claim_id = getattr(item, "item_id", None)
                    if claim_id:
                        try:
                            await self.deprecation_pipeline.check_reinforcement(claim_id)
                        except Exception as reinforce_exc:
                            logger.debug(
                                "Reinforcement check failed for %s: %s",
                                claim_id,
                                reinforce_exc,
                            )

            # Debug logging: emit retrieval event
            from cortex.debug import log_retrieval_event
            items = getattr(result, "items", []) if result else []
            top_score = 0.0
            if items:
                scores = [getattr(it, "score", 0.0) or 0.0 for it in items]
                top_score = max(scores) if scores else 0.0
            tokens_used_val = getattr(result, "tokens_used", 0) if result else 0
            log_retrieval_event(
                session_id=session_id or "",
                query=query,
                mode=mode,
                items_returned=len(items),
                top_score=top_score,
                tokens_used=tokens_used_val or 0,
            )

            return result
        except Exception as exc:
            logger.error("Retrieval failed: %s", exc, exc_info=True)
            return None

    # =========================================================================
    # MID-TURN RE-RETRIEVAL (#372)
    # =========================================================================

    async def on_tool_result(
        self,
        session_id: str,
        tool_name: str,
        result_text: str,
        current_context: Optional[List[str]] = None,
        existing_memory_ids: Optional[List[str]] = None,
        budget_tokens: int = 2000,
        owner_id: Optional[str] = None,
    ) -> Optional[Any]:
        """Handle a tool result and optionally trigger supplemental retrieval.

        Called by the host when a tool call returns results mid-conversation.
        If re-retrieval is enabled and a trigger is detected, executes a
        supplemental retrieval and returns the result.

        Args:
            session_id: Active session ID.
            tool_name: Name of the tool that returned results.
            result_text: The tool's result text.
            current_context: Existing context strings for comparison.
            existing_memory_ids: Already-injected memory IDs to avoid duplicates.
            budget_tokens: Remaining token budget for supplemental results.
            owner_id: Multi-tenancy scope key (defaults to config.owner_id).

        Returns:
            ReRetrievalResult if supplemental retrieval was triggered, else None.
        """
        if not getattr(self.config, "re_retrieval_enabled", True):
            return None

        if not result_text or not result_text.strip():
            return None

        try:
            from cortex.core.re_retrieval import (
                ReRetrievalEngine,
                ReRetrievalRequest,
                ReRetrievalTrigger,
            )

            if self.retrieval is None:
                logger.debug("on_tool_result: retrieval pipeline not available")
                return None

            # Lazy-init the re-retrieval engine (one per plugin instance)
            if not hasattr(self, "_re_retrieval_engine"):
                from cortex.core.working_memory import WorkingMemoryCache

                wm = getattr(self, "working_memory", None)
                if wm is None:
                    wm = WorkingMemoryCache(enabled=False)

                self._re_retrieval_engine = ReRetrievalEngine(
                    retrieval=self.retrieval,
                    working_memory=wm,
                    config=self.config,
                )

            engine: ReRetrievalEngine = self._re_retrieval_engine

            # Check if we should trigger
            trigger = await engine.should_trigger(
                new_context=result_text,
                current_context=current_context or [],
            )

            if trigger is None:
                logger.debug(
                    "on_tool_result: no trigger for tool=%s",
                    tool_name,
                )
                return None

            effective_owner = owner_id or self.config.owner_id

            request = ReRetrievalRequest(
                trigger=trigger,
                new_context=result_text,
                session_id=session_id,
                owner_id=effective_owner,
                existing_memory_ids=existing_memory_ids or [],
                budget_tokens=budget_tokens,
            )

            result = await engine.retrieve(request)
            logger.info(
                "on_tool_result: re-retrieval triggered (tool=%s trigger=%s items=%d latency=%.1fms)",
                tool_name,
                trigger.value,
                len(result.items),
                result.latency_ms,
            )
            return result

        except Exception as exc:
            logger.error("on_tool_result failed: %s", exc, exc_info=True)
            return None

    # =========================================================================
    # FEEDBACK
    # =========================================================================

    async def feedback(
        self,
        memory_id: str,
        helpful: bool,
        context: Optional[str] = None,
        target_type: str = "claim",
    ) -> Any:
        """
        Submit retrieval feedback for a memory.

        Args:
            memory_id:   The memory being rated.
            helpful:     True = helpful, False = harmful.
            context:     Optional free-form context string.
            target_type: 'claim' | 'cornerstone' | 'procedure' | etc.

        Returns:
            MemoryFeedback record or None on error.
        """
        if self.feedback_system is None:
            logger.error("FeedbackSystem not available")
            return None
        try:
            result = await self.feedback_system.submit_feedback(
                memory_id=memory_id,
                helpful=helpful,
                context=context,
                target_type=target_type,
            )
            # Emit FEEDBACK_SUBMITTED event
            if getattr(self, "event_emitter", None) is not None and result is not None:
                try:
                    from cortex.events.emitter import CortexEvent, EventType
                    await self.event_emitter.emit(CortexEvent(
                        event_type=EventType.FEEDBACK_SUBMITTED,
                        owner_id=self.config.owner_id,
                        payload={
                            "memory_id": memory_id,
                            "helpful": helpful,
                            "target_type": target_type,
                            "feedback_id": getattr(result, "id", ""),
                        },
                        session_id=self._session_id,
                    ))
                except Exception as emit_exc:
                    logger.debug("Event emit failed (FEEDBACK_SUBMITTED): %s", emit_exc)
            return result
        except Exception as exc:
            logger.error("feedback submission failed: %s", exc, exc_info=True)
            return None

    # =========================================================================
    # DIALECTIC (M14) — Ask your memory
    # =========================================================================

    async def ask(
        self,
        query: str,
        owner_id: Optional[str] = None,
        history: Optional[List[Dict[str, Any]]] = None,
        mode: str = "fast",
        max_steps: int = 5,
    ) -> "AskResult":
        """
        Ask a question and synthesise an answer from retrieved memories.

        Two modes are supported:
            - ``"fast"`` (default): single-shot retrieval + LLM synthesis.
            - ``"agentic"``: multi-step retrieval loop for complex questions.
              Decomposes the question, retrieves per sub-query, checks
              sufficiency, and synthesises a final grounded answer.

        If *history* is provided (fast mode only), passes it to
        ask_with_history() for follow-up question support.

        Args:
            query:     Natural-language question.
            owner_id:  Memory namespace (defaults to plugin config owner_id).
            history:   Optional prior conversation turns — list of {role, content}.
                       Ignored when mode="agentic".
            mode:      ``"fast"`` (default) or ``"agentic"``.
            max_steps: Maximum retrieval steps for agentic mode (default 5).

        Returns:
            AskResult with synthesised answer, source memories, and confidence.
        """
        effective_owner_id = owner_id or self.config.owner_id
        errors: List[str] = []

        if self.retrieval is None or self.llm is None:
            err = "Dialectic requires retrieval pipeline and LLM — one or both unavailable"
            logger.error(err)
            return AskResult(
                answer="I cannot answer: the memory system is not fully initialised.",
                errors=[err],
                ok=False,
            )

        try:
            from cortex.dialectic.engine import DialecticEngine

            engine = DialecticEngine(
                retrieval_engine=self.retrieval,
                llm_provider=getattr(self, "_dialectic_llm", self.llm),
                config=self.config,
            )

            if mode == "agentic":
                response = await engine.ask_agentic(
                    query=query,
                    owner_id=effective_owner_id,
                    max_steps=max_steps,
                )
            elif history:
                response = await engine.ask_with_history(
                    query=query,
                    owner_id=effective_owner_id,
                    history=history,
                )
            else:
                response = await engine.ask(
                    query=query,
                    owner_id=effective_owner_id,
                )

            return AskResult(
                answer=response.answer,
                sources=response.sources,
                confidence=response.confidence,
                reasoning=response.reasoning,
                errors=errors,
                ok=True,
            )

        except Exception as exc:
            err = f"Dialectic ask failed: {exc}"
            logger.error(err, exc_info=True)
            return AskResult(
                answer="An error occurred while answering your question.",
                errors=[err],
                ok=False,
            )
