"""IKE (Institutional Knowledge Engine) bridge — optional integration.

Wraps the IKE sync client with try/except ImportError so the SDK
works with or without IKE installed.  When IKE is available, workflows
gain institutional memory: scoring, fix generation, debate, knowledge
base queries, self-learning cycles, and Excel wisdom extraction.

IKE Pillars mapped to SDK workflows:
    P1 Knowledge Base — kb_query, catalog, profiling, reconciliation
    P2 Fix Engine     — codegen (fix_generation)
    P3 Debate Engine  — blce.full_engine
    P4 Scoring Engine — detection, triage
    P5 Learning Cycle — quality_governance
    P6 Excel Wisdom   — blce.logic_extraction
"""

from __future__ import annotations

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

logger = logging.getLogger(__name__)

# --- IKE availability check ------------------------------------------------
_IKE_AVAILABLE = False
_IKEClient = None

try:
    from ike.client.sync_client import IKEClient as _IKEClientCls

    _IKE_AVAILABLE = True
    _IKEClient = _IKEClientCls
except ImportError:
    pass


def ike_available() -> bool:
    """Return True if the ``ike`` package is installed."""
    return _IKE_AVAILABLE


class IKEBridge:
    """Optional bridge to IKE's institutional knowledge capabilities.

    All methods are safe to call even when IKE is not installed — they
    return ``None`` and log a debug message.  Workflows check
    ``self.available`` before calling IKE methods.

    Usage::

        bridge = IKEBridge(endpoint="http://localhost:8000", tenant_id="acme")
        if bridge.available:
            result = bridge.score("rule_FDP_001", "item_42", 0.87)
            bridge.ingest_knowledge("GL account 4100 is always Rent Expense")
    """

    def __init__(
        self,
        endpoint: str = "http://localhost:8000",
        tenant_id: str = "default",
        timeout: float = 30.0,
    ):
        self._endpoint = endpoint
        self._tenant_id = tenant_id
        self._timeout = timeout
        self._client: Optional[Any] = None

        if _IKE_AVAILABLE:
            try:
                self._client = _IKEClient(
                    base_url=endpoint,
                    tenant_id=tenant_id,
                    timeout=timeout,
                )
                logger.info("IKE bridge connected to %s (tenant=%s)", endpoint, tenant_id)
            except Exception as exc:
                logger.warning("IKE bridge init failed: %s", exc)
                self._client = None

    @property
    def available(self) -> bool:
        """True if IKE client is connected and ready."""
        return self._client is not None

    def close(self) -> None:
        """Close the IKE client connection."""
        if self._client is not None:
            self._client.close()
            self._client = None

    # --- P1: Knowledge Base -------------------------------------------------

    def ingest_knowledge(
        self,
        content: str,
        source_type: str = "manual",
        tags: Optional[List[str]] = None,
        **kwargs: Any,
    ) -> Optional[str]:
        """Ingest an entry into IKE's knowledge base.

        Returns the entry_id or None if IKE is unavailable.
        """
        if not self.available:
            logger.debug("IKE unavailable — skipping knowledge ingest")
            return None
        try:
            return self._client.ingest(content, source_type, tags, **kwargs)
        except Exception as exc:
            logger.warning("IKE ingest failed: %s", exc)
            return None

    def query_knowledge(
        self,
        query: str,
        top_k: int = 10,
        **kwargs: Any,
    ) -> Optional[Dict[str, Any]]:
        """Query IKE's hybrid knowledge base (vector + graph + lexical).

        Returns search results or None if IKE is unavailable.
        """
        if not self.available:
            logger.debug("IKE unavailable — skipping knowledge query")
            return None
        try:
            return self._client.query(query, top_k, **kwargs)
        except Exception as exc:
            logger.warning("IKE query failed: %s", exc)
            return None

    def kb_stats(self) -> Optional[Dict[str, Any]]:
        """Get IKE knowledge base statistics."""
        if not self.available:
            return None
        try:
            return self._client.kb_stats()
        except Exception as exc:
            logger.warning("IKE kb_stats failed: %s", exc)
            return None

    def evidence_trail(self, entry_id: str) -> Optional[Dict[str, Any]]:
        """Get auditable evidence trail for a KB entry."""
        if not self.available:
            return None
        try:
            return self._client.evidence_trail(entry_id)
        except Exception as exc:
            logger.warning("IKE evidence_trail failed: %s", exc)
            return None

    # --- P2: Fix Engine -----------------------------------------------------

    def generate_fix(
        self,
        rule_id: str,
        finding: Dict[str, Any],
        context: Optional[Dict[str, Any]] = None,
    ) -> Optional[Dict[str, Any]]:
        """Generate a fix using IKE's 3-tier fix engine.

        Tier 1: trust replay, Tier 2: RAG, Tier 3: LLM fallback.
        Returns fix result or None if IKE is unavailable.
        """
        if not self.available:
            logger.debug("IKE unavailable — skipping fix generation")
            return None
        try:
            return self._client.generate_fix(rule_id, finding, context)
        except Exception as exc:
            logger.warning("IKE generate_fix failed: %s", exc)
            return None

    def approve_fix(self, fix_id: str) -> Optional[Dict[str, Any]]:
        """Approve a generated fix, advancing trust FSM."""
        if not self.available:
            return None
        try:
            return self._client.approve_fix(fix_id)
        except Exception as exc:
            logger.warning("IKE approve_fix failed: %s", exc)
            return None

    def reject_fix(self, fix_id: str, reason: str) -> Optional[Dict[str, Any]]:
        """Reject a generated fix with reason."""
        if not self.available:
            return None
        try:
            return self._client.reject_fix(fix_id, reason)
        except Exception as exc:
            logger.warning("IKE reject_fix failed: %s", exc)
            return None

    def trust_status(self, rule_id: str) -> Optional[Dict[str, Any]]:
        """Get trust state for a rule (manual_review/probation/trusted/suspended)."""
        if not self.available:
            return None
        try:
            return self._client.trust_status(rule_id)
        except Exception as exc:
            logger.warning("IKE trust_status failed: %s", exc)
            return None

    # --- P3: Debate Engine --------------------------------------------------

    def run_debate(
        self,
        topic: str,
        finding: Dict[str, Any],
        context: Optional[Dict[str, Any]] = None,
    ) -> Optional[Dict[str, Any]]:
        """Run a 3-agent debate (Proposer-Challenger-Arbiter).

        Returns verdict or None if IKE is unavailable.
        """
        if not self.available:
            logger.debug("IKE unavailable — skipping debate")
            return None
        try:
            return self._client.run_debate(topic, finding, context)
        except Exception as exc:
            logger.warning("IKE run_debate failed: %s", exc)
            return None

    def learn_verdict(self, preferred_role: str, reason: str) -> Optional[Dict[str, Any]]:
        """Record verdict preference for future debates."""
        if not self.available:
            return None
        try:
            return self._client.learn_verdict(preferred_role, reason)
        except Exception as exc:
            logger.warning("IKE learn_verdict failed: %s", exc)
            return None

    # --- P4: Scoring Engine -------------------------------------------------

    def score(
        self,
        bandit_id: str,
        item_id: str,
        raw_confidence: float,
        context: Optional[Dict[str, Any]] = None,
    ) -> Optional[Dict[str, Any]]:
        """Score an item using Thompson Sampling + SPRT + Platt calibration.

        Returns calibrated score or None if IKE is unavailable.
        """
        if not self.available:
            logger.debug("IKE unavailable — skipping scoring")
            return None
        try:
            return self._client.score(bandit_id, item_id, raw_confidence, context)
        except Exception as exc:
            logger.warning("IKE score failed: %s", exc)
            return None

    def batch_score(self, items: List[Dict[str, Any]]) -> Optional[List[Dict[str, Any]]]:
        """Score multiple items in batch."""
        if not self.available:
            return None
        try:
            return self._client.batch_score(items)
        except Exception as exc:
            logger.warning("IKE batch_score failed: %s", exc)
            return None

    def score_feedback(
        self,
        bandit_id: str,
        item_id: str,
        outcome: str,
        reason: Optional[str] = None,
    ) -> Optional[Dict[str, Any]]:
        """Record feedback on a scored item (confirmed/dismissed)."""
        if not self.available:
            return None
        try:
            return self._client.score_feedback(bandit_id, item_id, outcome, reason)
        except Exception as exc:
            logger.warning("IKE score_feedback failed: %s", exc)
            return None

    def calibrate(self, bandit_id: str) -> Optional[Dict[str, Any]]:
        """Run Platt calibration on a scoring bandit."""
        if not self.available:
            return None
        try:
            return self._client.calibrate(bandit_id)
        except Exception as exc:
            logger.warning("IKE calibrate failed: %s", exc)
            return None

    def detection_stats(self, bandit_id: str) -> Optional[Dict[str, Any]]:
        """Get detection statistics for a scoring bandit."""
        if not self.available:
            return None
        try:
            return self._client.detection_stats(bandit_id)
        except Exception as exc:
            logger.warning("IKE detection_stats failed: %s", exc)
            return None

    # --- P5: Learning Cycle -------------------------------------------------

    def run_learning_cycle(self) -> Optional[Dict[str, Any]]:
        """Run the 7-step self-learning cycle.

        Steps: quality gates -> falsification -> clustering ->
        retuning -> regression -> audit -> bandit update.
        Returns cycle result or None if IKE is unavailable.
        """
        if not self.available:
            logger.debug("IKE unavailable — skipping learning cycle")
            return None
        try:
            return self._client.run_learning_cycle()
        except Exception as exc:
            logger.warning("IKE run_learning_cycle failed: %s", exc)
            return None

    def learning_status(self) -> Optional[Dict[str, Any]]:
        """Get current learning cycle status."""
        if not self.available:
            return None
        try:
            return self._client.learning_status()
        except Exception as exc:
            logger.warning("IKE learning_status failed: %s", exc)
            return None

    def export_patterns(self) -> Optional[List[Dict[str, Any]]]:
        """Export anonymized patterns for federated learning."""
        if not self.available:
            return None
        try:
            return self._client.export_patterns()
        except Exception as exc:
            logger.warning("IKE export_patterns failed: %s", exc)
            return None

    def import_patterns(self, patterns: List[Dict[str, Any]]) -> Optional[int]:
        """Import anonymized patterns from another tenant."""
        if not self.available:
            return None
        try:
            return self._client.import_patterns(patterns)
        except Exception as exc:
            logger.warning("IKE import_patterns failed: %s", exc)
            return None

    # --- Convenience --------------------------------------------------------

    def __repr__(self) -> str:
        status = "connected" if self.available else "unavailable"
        return f"IKEBridge(endpoint={self._endpoint!r}, tenant={self._tenant_id!r}, {status})"


# -----------------------------------------------------------------------
# Async IKE Bridge (E3)
# -----------------------------------------------------------------------

_ASYNC_IKE_AVAILABLE = False
_AsyncIKEClient = None

try:
    from ike.client.async_client import AsyncIKEClient as _AsyncIKEClientCls

    _ASYNC_IKE_AVAILABLE = True
    _AsyncIKEClient = _AsyncIKEClientCls
except ImportError:
    try:
        from ike.ike.client.async_client import AsyncIKEClient as _AsyncIKEClientCls

        _ASYNC_IKE_AVAILABLE = True
        _AsyncIKEClient = _AsyncIKEClientCls
    except ImportError:
        pass


def async_ike_available() -> bool:
    """Return True if the async ``ike`` client is installed."""
    return _ASYNC_IKE_AVAILABLE


class AsyncIKEBridge:
    """Async counterpart of :class:`IKEBridge`.

    Mirrors IKE P1–P5 methods as ``async def``.  P6 (Excel) is excluded
    because ``AsyncIKEClient`` does not expose it.

    Usage::

        bridge = AsyncIKEBridge(endpoint="http://localhost:8000")
        if bridge.available:
            result = await bridge.score("rule_001", "item_42", 0.87)
            await bridge.close()
    """

    def __init__(
        self,
        endpoint: str = "http://localhost:8000",
        tenant_id: str = "default",
        timeout: float = 30.0,
    ):
        self._endpoint = endpoint
        self._tenant_id = tenant_id
        self._timeout = timeout
        self._client: Optional[Any] = None

        if _ASYNC_IKE_AVAILABLE:
            try:
                self._client = _AsyncIKEClient(
                    base_url=endpoint,
                    tenant_id=tenant_id,
                    timeout=timeout,
                )
                logger.info("Async IKE bridge connected to %s (tenant=%s)", endpoint, tenant_id)
            except Exception as exc:
                logger.warning("Async IKE bridge init failed: %s", exc)
                self._client = None

    @property
    def available(self) -> bool:
        """True if async IKE client is connected and ready."""
        return self._client is not None

    async def close(self) -> None:
        """Close the async IKE client connection."""
        if self._client is not None:
            await self._client.close()
            self._client = None

    # --- P1: Knowledge Base -------------------------------------------------

    async def ingest_knowledge(
        self, content: str, source_type: str = "manual",
        tags: Optional[List[str]] = None, **kwargs: Any,
    ) -> Optional[str]:
        if not self.available:
            return None
        try:
            return await self._client.ingest(content, source_type, tags, **kwargs)
        except Exception as exc:
            logger.warning("Async IKE ingest failed: %s", exc)
            return None

    async def query_knowledge(
        self, query: str, top_k: int = 10, **kwargs: Any,
    ) -> Optional[Dict[str, Any]]:
        if not self.available:
            return None
        try:
            return await self._client.query(query, top_k, **kwargs)
        except Exception as exc:
            logger.warning("Async IKE query failed: %s", exc)
            return None

    async def kb_stats(self) -> Optional[Dict[str, Any]]:
        if not self.available:
            return None
        try:
            return await self._client.kb_stats()
        except Exception as exc:
            logger.warning("Async IKE kb_stats failed: %s", exc)
            return None

    async def evidence_trail(self, entry_id: str) -> Optional[Dict[str, Any]]:
        if not self.available:
            return None
        try:
            return await self._client.evidence_trail(entry_id)
        except Exception as exc:
            logger.warning("Async IKE evidence_trail failed: %s", exc)
            return None

    # --- P2: Fix Engine -----------------------------------------------------

    async def generate_fix(
        self, rule_id: str, finding: Dict[str, Any],
        context: Optional[Dict[str, Any]] = None,
    ) -> Optional[Dict[str, Any]]:
        if not self.available:
            return None
        try:
            return await self._client.generate_fix(rule_id, finding, context)
        except Exception as exc:
            logger.warning("Async IKE generate_fix failed: %s", exc)
            return None

    async def approve_fix(self, fix_id: str) -> Optional[Dict[str, Any]]:
        if not self.available:
            return None
        try:
            return await self._client.approve_fix(fix_id)
        except Exception as exc:
            logger.warning("Async IKE approve_fix failed: %s", exc)
            return None

    async def reject_fix(self, fix_id: str, reason: str) -> Optional[Dict[str, Any]]:
        if not self.available:
            return None
        try:
            return await self._client.reject_fix(fix_id, reason)
        except Exception as exc:
            logger.warning("Async IKE reject_fix failed: %s", exc)
            return None

    async def trust_status(self, rule_id: str) -> Optional[Dict[str, Any]]:
        if not self.available:
            return None
        try:
            return await self._client.trust_status(rule_id)
        except Exception as exc:
            logger.warning("Async IKE trust_status failed: %s", exc)
            return None

    # --- P3: Debate Engine --------------------------------------------------

    async def run_debate(
        self, topic: str, finding: Dict[str, Any],
        context: Optional[Dict[str, Any]] = None,
    ) -> Optional[Dict[str, Any]]:
        if not self.available:
            return None
        try:
            return await self._client.run_debate(topic, finding, context)
        except Exception as exc:
            logger.warning("Async IKE run_debate failed: %s", exc)
            return None

    async def learn_verdict(
        self, preferred_role: str, reason: str,
    ) -> Optional[Dict[str, Any]]:
        if not self.available:
            return None
        try:
            return await self._client.learn_verdict(preferred_role, reason)
        except Exception as exc:
            logger.warning("Async IKE learn_verdict failed: %s", exc)
            return None

    # --- P4: Scoring Engine -------------------------------------------------

    async def score(
        self, bandit_id: str, item_id: str, raw_confidence: float,
        context: Optional[Dict[str, Any]] = None,
    ) -> Optional[Dict[str, Any]]:
        if not self.available:
            return None
        try:
            return await self._client.score(bandit_id, item_id, raw_confidence, context)
        except Exception as exc:
            logger.warning("Async IKE score failed: %s", exc)
            return None

    async def batch_score(self, items: List[Dict[str, Any]]) -> Optional[List[Dict[str, Any]]]:
        if not self.available:
            return None
        try:
            return await self._client.batch_score(items)
        except Exception as exc:
            logger.warning("Async IKE batch_score failed: %s", exc)
            return None

    async def score_feedback(
        self, bandit_id: str, item_id: str, outcome: str,
        reason: Optional[str] = None,
    ) -> Optional[Dict[str, Any]]:
        if not self.available:
            return None
        try:
            return await self._client.score_feedback(bandit_id, item_id, outcome, reason)
        except Exception as exc:
            logger.warning("Async IKE score_feedback failed: %s", exc)
            return None

    async def calibrate(self, bandit_id: str) -> Optional[Dict[str, Any]]:
        if not self.available:
            return None
        try:
            return await self._client.calibrate(bandit_id)
        except Exception as exc:
            logger.warning("Async IKE calibrate failed: %s", exc)
            return None

    async def detection_stats(self, bandit_id: str) -> Optional[Dict[str, Any]]:
        if not self.available:
            return None
        try:
            return await self._client.detection_stats(bandit_id)
        except Exception as exc:
            logger.warning("Async IKE detection_stats failed: %s", exc)
            return None

    # --- P5: Learning Cycle -------------------------------------------------

    async def run_learning_cycle(self) -> Optional[Dict[str, Any]]:
        if not self.available:
            return None
        try:
            return await self._client.run_learning_cycle()
        except Exception as exc:
            logger.warning("Async IKE run_learning_cycle failed: %s", exc)
            return None

    async def learning_status(self) -> Optional[Dict[str, Any]]:
        if not self.available:
            return None
        try:
            return await self._client.learning_status()
        except Exception as exc:
            logger.warning("Async IKE learning_status failed: %s", exc)
            return None

    async def export_patterns(self) -> Optional[List[Dict[str, Any]]]:
        if not self.available:
            return None
        try:
            return await self._client.export_patterns()
        except Exception as exc:
            logger.warning("Async IKE export_patterns failed: %s", exc)
            return None

    async def import_patterns(self, patterns: List[Dict[str, Any]]) -> Optional[int]:
        if not self.available:
            return None
        try:
            return await self._client.import_patterns(patterns)
        except Exception as exc:
            logger.warning("Async IKE import_patterns failed: %s", exc)
            return None

    def __repr__(self) -> str:
        status = "connected" if self.available else "unavailable"
        return f"AsyncIKEBridge(endpoint={self._endpoint!r}, tenant={self._tenant_id!r}, {status})"
