import sys
import os

sys.path.insert(
    0, os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
)

from common.utils.asyncio_macos_fix import ensure_asyncio_compatibility
from .patch_adk import patch_adk
from solace_ai_connector.common.log import log

ensure_asyncio_compatibility()
patch_adk()

from typing import Any, Dict, List, Optional, Union, Literal
from pydantic import Field, ValidationError, model_validator
from solace_ai_connector.flow.app import App

from ...common.a2a import (
    get_agent_request_topic,
    get_discovery_topic,
    get_agent_response_subscription_topic,
    get_agent_status_subscription_topic,
    get_sam_events_subscription_topic,
)
from ...common.constants import DEFAULT_COMMUNICATION_TIMEOUT
from ...agent.sac.component import SamAgentComponent
from ...agent.utils.artifact_helpers import DEFAULT_SCHEMA_MAX_KEYS
from ...common.utils.pydantic_utils import SamConfigBase
from ..tools.tool_config_types import AnyToolConfig

info = {
    "class_name": "SamAgentApp",
    "description": "Custom App class for SAM Agent Host with namespace prefixing and automatic subscription generation.",
}


# --- Pydantic Models for Configuration Validation ---


class AgentCardConfig(SamConfigBase):
    """Configuration for the agent's self-description card."""

    description: str = Field(
        default="", description="Concise agent description for discovery."
    )
    defaultInputModes: List[str] = Field(
        default=["text"], description="Supported input content types."
    )
    defaultOutputModes: List[str] = Field(
        default=["text"], description="Supported output content types."
    )
    skills: List[Dict[str, Any]] = Field(
        default_factory=list,
        description="List of advertised agent skills (A2A AgentSkill structure).",
    )
    documentationUrl: Optional[str] = Field(
        default=None, description="Optional URL for agent documentation."
    )
    provider: Optional[Dict[str, Any]] = Field(
        default=None, description="Optional provider information (A2A AgentProvider structure)."
    )


class AgentCardPublishingConfig(SamConfigBase):
    """Configuration for publishing the agent card."""

    interval_seconds: int = Field(
        ..., description="Publish interval (seconds). <= 0 disables periodic publish."
    )


class AgentDiscoveryConfig(SamConfigBase):
    """Configuration for discovering other agents."""

    enabled: bool = Field(default=True, description="Enable discovery and instruction injection.")


class InterAgentCommunicationConfig(SamConfigBase):
    """Configuration for interacting with peer agents."""

    allow_list: List[str] = Field(
        default=["*"], description="Agent name patterns to allow delegation to."
    )
    deny_list: List[str] = Field(
        default_factory=list, description="Agent name patterns to deny delegation to."
    )
    request_timeout_seconds: int = Field(
        default=DEFAULT_COMMUNICATION_TIMEOUT, description="Timeout for peer requests (seconds)."
    )


class AgentInitCleanupConfig(SamConfigBase):
    """Configuration for custom agent initialization or cleanup functions."""

    module: str = Field(
        ...,
        description="Python module path for the function (e.g., 'my_plugin.initializers').",
    )
    name: str = Field(..., description="Name of the function within the module.")
    base_path: Optional[str] = Field(
        default=None, description="Optional base path for module resolution if not in PYTHONPATH."
    )
    config: Dict[str, Any] = Field(
        default_factory=dict, description="Configuration dictionary for the function."
    )


class DataToolsConfig(SamConfigBase):
    """Configuration for built-in data analysis tools."""

    sqlite_memory_threshold_mb: int = Field(
        default=100,
        description="Size threshold (MB) for using in-memory vs. temp file SQLite DB for CSV input.",
    )
    max_result_preview_rows: int = Field(
        default=50, description="Max rows to return in preview for SQL/JQ results."
    )
    max_result_preview_bytes: int = Field(
        default=4096,
        description="Max bytes to return in preview for SQL/JQ results (if row limit not hit first).",
    )


class ExtractContentConfig(SamConfigBase):
    """Configuration for the LLM-powered artifact extraction tool."""

    supported_binary_mime_types: List[str] = Field(
        default_factory=list,
        description="List of binary MIME type patterns (e.g., 'image/png', 'image/*', 'video/mp4') that the tool should attempt to process using its internal LLM.",
    )
    model: Optional[Union[str, Dict[str, Any]]] = Field(
        default=None,
        description="Specifies the LLM for extraction. String (ADK LLMRegistry name) or dict (LiteLlm config). Defaults to agent's LLM.",
    )


class McpProcessingConfig(SamConfigBase):
    """Configuration for intelligent processing of MCP tool responses."""

    enable_intelligent_processing: bool = Field(
        default=True,
        description="Enable intelligent content-aware processing of MCP responses. When disabled, falls back to raw JSON saving.",
    )
    enable_text_format_detection: bool = Field(
        default=True,
        description="Enable detection and parsing of structured text formats (CSV, JSON, YAML) within text content.",
    )
    enable_content_parsing: bool = Field(
        default=True,
        description="Enable parsing and validation of detected content formats for enhanced metadata.",
    )
    fallback_to_raw_on_error: bool = Field(
        default=True, description="Fall back to raw JSON saving if intelligent processing fails."
    )
    save_raw_alongside_intelligent: bool = Field(
        default=False,
        description="Save both intelligent artifacts and raw JSON response for debugging/comparison.",
    )
    max_content_items: int = Field(
        default=50,
        description="Maximum number of content items to process from a single MCP response.",
    )
    max_single_item_size_mb: int = Field(
        default=100,
        description="Maximum size in MB for a single content item before skipping intelligent processing.",
    )


class ArtifactServiceConfig(SamConfigBase):
    """Configuration for the ADK Artifact Service."""

    type: str = Field(..., description="Service type (e.g., 'memory', 'gcs', 'filesystem').")
    base_path: Optional[str] = Field(
        default=None, description="Base directory path (required for type 'filesystem')."
    )
    bucket_name: Optional[str] = Field(
        default=None, description="GCS bucket name (required for type 'gcs')."
    )
    artifact_scope: Literal["namespace", "app", "custom"] = Field(
        default="namespace", description="Process-wide scope for all artifact services."
    )
    artifact_scope_value: Optional[str] = Field(
        default=None,
        description="Custom identifier for artifact scope (required if artifact_scope is 'custom').",
    )

    @model_validator(mode="after")
    def check_artifact_scope(self) -> "ArtifactServiceConfig":
        if self.artifact_scope == "custom" and not self.artifact_scope_value:
            raise ValueError(
                "'artifact_scope_value' is required when 'artifact_scope' is 'custom'."
            )
        if self.artifact_scope != "custom" and self.artifact_scope_value:
            log.warning(
                "Configuration Warning: 'artifact_scope_value' is ignored when 'artifact_scope' is not 'custom'."
            )
        return self


class SessionServiceConfig(SamConfigBase):
    """Configuration for the ADK Session Service."""

    type: str = Field(..., description="Service type (e.g., 'memory', 'vertex_rag').")
    default_behavior: Literal["PERSISTENT", "RUN_BASED"] = Field(
        default="PERSISTENT", description="Default behavior for session service."
    )


class SamAgentAppConfig(SamConfigBase):
    """Pydantic model for the complete agent application configuration."""

    namespace: str = Field(
        ..., description="Absolute topic prefix for A2A communication (e.g., 'myorg/dev')."
    )
    agent_name: str = Field(..., description="Unique name for this ADK agent instance.")
    model: Union[str, Dict[str, Any]] = Field(
        ..., description="ADK model name (string) or BaseLlm config dict."
    )
    instruction: Any = Field(
        default="",
        description="User-provided instructions for the ADK agent (string or invoke block).",
    )
    global_instruction: Any = Field(
        default="",
        description="User-provided global instructions for the agent tree (string or invoke block).",
    )
    tools: List[AnyToolConfig] = Field(
        default_factory=list,
        description="List of tool configurations (python, mcp, built-in). Each tool can have 'required_scopes'.",
    )
    supports_streaming: bool = Field(
        default=False, description="Whether this host supports A2A streaming (tasks/sendSubscribe)."
    )
    planner: Optional[Dict[str, Any]] = Field(
        default=None, description="Optional configuration for an ADK planner."
    )
    code_executor: Optional[Dict[str, Any]] = Field(
        default=None, description="Optional configuration for an ADK code executor."
    )
    inject_current_time: bool = Field(
        default=True, description="Whether to inject the current time into the agent's instruction."
    )
    session_service: SessionServiceConfig = Field(
        default_factory=lambda: SessionServiceConfig(type="memory"),
        description="Configuration for ADK Session Service.",
    )
    artifact_service: ArtifactServiceConfig = Field(
        default_factory=lambda: ArtifactServiceConfig(type="memory"),
        description="Configuration for ADK Artifact Service.",
    )
    memory_service: Dict[str, Any] = Field(
        default={"type": "memory"},
        description="Configuration for ADK Memory Service (defaults to memory).",
    )
    tool_output_save_threshold_bytes: int = Field(
        default=2048,
        description="If any tool's processed output exceeds this size (bytes), its full content is saved as a new ADK artifact.",
    )
    tool_output_llm_return_max_bytes: int = Field(
        default=4096,
        description="Maximum size (bytes) of any tool's output content returned directly to the LLM.",
    )
    mcp_tool_response_save_threshold_bytes: int = Field(
        default=2048,
        description="Threshold in bytes above which MCP tool responses are saved as artifacts.",
    )
    mcp_tool_llm_return_max_bytes: int = Field(
        default=4096,
        description="Maximum size in bytes of MCP tool response content returned directly to the LLM.",
    )
    artifact_handling_mode: Literal["ignore", "embed", "reference"] = Field(
        default="ignore", description="How to represent created artifacts in A2A messages."
    )
    schema_max_keys: int = Field(
        default=DEFAULT_SCHEMA_MAX_KEYS,
        ge=0,
        description="Maximum number of dictionary keys to inspect during schema inference.",
    )
    enable_embed_resolution: bool = Field(
        default=True,
        description="Enable early-stage processing of dynamic embeds and inject related instructions.",
    )
    enable_auto_continuation: bool = Field(
        default=True,
        description="If true, automatically attempts to continue LLM generation if it is interrupted by a token limit.",
    )
    stream_batching_threshold_bytes: int = Field(
        default=0,
        description="Minimum size in bytes for accumulated text from LLM stream before sending a status update.",
    )
    max_message_size_bytes: int = Field(
        default=10_000_000,
        description="Maximum allowed message size in bytes before rejecting publication.",
    )
    enable_artifact_content_instruction: bool = Field(
        default=True,
        description="Inject instructions about the 'artifact_content' embed type.",
    )
    agent_card: AgentCardConfig = Field(
        ..., description="Static definition of this agent's capabilities for discovery."
    )
    agent_card_publishing: AgentCardPublishingConfig = Field(
        ..., description="Settings for publishing the agent card."
    )
    agent_discovery: AgentDiscoveryConfig = Field(
        default_factory=AgentDiscoveryConfig,
        description="Settings for discovering other agents and injecting related instructions.",
    )
    inter_agent_communication: InterAgentCommunicationConfig = Field(
        default_factory=InterAgentCommunicationConfig,
        description="Configuration for interacting with peer agents.",
    )
    inject_system_purpose: bool = Field(
        default=False,
        description="If true, injects the system_purpose received from the gateway into the agent's prompt.",
    )
    inject_response_format: bool = Field(
        default=False,
        description="If true, injects the response_format received from the gateway into the agent's prompt.",
    )
    inject_user_profile: bool = Field(
        default=False,
        description="If true, injects the user_profile received from the gateway into the agent's prompt.",
    )
    agent_init_function: Optional[AgentInitCleanupConfig] = Field(
        default=None, description="Configuration for the agent's custom initialization function."
    )
    agent_cleanup_function: Optional[AgentInitCleanupConfig] = Field(
        default=None, description="Configuration for the agent's custom cleanup function."
    )
    text_artifact_content_max_length: int = Field(
        default=1000,
        ge=100,
        le=100000,
        description="Maximum character length for text-based artifact content.",
    )
    max_llm_calls_per_task: int = Field(
        default=20, description="Maximum number of LLM calls allowed for a single A2A task."
    )
    data_tools_config: DataToolsConfig = Field(
        default_factory=DataToolsConfig,
        description="Runtime configuration parameters for built-in data analysis tools.",
    )
    extract_content_from_artifact_config: ExtractContentConfig = Field(
        default_factory=ExtractContentConfig,
        description="Configuration for the LLM-powered artifact extraction tool.",
    )
    mcp_intelligent_processing: McpProcessingConfig = Field(
        default_factory=McpProcessingConfig,
        description="Configuration for intelligent processing of MCP tool responses.",
    )


class SamAgentApp(App):
    """
    Custom App class for SAM Agent Host that automatically generates
    the required Solace subscriptions based on namespace and agent name,
    and programmatically defines the single SamAgentComponent instance.
    It also defines the expected configuration structure via `app_schema`.
    """

    # The app_schema dictionary is now redundant for validation but can be kept for documentation
    # or for frameworks that might inspect it. For now, we will remove it.
    app_schema = {}

    def __init__(self, app_info: Dict[str, Any], **kwargs):
        log.debug("Initializing A2A_ADK_App...")

        app_config_dict = app_info.get("app_config", {})

        try:
            # Validate the raw dict, cleaning None values to allow defaults to apply
            app_config = SamAgentAppConfig.model_validate_and_clean(app_config_dict)
            # Overwrite the raw dict with the validated object for downstream use
            app_info["app_config"] = app_config
        except ValidationError as e:
            log.error("Agent configuration validation failed:\n%s", e)
            raise ValueError(f"Invalid agent configuration: {e}") from e

        # The rest of the method can now safely use .get() on the app_config object,
        # ensuring full backward compatibility.
        namespace = app_config.get("namespace")
        agent_name = app_config.get("agent_name")
        broker_request_response = app_info.get("broker_request_response")

        log.info(
            "Configuring A2A_ADK_App for Agent: '%s' in Namespace: '%s'",
            agent_name,
            namespace,
        )

        required_topics = [
            get_agent_request_topic(namespace, agent_name),
            get_discovery_topic(namespace),
            get_agent_response_subscription_topic(namespace, agent_name),
            get_agent_status_subscription_topic(namespace, agent_name),
            get_sam_events_subscription_topic(namespace, "session"),
        ]
        generated_subs = [{"topic": topic} for topic in required_topics]
        log.info(
            "Automatically generated subscriptions for Agent '%s': %s",
            agent_name,
            generated_subs,
        )

        component_definition = {
            "name": f"{agent_name}_host",
            "component_class": SamAgentComponent,
            "component_config": {},
            "subscriptions": generated_subs,
        }
        if broker_request_response:
            component_definition["broker_request_response"] = broker_request_response

        app_info["components"] = [component_definition]
        log.debug("Replaced 'components' in app_info with programmatic definition.")

        broker_config = app_info.setdefault("broker", {})

        broker_config["input_enabled"] = True
        broker_config["output_enabled"] = True
        log.debug("Injected broker.input_enabled=True and broker.output_enabled=True")

        generated_queue_name = f"{namespace.strip('/')}/q/a2a/{agent_name}"
        broker_config["queue_name"] = generated_queue_name
        log.debug("Injected generated broker.queue_name: %s", generated_queue_name)

        broker_config["temporary_queue"] = True
        log.debug("Set broker_config.temporary_queue = True")

        super().__init__(app_info, **kwargs)
        log.debug("A2A_ADK_App initialization complete.")
