"""
Seamless memory provider: automatic retrieval and storage per turn.
Makes memory recall unconscious, like human association.

Supports an optional :class:`AsyncStoragePipeline` to decouple writes
from the hot response path (set ``FEATURES__STORE_ASYNC=true``).
"""

from dataclasses import dataclass
from datetime import datetime
from typing import Any

from ..core.schemas import MemoryPacket, RetrievedMemory

try:
    from ..memory.orchestrator import MemoryOrchestrator
except ImportError:
    MemoryOrchestrator = None  # type: ignore

try:
    from ..storage.async_pipeline import AsyncStoragePipeline
except ImportError:
    AsyncStoragePipeline = None  # type: ignore


@dataclass
class SeamlessTurnResult:
    """Result of processing a conversation turn with seamless memory."""

    memory_context: str  # Formatted string for LLM injection
    injected_memories: list[RetrievedMemory]
    stored_count: int
    reconsolidation_applied: bool


class SeamlessMemoryProvider:
    """
    Automatically retrieves relevant memories for each interaction.
    Makes memory recall unconscious - like human association.
    """

    def __init__(
        self,
        orchestrator: "MemoryOrchestrator",
        max_context_tokens: int = 1500,
        auto_store: bool = True,
        relevance_threshold: float = 0.3,
        async_pipeline: Any | None = None,
    ):
        self.orchestrator = orchestrator
        self.max_context_tokens = max_context_tokens
        self.auto_store = auto_store
        self.relevance_threshold = relevance_threshold
        self.async_pipeline = async_pipeline

    async def process_turn(
        self,
        tenant_id: str,
        user_message: str,
        assistant_response: str | None = None,
        session_id: str | None = None,
        turn_id: str | None = None,
        timestamp: datetime | None = None,
        user_timezone: str | None = None,
    ) -> SeamlessTurnResult:
        """
        Process a conversation turn:
        1. Auto-retrieve relevant memories for user message
        2. Optionally store salient information (sync or async)
        3. Run reconsolidation if assistant responded

        When ``async_pipeline`` is enabled, steps 2 and 3 are enqueued
        to a background worker so the response returns after step 1 only.
        """
        # Step 1: Retrieve relevant context BEFORE response (always sync)
        memory_context, injected_memories = await self._retrieve_context(
            tenant_id, user_message, user_timezone=user_timezone
        )

        stored_count = 0
        reconsolidation_applied = False

        if self.auto_store:
            # --- Async storage path ---
            if self.async_pipeline and self.async_pipeline.enabled:
                await self.async_pipeline.enqueue(
                    tenant_id=tenant_id,
                    user_message=user_message,
                    assistant_response=assistant_response,
                    session_id=session_id,
                    turn_id=turn_id,
                    timestamp=timestamp,
                )
                stored_count = -1  # Indicates "queued for async processing"
            else:
                # --- Synchronous storage path (original) ---
                # Step 2: Store user message
                write_result = await self.orchestrator.write(
                    tenant_id=tenant_id,
                    content=user_message,
                    session_id=session_id,
                    context_tags=["conversation", "user_input"],
                    timestamp=timestamp,
                )
                stored_count += write_result.get("chunks_created", 0) or (
                    1 if write_result.get("memory_id") else 0
                )

                # Step 3: Store assistant response + reconsolidate
                if assistant_response:
                    resp_result = await self._process_response(
                        tenant_id=tenant_id,
                        user_message=user_message,
                        assistant_response=assistant_response,
                        session_id=session_id,
                        turn_id=turn_id,
                        retrieved_memories=[m.record for m in injected_memories],
                        timestamp=timestamp,
                    )
                    stored_count += resp_result.get("chunks_created", 0) or (
                        1 if resp_result.get("memory_id") else 0
                    )
                    reconsolidation_applied = resp_result.get("reconsolidation_applied", False)

        return SeamlessTurnResult(
            memory_context=memory_context,
            injected_memories=injected_memories,
            stored_count=stored_count,
            reconsolidation_applied=reconsolidation_applied,
        )

    async def _retrieve_context(
        self,
        tenant_id: str,
        message: str,
        user_timezone: str | None = None,
    ) -> tuple[str, list[RetrievedMemory]]:
        """Retrieve and format memories for injection."""
        packet = await self.orchestrator.read(
            tenant_id=tenant_id,
            query=message,
            max_results=10,
            user_timezone=user_timezone,
        )
        # Filter by relevance and format for LLM
        filtered = [m for m in packet.all_memories if m.relevance_score >= self.relevance_threshold]
        context_str = self._format_for_injection(packet, filtered)
        return context_str, filtered

    def _format_for_injection(
        self,
        packet: MemoryPacket,
        memories: list[RetrievedMemory],
    ) -> str:
        """Build a context string from filtered memories (respects max_context_tokens)."""
        max_chars = self.max_context_tokens * 4  # rough: 4 chars per token
        if not memories:
            return ""

        # Build a minimal packet with only filtered memories for consistent formatting
        from ..core.enums import MemoryType

        facts = [m for m in memories if m.record.type == MemoryType.SEMANTIC_FACT]
        preferences = [m for m in memories if m.record.type == MemoryType.PREFERENCE]
        procedures = [m for m in memories if m.record.type == MemoryType.PROCEDURE]
        constraints = [m for m in memories if m.record.type == MemoryType.CONSTRAINT]
        recent = [
            m
            for m in memories
            if m.record.type
            not in (
                MemoryType.SEMANTIC_FACT,
                MemoryType.PREFERENCE,
                MemoryType.PROCEDURE,
                MemoryType.CONSTRAINT,
            )
        ]

        filtered_packet = MemoryPacket(
            query=packet.query,
            retrieved_at=packet.retrieved_at,
            facts=facts,
            recent_episodes=recent,
            preferences=preferences,
            procedures=procedures,
            constraints=constraints,
        )
        return filtered_packet.to_context_string(max_chars=max_chars)

    async def _process_response(
        self,
        tenant_id: str,
        user_message: str,
        assistant_response: str,
        session_id: str | None,
        turn_id: str | None,
        retrieved_memories: list,
        timestamp: datetime | None = None,
    ) -> dict:
        """Store assistant response and run reconsolidation."""

        # Store assistant response
        write_result = await self.orchestrator.write(
            tenant_id=tenant_id,
            content=assistant_response,
            session_id=session_id,
            context_tags=["conversation", "assistant_response"],
            timestamp=timestamp,
        )

        scope_id = session_id or tenant_id
        tid = turn_id or "turn"
        reconsolidation_applied = False

        if (
            retrieved_memories
            and hasattr(self.orchestrator, "reconsolidation")
            and self.orchestrator.reconsolidation
        ):
            from ..core.schemas import MemoryRecord

            records = [
                m if isinstance(m, MemoryRecord) else getattr(m, "record", m)
                for m in retrieved_memories
            ]
            rec_result = await self.orchestrator.reconsolidation.process_turn(
                tenant_id=tenant_id,
                scope_id=scope_id,
                turn_id=tid,
                user_message=user_message,
                assistant_response=assistant_response,
                retrieved_memories=records,
            )
            reconsolidation_applied = (
                rec_result.memories_processed > 0 or len(rec_result.operations_applied) > 0
            )

        return {
            **write_result,
            "reconsolidation_applied": reconsolidation_applied,
        }
