"""Command helpers for JsonRpcClient.

Author: Vasiliy Zdanovskiy
email: vasilyvz@gmail.com
"""

from __future__ import annotations

from typing import Any, Dict, Optional, TYPE_CHECKING

from mcp_proxy_adapter.client.jsonrpc_client.transport import JsonRpcTransport

if TYPE_CHECKING:
    from mcp_proxy_adapter.client.jsonrpc_client.schema_generator import MethodInfo


class CommandApiMixin(JsonRpcTransport):
    """Mixin providing standard JSON-RPC command helpers."""

    async def echo(
        self, message: str = "Hello, World!", timestamp: Optional[str] = None
    ) -> Dict[str, Any]:
        params: Dict[str, Any] = {"message": message}
        if timestamp:
            params["timestamp"] = timestamp
        response = await self.jsonrpc_call("echo", params)
        return self._extract_result(response)

    async def help(
        self, command_name: Optional[str] = None
    ) -> Dict[str, Any]:
        params: Dict[str, Any] = {}
        if command_name:
            params["command"] = command_name
        response = await self.jsonrpc_call("help", params)
        return self._extract_result(response)

    async def get_config(self) -> Dict[str, Any]:
        response = await self.jsonrpc_call("config", {})
        return self._extract_result(response)

    async def long_task(self, seconds: int) -> Dict[str, Any]:
        response = await self.jsonrpc_call("long_task", {"seconds": seconds})
        return self._extract_result(response)

    async def job_status(self, job_id: str) -> Dict[str, Any]:
        response = await self.jsonrpc_call("job_status", {"job_id": job_id})
        return self._extract_result(response)

    async def execute_command(
        self,
        command: str,
        params: Optional[Dict[str, Any]] = None,
        use_cmd_endpoint: bool = False,
    ) -> Dict[str, Any]:
        """
        Execute command using either /cmd endpoint or JSON-RPC.
        
        The /cmd endpoint supports the CommandRequest schema with oneOf discriminator
        and detailed parameter validation. Use use_cmd_endpoint=True to use this endpoint.
        
        Args:
            command: Command name to execute
            params: Optional command parameters
            use_cmd_endpoint: If True, use /cmd endpoint; otherwise use JSON-RPC
            
        Returns:
            Command execution result
        """
        if use_cmd_endpoint:
            return await self.cmd_call(command, params)
        else:
            response = await self.jsonrpc_call(command, params or {})
            return self._extract_result(response)
    
    async def execute(
        self,
        method_name: str,
        params: Optional[Dict[str, Any]] = None,
    ) -> Dict[str, Any]:
        """
        Execute method with schema-based validation and default values.
        
        This method uses the SchemaRequestGenerator to:
        - Validate required parameters
        - Set default values for missing optional parameters
        - Validate parameter types
        - Execute the command and return deserialized Python object
        
        Args:
            method_name: Name of the method to execute
            params: Method parameters (optional)
            
        Returns:
            Deserialized command execution result (Python dict)
            
        Raises:
            MethodNotFoundError: If method is not found in schema
            RequiredParameterMissingError: If required parameter is missing
            InvalidParameterTypeError: If parameter type is invalid
            InvalidParameterValueError: If parameter value is invalid
            RuntimeError: If command execution fails
        """
        from mcp_proxy_adapter.client.jsonrpc_client.schema_generator import SchemaRequestGenerator
        from mcp_proxy_adapter.client.jsonrpc_client.exceptions import (
            MethodNotFoundError,
            RequiredParameterMissingError,
            InvalidParameterTypeError,
            InvalidParameterValueError,
        )
        
        # Get schema generator
        generator = await self.get_schema_generator_async()
        
        # Validate and prepare parameters
        try:
            prepared_params = generator.validate_and_prepare_params(method_name, params)
        except (MethodNotFoundError, RequiredParameterMissingError, 
                InvalidParameterTypeError, InvalidParameterValueError) as e:
            raise  # Re-raise schema validation errors
        
        # Execute command
        result = await self.cmd_call(method_name, prepared_params)
        
        # Result is already a Python dict (deserialized JSON)
        return result
    
    async def get_methods(self) -> Dict[str, "MethodInfo"]:
        """
        Get all available methods with their descriptions.
        
        Returns:
            Dictionary mapping method names to MethodInfo objects
        """
        generator = await self.get_schema_generator_async()
        return generator.get_methods()
    
    async def get_method_description(self, method_name: str) -> str:
        """
        Get detailed description of a method.
        
        Args:
            method_name: Name of the method
            
        Returns:
            Detailed description string including:
            - Method name and description
            - Return type and description
            - Parameter details (type, description, default, required)
        """
        generator = await self.get_schema_generator_async()
        return generator.get_method_description(method_name)
    
    async def schema_example(self) -> str:
        """
        Get schema example with detailed descriptions.
        
        Returns:
            JSON string containing:
            - Full OpenAPI schema
            - Standard description (OpenAPI 3.0.2)
            - Detailed field descriptions
            - All methods with parameter details
        """
        generator = await self.get_schema_generator_async()
        return generator.schema_example()
    
    async def get_commands_list(self) -> Dict[str, Any]:
        """
        Get list of all available commands.
        
        This method calls the GET /commands endpoint to retrieve
        the list of all registered commands on the server.
        
        Returns:
            Dictionary containing list of commands and their metadata
        """
        client = await self._get_client()
        response = await client.get(f"{self.base_url}/commands", headers=self.headers)
        response.raise_for_status()
        from typing import cast
        return cast(Dict[str, Any], response.json())
    
    async def get_heartbeat(self) -> Dict[str, Any]:
        """
        Get server heartbeat information.
        
        This method calls the GET /heartbeat endpoint to retrieve
        server heartbeat status and metadata.
        
        Returns:
            Dictionary containing heartbeat status, server name, URL, and timestamp
        """
        client = await self._get_client()
        response = await client.get(f"{self.base_url}/heartbeat", headers=self.headers)
        response.raise_for_status()
        from typing import cast
        return cast(Dict[str, Any], response.json())
    
    async def list_commands(self) -> Dict[str, Any]:
        """
        List all available commands via JSON-RPC.
        
        This is an alias for calling the 'list' command via JSON-RPC.
        Equivalent to: execute_command("list", {})
        
        Returns:
            Dictionary containing list of commands
        """
        return await self.execute_command("list", {})
    
    async def load_command(self, module_path: str, **kwargs) -> Dict[str, Any]:
        """
        Load a command module.
        
        Args:
            module_path: Path to the command module to load
            **kwargs: Additional parameters for the load command
            
        Returns:
            Result of the load operation
        """
        params = {"module_path": module_path, **kwargs}
        return await self.execute_command("load", params)
    
    async def unload_command(self, command_name: str, **kwargs) -> Dict[str, Any]:
        """
        Unload a command.
        
        Args:
            command_name: Name of the command to unload
            **kwargs: Additional parameters for the unload command
            
        Returns:
            Result of the unload operation
        """
        params = {"command": command_name, **kwargs}
        return await self.execute_command("unload", params)
    
    async def reload_command(self, command_name: str, **kwargs) -> Dict[str, Any]:
        """
        Reload a command.
        
        Args:
            command_name: Name of the command to reload
            **kwargs: Additional parameters for the reload command
            
        Returns:
            Result of the reload operation
        """
        params = {"command": command_name, **kwargs}
        return await self.execute_command("reload", params)
    
    async def get_tool(self, tool_name: str) -> Dict[str, Any]:
        """
        Get tool description.
        
        Args:
            tool_name: Name of the tool
            
        Returns:
            Tool description and metadata
        """
        client = await self._get_client()
        response = await client.get(
            f"{self.base_url}/tools/{tool_name}",
            headers=self.headers,
        )
        response.raise_for_status()
        from typing import cast
        return cast(Dict[str, Any], response.json())
    
    async def execute_tool(
        self,
        tool_name: str,
        payload: Optional[Dict[str, Any]] = None,
    ) -> Dict[str, Any]:
        """
        Execute a tool.
        
        Args:
            tool_name: Name of the tool to execute
            payload: Tool execution parameters
            
        Returns:
            Tool execution result
        """
        client = await self._get_client()
        response = await client.post(
            f"{self.base_url}/tools/{tool_name}",
            json=payload or {},
            headers=self.headers,
        )
        response.raise_for_status()
        from typing import cast
        return cast(Dict[str, Any], response.json())
    
    # Queue-related command helpers
    
    async def get_command_status(self, job_id: str) -> Dict[str, Any]:
        """
        Get status of a command execution in queue.
        
        This is a convenience method that calls queue_get_job_status.
        Use this when you have a job_id from a queued command execution.
        
        Args:
            job_id: Job ID returned from execute_command when command uses queue
            
        Returns:
            Dictionary containing job status, progress, description, and result
        """
        return await self.queue_get_job_status(job_id)
    
    async def cancel_command(self, job_id: str) -> Dict[str, Any]:
        """
        Cancel a command execution in queue.
        
        This method stops and deletes the job from the queue.
        Equivalent to: queue_stop_job() + queue_delete_job()
        
        Args:
            job_id: Job ID returned from execute_command when command uses queue
            
        Returns:
            Result of the cancellation operation
        """
        # Try to stop first, then delete
        try:
            await self.queue_stop_job(job_id)
        except Exception:
            pass  # Job might already be stopped or completed
        return await self.queue_delete_job(job_id)
    
    async def list_queued_commands(
        self,
        status: Optional[str] = None,
        limit: Optional[int] = None,
    ) -> Dict[str, Any]:
        """
        List all commands currently in the queue.
        
        This method filters jobs to show only command executions (job_type="command_execution").
        
        Args:
            status: Filter by status (pending, running, completed, failed, stopped)
            limit: Maximum number of jobs to return (default: 100)
            
        Returns:
            Dictionary containing list of queued commands with their status
        """
        # Use queue_list_jobs with job_type filter for command executions
        result = await self.queue_list_jobs(status=status, job_type="command_execution")
        
        # Apply limit if specified
        if limit and "data" in result:
            jobs = result.get("data", {}).get("jobs", [])
            if len(jobs) > limit:
                result["data"]["jobs"] = jobs[:limit]
                result["data"]["total_count"] = limit
        
        return result
    
    async def list_all_queue_jobs(
        self,
        status: Optional[str] = None,
        job_type: Optional[str] = None,
        limit: Optional[int] = None,
    ) -> Dict[str, Any]:
        """
        List all jobs in the queue (commands and other job types).
        
        Args:
            status: Filter by status (pending, running, completed, failed, stopped)
            job_type: Filter by job type (command_execution, data_processing, api_call)
            limit: Maximum number of jobs to return (default: 100)
            
        Returns:
            Dictionary containing list of all jobs with their status
        """
        result = await self.queue_list_jobs(status=status, job_type=job_type)
        
        # Apply limit if specified
        if limit and "data" in result:
            jobs = result.get("data", {}).get("jobs", [])
            if len(jobs) > limit:
                result["data"]["jobs"] = jobs[:limit]
                result["data"]["total_count"] = limit
        
        return result