"""
HTTP step for making web requests in workflows.
"""
 
import time
from typing import Any, Dict, Optional
from pydantic import BaseModel, Field
import requests
import logging
from typing_extensions import Self

from ...._component_config import Component

from ._step import BaseStep, BaseStepConfig
from ..core._models import StepMetadata, Context

logger = logging.getLogger(__name__)


class HttpRequestInput(BaseModel):
    """Input model for HTTP requests."""
    url: str = Field(description="URL to fetch")
    method: str = Field(default="GET", description="HTTP method")
    headers: Optional[Dict[str, str]] = Field(default=None, description="HTTP headers")
    data: Optional[Dict[str, Any]] = Field(default=None, description="Request data for POST/PUT")
    timeout: Optional[int] = Field(default=30, description="Request timeout in seconds")
    verify_ssl: bool = Field(default=True, description="Whether to verify SSL certificates")


class HttpResponseOutput(BaseModel):
    """Output model for HTTP responses."""
    status_code: int = Field(description="HTTP status code")
    content: str = Field(description="Response content as string")
    headers: Dict[str, str] = Field(description="Response headers")
    url: str = Field(description="Final URL after redirects")
    encoding: Optional[str] = Field(default=None, description="Response encoding")
    elapsed_time: float = Field(description="Request duration in seconds")


class HttpStepConfig(BaseStepConfig):
    """Configuration for HttpStep serialization."""
    pass


class HttpStep(Component[HttpStepConfig], BaseStep[HttpRequestInput, HttpResponseOutput]):
    """A step that makes HTTP requests and returns the response."""
    
    component_config_schema = HttpStepConfig
    component_type = "step"
    component_provider_override = "picoagents.orchestration.workflow.HttpStep"
    
    def __init__(
        self,
        step_id: str,
        metadata: StepMetadata,
        input_type: type[HttpRequestInput] = HttpRequestInput,
        output_type: type[HttpResponseOutput] = HttpResponseOutput
    ):
        """Initialize the HTTP step.
        
        Args:
            step_id: Unique identifier for this step
            metadata: Step metadata
            input_type: Input validation model
            output_type: Output validation model
        """
        super().__init__(step_id, metadata, input_type, output_type)
    
    async def execute(self, input_data: HttpRequestInput, context: Context) -> HttpResponseOutput:
        """Execute the HTTP request.
        
        Args:
            input_data: Validated input data
            context: Additional context including workflow state
            
        Returns:
            HTTP response data
            
        Raises:
            Exception: If HTTP request fails
        """
        start_time = time.time()
        
        try:
            # Prepare request parameters
            request_kwargs = {
                'timeout': input_data.timeout,
                'verify': input_data.verify_ssl
            }
            
            if input_data.headers:
                request_kwargs['headers'] = input_data.headers
            
            if input_data.data and input_data.method.upper() in ['POST', 'PUT', 'PATCH']:
                request_kwargs['json'] = input_data.data
            
            # Make the request
            logger.info(f"Making {input_data.method} request to {input_data.url}")
            response = requests.request(
                method=input_data.method.upper(),
                url=input_data.url,
                **request_kwargs
            )
            
            elapsed_time = time.time() - start_time
            
            # Store request info in context for debugging
            context.set(f'{self.step_id}_request_info', {
                'url': input_data.url,
                'method': input_data.method,
                'status_code': response.status_code,
                'elapsed_time': elapsed_time,
                'content_length': len(response.content)
            })
            
            # Handle HTTP errors
            response.raise_for_status()
            
            # Determine encoding
            encoding = response.encoding
            if not encoding:
                encoding = response.apparent_encoding
            
            # Decode content
            try:
                content = response.text
            except UnicodeDecodeError:
                # Fallback to raw content if text decoding fails
                content = response.content.decode('utf-8', errors='replace')
            
            return HttpResponseOutput(
                status_code=response.status_code,
                content=content,
                headers=dict(response.headers),
                url=response.url,
                encoding=encoding,
                elapsed_time=elapsed_time
            )
            
        except requests.exceptions.Timeout:
            elapsed_time = time.time() - start_time
            error_msg = f"Request to {input_data.url} timed out after {input_data.timeout} seconds"
            logger.error(error_msg)
            context.set(f'{self.step_id}_error', {
                'type': 'timeout',
                'message': error_msg,
                'elapsed_time': elapsed_time
            })
            raise Exception(error_msg)
            
        except requests.exceptions.SSLError as e:
            elapsed_time = time.time() - start_time
            error_msg = f"SSL error for {input_data.url}: {str(e)}"
            logger.error(error_msg)
            context.set(f'{self.step_id}_error', {
                'type': 'ssl_error',
                'message': error_msg,
                'elapsed_time': elapsed_time
            })
            raise Exception(error_msg)
            
        except requests.exceptions.RequestException as e:
            elapsed_time = time.time() - start_time
            error_msg = f"Request failed for {input_data.url}: {str(e)}"
            logger.error(error_msg)
            context.set(f'{self.step_id}_error', {
                'type': 'request_error',
                'message': error_msg,
                'elapsed_time': elapsed_time
            })
            raise Exception(error_msg)

    def _to_config(self) -> HttpStepConfig: # type: ignore 
        """Convert step to configuration for serialization."""
        base_data = self._serialize_types()
        return HttpStepConfig(**base_data)
    
    @classmethod
    def _from_config(cls, config: HttpStepConfig) ->  Self: # type: ignore
        """Create step from configuration.
        
        Args:
            config: Step configuration
            
        Returns:
            Recreated HttpStep instance
        """
        input_type, output_type = cls._deserialize_types(config)  # type: ignore
        return cls(
            step_id=config.step_id,
            metadata=config.metadata,
            input_type=input_type,
            output_type=output_type
        ) 