"""Global configuration for Hindsight-LiteLLM integration.

This module provides a clean API for configuring Hindsight integration:

1. configure() - Connection settings + default per-call settings
   - API URL, authentication, and default values for all per-call settings

2. set_defaults() - Update default values for per-call settings
   - Convenience function to update defaults without reconfiguring connection

3. Per-call kwargs (hindsight_* prefix) - Override any setting per-call
   - hindsight_bank_id, hindsight_budget, hindsight_inject_memories, etc.

4. set_bank_mission() - Set the mission for a memory bank (for mental models)
"""

import os
from dataclasses import asdict, dataclass, field, fields
from enum import Enum
from typing import Any, Dict, List, Optional

# Default Hindsight API URL (production)
DEFAULT_HINDSIGHT_API_URL = "https://api.hindsight.vectorize.io"
DEFAULT_BANK_ID = "default"
HINDSIGHT_API_KEY_ENV = "HINDSIGHT_API_KEY"


class MemoryInjectionMode(str, Enum):
    """How memories should be injected into the prompt.

    Use inject_memories=False if you don't want memory injection.
    """

    SYSTEM_MESSAGE = "system_message"  # Add to/create system message
    PREPEND_USER = "prepend_user"  # Prepend to last user message


@dataclass
class HindsightCallSettings:
    """Unified settings for Hindsight memory operations.

    All fields here can be:
    - Set as defaults via configure() or set_defaults()
    - Overridden per-call via hindsight_* kwargs (e.g., hindsight_bank_id="other")

    To add a new setting, just add a field here - it automatically works everywhere.

    Attributes:
        bank_id: Memory bank ID for operations. Use different bank_ids per user
            for multi-user support (e.g., f"user-{user_id}")
        session_id: Session ID for grouping conversations (maps to Hindsight's
            document_id). Use this to group related messages in a conversation.
            When set, Hindsight uses upsert behavior (same session = replace).
        document_id: DEPRECATED - Use session_id instead. Kept for backward
            compatibility. If both are set, session_id takes precedence.

        store_conversations: Whether to store conversations to Hindsight
        inject_memories: Whether to inject relevant memories into prompts
        injection_mode: How to inject memories (system_message or prepend_user)

        budget: Budget level for memory recall (low, mid, high)
        fact_types: Filter by fact types (world, experience, opinion, observation)
        max_memories: Maximum memories to inject (None = no limit)
        max_memory_tokens: Maximum tokens for memory context
        include_entities: Include entity observations in recall results
        trace: Enable trace info for recall debugging

        tags: Tags to apply when storing conversations. Use for visibility scoping
            (e.g., ["user:alice", "session:123"]). Stored memories will have these tags.
        recall_tags: Tags to filter by when recalling/reflecting memories. Only memories
            matching these tags (based on recall_tags_match mode) will be retrieved.
        recall_tags_match: How to match recall_tags. Options:
            - "any": OR matching, includes untagged memories (default)
            - "all": AND matching, includes untagged memories
            - "any_strict": OR matching, excludes untagged memories
            - "all_strict": AND matching, excludes untagged memories

        use_reflect: Use reflect API instead of recall for memory injection
        reflect_context: Context for reflect reasoning (shapes response, not retrieval)
        reflect_response_schema: JSON Schema for structured reflect output
        reflect_include_facts: Include facts used by reflect in debug info

        query: Custom query for memory recall (if not set, extracts from user message)
        verbose: Enable verbose logging
    """

    # Memory bank settings
    bank_id: Optional[str] = None
    session_id: Optional[str] = None  # Primary - maps to Hindsight's document_id
    document_id: Optional[str] = None  # Deprecated - use session_id instead

    # Feature toggles
    store_conversations: bool = True
    inject_memories: bool = True
    injection_mode: MemoryInjectionMode = MemoryInjectionMode.SYSTEM_MESSAGE

    # Recall settings
    budget: str = "mid"  # low, mid, high
    fact_types: Optional[List[str]] = None  # world, experience, opinion, observation
    max_memories: Optional[int] = None  # None = no limit
    max_memory_tokens: int = 4096
    include_entities: bool = True
    trace: bool = False

    # Tags for visibility scoping
    tags: Optional[List[str]] = None  # Tags applied when storing conversations
    recall_tags: Optional[List[str]] = None  # Tags to filter recall/reflect
    recall_tags_match: str = "any"  # any, all, any_strict, all_strict

    # Reflect settings (alternative to recall)
    use_reflect: bool = False
    reflect_context: Optional[str] = None
    reflect_response_schema: Optional[Dict[str, Any]] = None
    reflect_include_facts: bool = False

    # Query override (if not set, extracts from last user message)
    query: Optional[str] = None

    # Logging
    verbose: bool = False

    @property
    def effective_document_id(self) -> Optional[str]:
        """Get the effective document_id for Hindsight API calls.

        Returns session_id if set, otherwise falls back to document_id.
        This maps to Hindsight's document_id parameter for retain operations.
        """
        return self.session_id if self.session_id is not None else self.document_id


def _merge_call_settings(
    defaults: HindsightCallSettings, kwargs: Dict[str, Any]
) -> HindsightCallSettings:
    """Merge per-call kwargs (hindsight_*) with defaults.

    This automatically handles all fields in HindsightCallSettings.
    When a new field is added to the dataclass, it works here automatically.

    Args:
        defaults: The default settings
        kwargs: The kwargs passed to the call, may contain hindsight_* overrides

    Returns:
        Merged settings with per-call values overriding defaults
    """
    # Start with defaults as dict
    merged = asdict(defaults)

    # Get valid field names from the dataclass
    valid_fields = {f.name for f in fields(HindsightCallSettings)}

    # Override with hindsight_* kwargs
    for key, value in kwargs.items():
        if key.startswith("hindsight_"):
            setting_name = key[len("hindsight_") :]
            if setting_name in valid_fields:
                merged[setting_name] = value

    return HindsightCallSettings(**merged)


# Backward compatibility alias
HindsightDefaults = HindsightCallSettings


@dataclass
class HindsightConfig:
    """Connection-level configuration for Hindsight integration.

    These are settings that require a new client connection to change:
    - API URL and authentication
    - Session-level settings (excluded_models, sync_storage)

    Per-call settings (bank_id, budget, etc.) are in default_settings.

    Attributes:
        hindsight_api_url: URL of the Hindsight API server
        api_key: API key for Hindsight authentication
        excluded_models: List of model patterns to exclude from interception
        sync_storage: If True, storage runs synchronously and raises errors immediately
        default_settings: Default values for all per-call settings
    """

    hindsight_api_url: str = DEFAULT_HINDSIGHT_API_URL
    api_key: Optional[str] = None
    excluded_models: List[str] = field(default_factory=list)
    sync_storage: bool = False
    default_settings: HindsightCallSettings = field(
        default_factory=HindsightCallSettings
    )

    # Backward compatibility properties - delegate to default_settings
    @property
    def bank_id(self) -> Optional[str]:
        return self.default_settings.bank_id

    @property
    def store_conversations(self) -> bool:
        return self.default_settings.store_conversations

    @property
    def inject_memories(self) -> bool:
        return self.default_settings.inject_memories

    @property
    def injection_mode(self) -> MemoryInjectionMode:
        return self.default_settings.injection_mode

    @property
    def verbose(self) -> bool:
        return self.default_settings.verbose


# Global instances
_global_config: Optional[HindsightConfig] = None


def configure(
    hindsight_api_url: Optional[str] = None,
    api_key: Optional[str] = None,
    excluded_models: Optional[List[str]] = None,
    sync_storage: bool = False,
    # Bank setup (one-time)
    mission: Optional[str] = None,
    bank_name: Optional[str] = None,
    # Per-call defaults (all HindsightCallSettings fields)
    bank_id: Optional[str] = None,
    session_id: Optional[str] = None,
    document_id: Optional[str] = None,  # Deprecated - use session_id
    store_conversations: bool = True,
    inject_memories: bool = True,
    injection_mode: MemoryInjectionMode = MemoryInjectionMode.SYSTEM_MESSAGE,
    budget: str = "mid",
    fact_types: Optional[List[str]] = None,
    max_memories: Optional[int] = None,
    max_memory_tokens: int = 4096,
    include_entities: bool = True,
    trace: bool = False,
    tags: Optional[List[str]] = None,
    recall_tags: Optional[List[str]] = None,
    recall_tags_match: str = "any",
    use_reflect: bool = False,
    reflect_context: Optional[str] = None,
    reflect_response_schema: Optional[Dict[str, Any]] = None,
    reflect_include_facts: bool = False,
    verbose: bool = False,
) -> HindsightConfig:
    """Configure Hindsight integration settings.

    Sets up connection settings and default values for per-call settings.
    All per-call settings can be overridden using hindsight_* kwargs.

    Args:
        hindsight_api_url: URL of the Hindsight API server
            (default: https://api.hindsight.vectorize.io)
        api_key: API key for Hindsight authentication. If not provided,
            reads from HINDSIGHT_API_KEY environment variable.
        excluded_models: List of model patterns to exclude from interception
        sync_storage: If True, storage runs synchronously and raises errors immediately.
            If False (default), storage runs in background for better performance.
        mission: Instructions guiding what Hindsight should learn and remember
            (used for mental model generation).
        bank_name: Optional display name for the bank.

        # Per-call defaults (can be overridden with hindsight_* kwargs):
        bank_id: Memory bank ID (default: "default"). For multi-user support,
            use different bank_ids per user (e.g., f"user-{user_id}")
        session_id: Session ID for grouping conversations. Maps to Hindsight's
            document_id. When set, enables upsert behavior (same session = replace).
        document_id: DEPRECATED - Use session_id instead.
        store_conversations: Whether to store conversations (default: True)
        inject_memories: Whether to inject memories (default: True)
        injection_mode: How to inject memories (system_message or prepend_user)
        budget: Recall budget level - low/mid/high (default: "mid")
        fact_types: Filter by fact types (world/experience/opinion/observation)
        max_memories: Max memories to inject (None = no limit)
        max_memory_tokens: Max tokens for memory context (default: 4096)
        include_entities: Include entity observations in recall (default: True)
        trace: Enable trace info for debugging (default: False)
        tags: Tags to apply when storing conversations (e.g., ["user:alice"])
        recall_tags: Tags to filter by when recalling/reflecting memories
        recall_tags_match: Tag matching mode - any/all/any_strict/all_strict (default: "any")
        use_reflect: Use reflect API instead of recall (default: False)
        reflect_context: Context for reflect reasoning
        reflect_response_schema: JSON Schema for structured reflect output
        reflect_include_facts: Include facts in reflect debug info (default: False)
        verbose: Enable verbose logging (default: False)

    Returns:
        The configured HindsightConfig instance

    Example:
        >>> from hindsight_litellm import configure, enable
        >>>
        >>> # Minimal usage - just set HINDSIGHT_API_KEY env var
        >>> configure()
        >>> enable()
        >>>
        >>> # With per-call defaults
        >>> configure(
        ...     bank_id="user-123",
        ...     budget="high",
        ...     background="Remember user preferences.",
        ... )
        >>> enable()
        >>>
        >>> # Override per-call:
        >>> response = litellm.completion(
        ...     model="gpt-4",
        ...     messages=[...],
        ...     hindsight_bank_id="other-user",  # Override default
        ... )
    """
    global _global_config

    # Apply connection-level defaults
    resolved_api_url = hindsight_api_url or DEFAULT_HINDSIGHT_API_URL
    resolved_api_key = api_key or os.environ.get(HINDSIGHT_API_KEY_ENV)
    resolved_bank_id = bank_id or DEFAULT_BANK_ID

    # Build default settings
    default_settings = HindsightCallSettings(
        bank_id=resolved_bank_id,
        document_id=document_id,
        session_id=session_id,
        store_conversations=store_conversations,
        inject_memories=inject_memories,
        injection_mode=injection_mode,
        budget=budget,
        fact_types=fact_types,
        max_memories=max_memories,
        max_memory_tokens=max_memory_tokens,
        include_entities=include_entities,
        trace=trace,
        tags=tags,
        recall_tags=recall_tags,
        recall_tags_match=recall_tags_match,
        use_reflect=use_reflect,
        reflect_context=reflect_context,
        reflect_response_schema=reflect_response_schema,
        reflect_include_facts=reflect_include_facts,
        verbose=verbose,
    )

    _global_config = HindsightConfig(
        hindsight_api_url=resolved_api_url,
        api_key=resolved_api_key,
        excluded_models=excluded_models or [],
        sync_storage=sync_storage,
        default_settings=default_settings,
    )

    # If mission or bank_name is provided, create/update the bank
    if mission or bank_name:
        _create_or_update_bank(
            hindsight_api_url=resolved_api_url,
            bank_id=resolved_bank_id,
            name=bank_name,
            mission=mission,
            verbose=verbose,
            api_key=resolved_api_key,
        )

    return _global_config


def set_defaults(
    bank_id: Optional[str] = None,
    session_id: Optional[str] = None,
    document_id: Optional[str] = None,  # Deprecated - use session_id
    store_conversations: Optional[bool] = None,
    inject_memories: Optional[bool] = None,
    injection_mode: Optional[MemoryInjectionMode] = None,
    budget: Optional[str] = None,
    fact_types: Optional[List[str]] = None,
    max_memories: Optional[int] = None,
    max_memory_tokens: Optional[int] = None,
    include_entities: Optional[bool] = None,
    trace: Optional[bool] = None,
    tags: Optional[List[str]] = None,
    recall_tags: Optional[List[str]] = None,
    recall_tags_match: Optional[str] = None,
    use_reflect: Optional[bool] = None,
    reflect_context: Optional[str] = None,
    reflect_response_schema: Optional[Dict[str, Any]] = None,
    reflect_include_facts: Optional[bool] = None,
    verbose: Optional[bool] = None,
) -> HindsightCallSettings:
    """Update default values for per-call settings.

    Updates only the specified fields, preserving other defaults.
    Any of these can be overridden on individual calls using
    hindsight_* kwargs (e.g., hindsight_bank_id="other-bank").

    Args:
        bank_id: Memory bank ID for memory operations
        session_id: Session ID for grouping conversations. Maps to Hindsight's
            document_id. When set, enables upsert behavior (same session = replace).
        document_id: DEPRECATED - Use session_id instead.
        store_conversations: Whether to store conversations
        inject_memories: Whether to inject memories
        injection_mode: How to inject memories (system_message or prepend_user)
        budget: Budget level for memory recall (low, mid, high)
        fact_types: Fact types to filter (world, experience, opinion, observation)
        max_memories: Max number of memories to inject
        max_memory_tokens: Max tokens for memory context
        include_entities: Include entity observations in recall
        trace: Enable trace info for debugging
        tags: Tags to apply when storing conversations
        recall_tags: Tags to filter by when recalling/reflecting memories
        recall_tags_match: Tag matching mode - any/all/any_strict/all_strict
        use_reflect: Use reflect API instead of recall
        reflect_context: Context for reflect reasoning
        reflect_response_schema: JSON Schema for structured reflect output
        reflect_include_facts: Include facts in reflect debug info
        verbose: Enable verbose logging

    Returns:
        The updated HindsightCallSettings instance

    Example:
        >>> from hindsight_litellm import configure, set_defaults
        >>> configure()
        >>> set_defaults(bank_id="my-agent", budget="high")
    """
    global _global_config

    # Ensure configure() was called
    if _global_config is None:
        # Auto-configure with defaults if not configured
        configure()

    # Get current defaults
    current = _global_config.default_settings

    # Update only provided values using dataclass fields
    updated_settings = HindsightCallSettings(
        bank_id=bank_id if bank_id is not None else current.bank_id,
        document_id=document_id if document_id is not None else current.document_id,
        session_id=session_id if session_id is not None else current.session_id,
        store_conversations=store_conversations
        if store_conversations is not None
        else current.store_conversations,
        inject_memories=inject_memories
        if inject_memories is not None
        else current.inject_memories,
        injection_mode=injection_mode
        if injection_mode is not None
        else current.injection_mode,
        budget=budget if budget is not None else current.budget,
        fact_types=fact_types if fact_types is not None else current.fact_types,
        max_memories=max_memories if max_memories is not None else current.max_memories,
        max_memory_tokens=max_memory_tokens
        if max_memory_tokens is not None
        else current.max_memory_tokens,
        include_entities=include_entities
        if include_entities is not None
        else current.include_entities,
        trace=trace if trace is not None else current.trace,
        tags=tags if tags is not None else current.tags,
        recall_tags=recall_tags if recall_tags is not None else current.recall_tags,
        recall_tags_match=recall_tags_match
        if recall_tags_match is not None
        else current.recall_tags_match,
        use_reflect=use_reflect if use_reflect is not None else current.use_reflect,
        reflect_context=reflect_context
        if reflect_context is not None
        else current.reflect_context,
        reflect_response_schema=reflect_response_schema
        if reflect_response_schema is not None
        else current.reflect_response_schema,
        reflect_include_facts=reflect_include_facts
        if reflect_include_facts is not None
        else current.reflect_include_facts,
        verbose=verbose if verbose is not None else current.verbose,
    )

    # Update the config's default settings
    _global_config = HindsightConfig(
        hindsight_api_url=_global_config.hindsight_api_url,
        api_key=_global_config.api_key,
        excluded_models=_global_config.excluded_models,
        sync_storage=_global_config.sync_storage,
        default_settings=updated_settings,
    )

    return updated_settings


def _create_or_update_bank(
    hindsight_api_url: str,
    bank_id: str,
    name: Optional[str] = None,
    mission: Optional[str] = None,
    verbose: bool = False,
    api_key: Optional[str] = None,
) -> None:
    """Create or update a memory bank with the given configuration.

    Args:
        hindsight_api_url: URL of the Hindsight API server
        bank_id: The bank ID to create/update
        name: Optional display name for the bank
        mission: Instructions guiding what Hindsight should learn and remember
        verbose: Enable verbose logging
    """
    try:
        from hindsight_client import Hindsight

        client = Hindsight(base_url=hindsight_api_url, api_key=api_key)
        client.create_bank(
            bank_id=bank_id,
            name=name,
            mission=mission,
        )
        if verbose:
            import logging

            logging.getLogger("hindsight_litellm").info(
                f"Created/updated bank '{bank_id}' with mission"
            )
    except ImportError:
        if verbose:
            import logging

            logging.getLogger("hindsight_litellm").warning(
                "hindsight_client not installed. Cannot create bank. "
                "Install with: pip install hindsight-client"
            )
    except Exception as e:
        if verbose:
            import logging

            logging.getLogger("hindsight_litellm").warning(
                f"Failed to create/update bank: {e}"
            )


def get_config() -> Optional[HindsightConfig]:
    """Get the current global configuration.

    Returns:
        The current HindsightConfig instance, or None if not configured
    """
    return _global_config


def get_defaults() -> Optional[HindsightCallSettings]:
    """Get the current global defaults for per-call settings.

    Returns:
        The current HindsightCallSettings instance, or None if not configured
    """
    if _global_config is not None:
        return _global_config.default_settings
    return None


def is_configured() -> bool:
    """Check if Hindsight has been configured with a valid bank_id.

    Returns:
        True if configure() has been called and a bank_id is set
    """
    if _global_config is not None and _global_config.bank_id:
        return True
    return False


def reset_config() -> None:
    """Reset all global configuration to None."""
    global _global_config
    _global_config = None


