"""EntityOpsMixin — entity resolution and cornerstone operations."""
from __future__ import annotations

import logging
from typing import Any, List, Optional

from .models import EvolutionProposalResult

logger = logging.getLogger(__name__)


class EntityOpsMixin:
    """Mixin providing entity merge, cornerstone seal/unseal, and evolution ops."""

    async def suggest_merges(
        self,
        similarity_threshold: float = 80.0,
        confidence_threshold: float = 0.5,
        cross_session_owner_ids: Optional[List[str]] = None,
        use_embedding_prefilter: bool = True,
        embedding_prefilter_threshold: float = 0.60,
    ) -> List[Any]:
        """Return entity pairs that look like duplicates, for human/dream-cycle review.

        Delegates to EntityResolver.suggest_merges() with embedding-based
        pre-filtering and optional cross-session co-reference detection.

        Args:
            similarity_threshold: Minimum fuzzy alias score (0-100).
            confidence_threshold: Minimum normalised confidence (0.0-1.0).
            cross_session_owner_ids: Compare entities from additional owner IDs
                against the plugin's own owner_id.  Enables cross-session
                co-reference linking.
            use_embedding_prefilter: Skip fuzzy comparison for pairs with low
                embedding cosine similarity (faster for large entity sets).
            embedding_prefilter_threshold: Cosine-similarity cutoff (0-1).

        Returns:
            List[MergeSuggestion] sorted by similarity (highest first).
            Returns [] if EntityResolver is not available.
        """
        if self.entity_resolver is None:
            logger.warning("suggest_merges: EntityResolver not available")
            return []
        try:
            return await self.entity_resolver.suggest_merges(
                owner_id=self.config.owner_id,
                similarity_threshold=similarity_threshold,
                confidence_threshold=confidence_threshold,
                cross_session_owner_ids=cross_session_owner_ids,
                use_embedding_prefilter=use_embedding_prefilter,
                embedding_prefilter_threshold=embedding_prefilter_threshold,
            )
        except Exception as exc:
            logger.error("suggest_merges failed: %s", exc, exc_info=True)
            return []

    async def merge_entities(
        self,
        keep_id: str,
        merge_id: str,
    ) -> Optional[Any]:
        """Merge two entities that refer to the same real-world thing.

        All aliases and claim FK references (subject_entity_id / object_entity_id)
        are atomically reassigned from merge_id → keep_id.  The merge_id entity
        is then deleted.  This is typically called after reviewing suggestions
        from suggest_merges().

        Args:
            keep_id: Entity to keep (the canonical one).
            merge_id: Entity to absorb and delete.

        Returns:
            The surviving Entity, or None if EntityResolver is unavailable.

        Raises:
            ValueError: If keep_id == merge_id.
        """
        if self.entity_resolver is None:
            logger.warning("merge_entities: EntityResolver not available")
            return None
        try:
            result = await self.entity_resolver.merge_entities(
                keep_id=keep_id,
                merge_id=merge_id,
            )
            # Emit ENTITY_MERGED 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.ENTITY_MERGED,
                        owner_id=self.config.owner_id,
                        payload={
                            "keep_id": keep_id,
                            "merge_id": merge_id,
                            "surviving_entity": getattr(result, "canonical_name", ""),
                        },
                    ))
                except Exception as emit_exc:
                    logger.debug("Event emit failed (ENTITY_MERGED): %s", emit_exc)
            return result
        except ValueError:
            raise
        except Exception as exc:
            logger.error("merge_entities failed: %s", exc, exc_info=True)
            return None

    # =========================================================================
    # CORNERSTONES
    # =========================================================================

    async def get_cornerstones(self) -> List[Any]:
        """Return all sealed cornerstones. Returns [] on error."""
        if self.cornerstone_guardian is None:
            logger.error("CornerstoneGuardian not available")
            return []
        try:
            return await self.cornerstone_guardian.get_all(
                owner_id=self.config.owner_id,
            )
        except Exception as exc:
            logger.error("get_cornerstones failed: %s", exc, exc_info=True)
            return []

    async def seal_cornerstone(
        self,
        memory_id: str,
        label: str = "",
        content: str = "",
        **kwargs: Any,
    ) -> Any:
        """
        Seal *memory_id* as a cornerstone.

        Returns a CornerstoneMemory or None on error.
        """
        if self.cornerstone_guardian is None:
            logger.error("CornerstoneGuardian not available")
            return None

        # Validate and sanitize label and content
        import cortex.api.plugin as _plugin_pkg
        effective_label = label or memory_id
        effective_label, content = _plugin_pkg.validate_cornerstone_fields(effective_label, content)

        try:
            result = await self.cornerstone_guardian.seal(
                label=effective_label,
                content=content,
                owner_id=self.config.owner_id,
                **kwargs,
            )
            # Emit CORNERSTONE_SEALED 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.CORNERSTONE_SEALED,
                        owner_id=self.config.owner_id,
                        payload={
                            "cornerstone_id": getattr(result, "id", ""),
                            "label": effective_label,
                        },
                    ))
                except Exception as emit_exc:
                    logger.debug("Event emit failed (CORNERSTONE_SEALED): %s", emit_exc)
            return result
        except Exception as exc:
            logger.error("seal_cornerstone failed: %s", exc, exc_info=True)
            return None

    async def unseal_cornerstone(self, memory_id: str) -> Any:
        """
        Unseal a cornerstone, making it eligible for modification/deletion.

        Returns an updated CornerstoneMemory or None on error.
        """
        if self.cornerstone_guardian is None:
            logger.error("CornerstoneGuardian not available")
            return None
        try:
            return await self.cornerstone_guardian.unseal(cs_id=memory_id)
        except Exception as exc:
            logger.error("unseal_cornerstone failed: %s", exc, exc_info=True)
            return None

    async def propose_cornerstone_evolution(
        self,
        cornerstone_id: str,
        new_evidence: str,
        source: str = "operator",
    ) -> EvolutionProposalResult:
        """
        Propose an LLM-generated evolution for a cornerstone.

        Uses the LLM to generate a revised golden copy that incorporates
        *new_evidence* while preserving core identity.  The proposal is stored
        and returned with its diff/reasoning — it does NOT auto-apply unless
        ``config.cornerstone_auto_apply_threshold`` is met.

        Args:
            cornerstone_id: The cornerstone to evolve.
            new_evidence:   Claims / observations that suggest the update.
            source:         Origin tag ('operator', 'dream_cycle', …).

        Returns:
            EvolutionProposalResult with all proposal details.
        """
        if self.cornerstone_guardian is None:
            err = "CornerstoneGuardian not available"
            logger.error(err)
            return EvolutionProposalResult(
                action="error",
                proposal_id="",
                proposed_content="",
                diff="",
                reasoning="",
                confidence=0.0,
                errors=[err],
                ok=False,
            )
        try:
            result = await self.cornerstone_guardian.propose_evolution(
                cornerstone_id=cornerstone_id,
                new_evidence=new_evidence,
                source=source,
            )
            return EvolutionProposalResult(
                action=result.get("action", "queued"),
                proposal_id=result.get("proposal_id", ""),
                proposed_content=result.get("proposed_content", ""),
                diff=result.get("diff", ""),
                reasoning=result.get("reasoning", ""),
                confidence=result.get("confidence", 0.0),
                requires_operator_review=result.get("requires_operator_review", True),
                cornerstone=result.get("cornerstone"),
                ok=True,
            )
        except ValueError as exc:
            err = str(exc)
            logger.warning("propose_cornerstone_evolution validation error: %s", err)
            return EvolutionProposalResult(
                action="error",
                proposal_id="",
                proposed_content="",
                diff="",
                reasoning="",
                confidence=0.0,
                errors=[err],
                ok=False,
            )
        except Exception as exc:
            err = f"propose_cornerstone_evolution failed: {exc}"
            logger.error(err, exc_info=True)
            return EvolutionProposalResult(
                action="error",
                proposal_id="",
                proposed_content="",
                diff="",
                reasoning="",
                confidence=0.0,
                errors=[err],
                ok=False,
            )

    async def apply_cornerstone_evolution(
        self,
        cornerstone_id: str,
        proposal_id: str,
        applied_by: str = "operator",
    ) -> Any:
        """
        Apply an approved evolution proposal to a cornerstone.

        Fetches the pending proposal, validates it, and calls
        CornerstoneGuardian.apply_evolution() to update the golden copy.

        Args:
            cornerstone_id: The cornerstone to update.
            proposal_id:    ID of the pending proposal to apply.
            applied_by:     Who is authorizing the change.

        Returns:
            Updated CornerstoneMemory on success, or None on error.
        """
        if self.cornerstone_guardian is None:
            logger.error("CornerstoneGuardian not available")
            return None
        try:
            return await self.cornerstone_guardian.apply_evolution(
                cornerstone_id=cornerstone_id,
                proposal_id=proposal_id,
                applied_by=applied_by,
            )
        except ValueError as exc:
            logger.warning("apply_cornerstone_evolution validation error: %s", exc)
            return None
        except Exception as exc:
            logger.error("apply_cornerstone_evolution failed: %s", exc, exc_info=True)
            return None
