"""SDK session context — holds config, audit logger, and connection state."""

from __future__ import annotations

import time
import uuid
from typing import Any, Dict, Optional

from databridge_core.sdk.core.audit import AuditLogger
from databridge_core.sdk.core.config import SDKConfig
from databridge_core.sdk.core.connection import SnowflakeConnection
from databridge_core.sdk.core.graph_bridge import GraphBridge
from databridge_core.sdk.core.ike_bridge import IKEBridge


class SDKContext:
    """Session context threading config, audit, and connection through workflows.

    Every workflow receives the context so it can log audit entries,
    access Snowflake, read configuration, use IKE for institutional
    knowledge, and update graph systems (lineage, evidence, vectors,
    KB, entity linker, Codex, IKE graph, formula graph).

    Usage::

        ctx = SDKContext()
        ctx = SDKContext(config=SDKConfig(sdk_data_dir="custom/"))
        ctx = SDKContext(user_id="analyst@acme.com")

        # With IKE enabled
        ctx = SDKContext(config=SDKConfig(enable_ike=True, ike_endpoint="http://localhost:8000"))
        if ctx.ike.available:
            ctx.ike.score("rule_001", "item_42", 0.87)

        # Graph systems (enabled by default)
        if ctx.graphs.available:
            ctx.graphs.record_lineage_node("sdk", "my_table", "TABLE")
    """

    def __init__(
        self,
        config: Optional[SDKConfig] = None,
        user_id: Optional[str] = None,
        connection: Optional[SnowflakeConnection] = None,
        metadata: Optional[Dict[str, Any]] = None,
    ):
        self.config = config or SDKConfig()
        self.user_id = user_id
        self.metadata = metadata or {}
        self.trace_id: str = uuid.uuid4().hex[:16]

        self.audit = AuditLogger(
            log_path=self.config.audit_log_path,
            user_id=user_id,
        )

        self._connection = connection
        self._ike: Optional[IKEBridge] = None
        self._graphs: Optional[GraphBridge] = None
        self._async_ike: Optional[Any] = None

        # Per-run cache (E4)
        self._run_cache: Dict[str, Any] = {}
        self._run_cache_ttls: Dict[str, float] = {}

    @property
    def connection(self) -> SnowflakeConnection:
        """Lazy Snowflake connection.

        Creates a ``SnowflakeConnection`` on first access using the
        configured connection name.  Call ``connection.connect()``
        explicitly before use.
        """
        if self._connection is None:
            self._connection = SnowflakeConnection(
                connection_name=self.config.snowflake_connection_name,
            )
        return self._connection

    @property
    def graphs(self) -> GraphBridge:
        """Lazy graph bridge — created on first access if enabled.

        Returns a ``GraphBridge`` instance.  If graphs are not enabled or
        no graph systems are installed, the bridge's ``.available``
        property returns False and all methods safely return None.
        """
        if self._graphs is None:
            if self.config.enable_graphs:
                self._graphs = GraphBridge(config=self.config)
            else:
                # Return a disconnected bridge (available=False)
                self._graphs = GraphBridge.__new__(GraphBridge)
                self._graphs._lineage = None
                self._graphs._evidence = None
                self._graphs._vector_store = None
                self._graphs._embedder = None
                self._graphs._kb_pipeline = None
                self._graphs._config = self.config
                self._graphs._en_linker = False
                self._graphs._en_codex = False
                self._graphs._en_ike_graph = False
                self._graphs._en_formula_graph = False
                self._graphs._ike_client = None
        return self._graphs

    @property
    def async_ike(self) -> Any:
        """Lazy async IKE bridge — created on first access if enabled.

        Returns an ``AsyncIKEBridge`` instance.  If IKE is not enabled,
        not installed, or the async client is unavailable, the bridge's
        ``.available`` property returns False.
        """
        if self._async_ike is None:
            from databridge_core.sdk.core.ike_bridge import AsyncIKEBridge, async_ike_available

            if self.config.enable_ike and self.config.ike_endpoint and async_ike_available():
                self._async_ike = AsyncIKEBridge(
                    endpoint=self.config.ike_endpoint,
                    tenant_id=self.config.ike_tenant_id,
                )
            else:
                # Return a disconnected bridge (available=False)
                self._async_ike = AsyncIKEBridge.__new__(AsyncIKEBridge)
                self._async_ike._client = None
                self._async_ike._endpoint = ""
                self._async_ike._tenant_id = self.config.ike_tenant_id
                self._async_ike._timeout = 30.0
        return self._async_ike

    @property
    def ike(self) -> IKEBridge:
        """Lazy IKE bridge — created on first access if enabled.

        Returns an ``IKEBridge`` instance.  If IKE is not enabled or
        not installed, the bridge's ``.available`` property returns
        False and all methods safely return None.
        """
        if self._ike is None:
            if self.config.enable_ike and self.config.ike_endpoint:
                self._ike = IKEBridge(
                    endpoint=self.config.ike_endpoint,
                    tenant_id=self.config.ike_tenant_id,
                )
            else:
                # Return a disconnected bridge (available=False)
                self._ike = IKEBridge.__new__(IKEBridge)
                self._ike._client = None
                self._ike._endpoint = ""
                self._ike._tenant_id = self.config.ike_tenant_id
                self._ike._timeout = 30.0
        return self._ike

    # --- Per-run cache (E4) ---

    def cache_get(self, key: str) -> Any:
        """Get a value from the per-run cache, returning None on miss or expiry."""
        if key not in self._run_cache:
            return None
        ttl_deadline = self._run_cache_ttls.get(key)
        if ttl_deadline is not None and time.monotonic() > ttl_deadline:
            del self._run_cache[key]
            del self._run_cache_ttls[key]
            return None
        return self._run_cache[key]

    def cache_set(self, key: str, value: Any, ttl: Optional[int] = None) -> None:
        """Store a value in the per-run cache with optional TTL (seconds)."""
        self._run_cache[key] = value
        if ttl is None:
            ttl = self.config.graph_query_cache_ttl
        self._run_cache_ttls[key] = time.monotonic() + ttl

    def cache_clear(self) -> None:
        """Clear the entire per-run cache."""
        self._run_cache.clear()
        self._run_cache_ttls.clear()

    def close(self) -> None:
        """Flush audit, close connections, and clear cache."""
        self.audit.flush()
        self.cache_clear()
        if self._connection and self._connection.is_connected:
            self._connection.close()
        if self._ike and self._ike.available:
            self._ike.close()
        if self._async_ike and hasattr(self._async_ike, 'available') and self._async_ike.available:
            # AsyncIKEBridge.close() is async; best-effort sync teardown
            self._async_ike._client = None
        if self._graphs:
            self._graphs.close()
            self._graphs = None

    def __enter__(self) -> "SDKContext":
        return self

    def __exit__(self, exc_type, exc_val, exc_tb) -> None:
        self.close()
