"""
AEY Vision Python SDK - AI-powered video analytics for security and surveillance.
"""
import json
import requests
import time
from typing import List, Dict, Union, Optional, Any, Callable, BinaryIO
from dataclasses import asdict
from io import BytesIO

# Import models and utilities
from .models import (
    Zone, Line, MetaData, Timing, Billing, AnalysisResult, AnalysisResponse,
    RegionCounterConfig, PPEConfig, RedactionConfig, ALPRConfig,
    AgeEmotionConfig, TimeInRegionConfig, EntryExitConfig, FallDetectionConfig
)
from .exceptions import (
    AeyVisionError, AuthenticationError, RateLimitError, ValidationError,
    APIError, NetworkError, TimeoutError
)
from .utils import retry_with_backoff, ProgressTracker, ProgressFileWrapper
from .builders import (
    MovementInZoneBuilder, PPEDetectionBuilder, VideoRedactionBuilder,
    TimeInRegionBuilder, EntryExitBuilder
)

# Export public API
__all__ = [
    'AeyVision',
    'Zone', 'Line',
    'RegionCounterConfig', 'PPEConfig', 'RedactionConfig', 'ALPRConfig',
    'AgeEmotionConfig', 'TimeInRegionConfig', 'EntryExitConfig', 'FallDetectionConfig',
    'AnalysisResponse', 'MetaData', 'Timing', 'Billing',
    'AeyVisionError', 'AuthenticationError', 'RateLimitError', 'ValidationError',
    'APIError', 'NetworkError', 'TimeoutError'
]

__version__ = '0.2.0'


class AeyVision:
    """
    AEY Vision SDK client for video analysis.
    
    Example:
        >>> sdk = AeyVision(api_key='your-api-key')
        >>> 
        >>> # Simple analysis
        >>> result = sdk.analyze('video.mp4', [{'type': 'region_counter', 'zones': [...]}])
        >>> 
        >>> # Using builder pattern
        >>> result = sdk.movement_in_zone('video.mp4') \\
        ...     .with_zones([{'name': 'entrance', 'points': [[0,0], [100,0], [100,100], [0,100]]}]) \\
        ...     .with_classes(['person', 'car']) \\
        ...     .with_confidence(0.7) \\
        ...     .execute()
        >>> 
        >>> # With context manager
        >>> with AeyVision(api_key='your-api-key') as sdk:
        ...     result = sdk.analyze('video.mp4', config)
    """
    
    def __init__(
        self,
        api_key: str,
        base_url: str = "https://api.aeyvision.com",
        timeout: int = 300,
        max_retries: int = 3,
        retry_delay: float = 1.0
    ):
        """
        Initialize the AEY Vision SDK.
        
        Args:
            api_key: Your API key
            base_url: Base URL for the API (default: https://api.aeyvision.com)
            timeout: Request timeout in seconds (default: 300)
            max_retries: Maximum number of retry attempts (default: 3)
            retry_delay: Initial retry delay in seconds (default: 1.0)
        """
        if not api_key:
            raise AuthenticationError("API key is required")
        
        self.api_key = api_key
        self.base_url = base_url.rstrip('/')
        self.timeout = timeout
        self.max_retries = max_retries
        self.retry_delay = retry_delay
        self._session: Optional[requests.Session] = None
    
    def __enter__(self):
        """Context manager entry."""
        self._session = requests.Session()
        return self
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        """Context manager exit."""
        if self._session:
            self._session.close()
            self._session = None
    
    def _get_session(self) -> requests.Session:
        """Get or create requests session."""
        if self._session is None:
            self._session = requests.Session()
        return self._session
    
    def _prepare_config(self, config: List[Union[Dict, Any]]) -> List[Dict]:
        """Prepare configuration for API request."""
        config_data = []
        for c in config:
            if hasattr(c, '__dict__'):
                if hasattr(c, 'dict'):
                    config_data.append(c.dict())
                else:
                    config_data.append(asdict(c))
            else:
                config_data.append(c)
        return config_data
    
    def _handle_response(self, response: requests.Response, return_annotated: bool, video_path: Optional[str] = None) -> 'AnalysisResult':
        """Handle API response and errors."""
        # Handle error responses
        if not response.ok:
            if response.status_code == 401:
                raise AuthenticationError("Invalid API key")
            elif response.status_code == 429:
                retry_after = response.headers.get('Retry-After')
                raise RateLimitError(
                    "Rate limit exceeded",
                    retry_after=int(retry_after) if retry_after else None
                )
            elif response.status_code == 400:
                raise ValidationError(f"Validation error: {response.text}")
            else:
                raise APIError(
                    f"API request failed: {response.text}",
                    status_code=response.status_code,
                    response_body=response.text
                )
        
        from .models import AnalysisResult
        
        # Handle annotated video response
        if return_annotated:
            content_type = response.headers.get('content-type', '')
            
            if 'application/json' in content_type:
                # New format: JSON response with base64 video
                data = response.json()
                base64_video = data.get('video')
                
                video_bytes = None
                if base64_video:
                    import base64
                    video_bytes = base64.b64decode(base64_video)
                
                # If we have analysis results in the response, use them
                if 'meta' in data and 'analyzers' in data:
                    return AnalysisResult.from_dict(data, video_bytes=video_bytes, video_path=video_path)
                
                # Fallback if structure is different (shouldn't happen with new API)
                raise APIError("Unexpected response structure with video")
            else:
                # Legacy format: Video blob response
                # Try to get analysis results from header
                video_bytes = response.content
                analysis_header = response.headers.get('X-Analysis-Results')
                
                if analysis_header:
                    import json
                    try:
                        data = json.loads(analysis_header)
                        return AnalysisResult.from_dict(data, video_bytes=video_bytes, video_path=video_path)
                    except:
                        pass
                
                # If no metadata, return a dummy result wrapping the bytes
                # This is a best-effort fallback for legacy endpoints
                return AnalysisResult(
                    meta=MetaData(0, 0, 0, 0),
                    analyzers={},
                    timing=Timing(0, 0),
                    video_bytes=video_bytes,
                    video_path=video_path
                )
        
        # Parse JSON response (no video requested)
        try:
            data = response.json()
            if isinstance(data, dict) and 'meta' in data and 'analyzers' in data:
                return AnalysisResult.from_dict(data, video_path=video_path)
            return data # Should be AnalysisResult but keeping fallback for safety
        except Exception as e:
            raise APIError(f"Failed to parse response: {e}")
    
    def analyze(
        self,
        video_path: Union[str, BinaryIO, bytes],
        config: List[Union[Dict, Any]],
        return_annotated: bool = False,
        progress_callback: Optional[Callable[[int, int], None]] = None
    ) -> 'AnalysisResult':
        """
        Analyze a video using the AEY Vision API.
        
        Args:
            video_path: Path to video file, file-like object, or bytes
            config: List of configuration objects or dictionaries
            return_annotated: Whether to return the annotated video as bytes
            progress_callback: Optional callback for upload progress (current, total)
        
        Returns:
            AnalysisResult object containing analysis data and optional video.
        
        Raises:
            AuthenticationError: Invalid API key
            ValidationError: Invalid configuration
            RateLimitError: Rate limit exceeded
            APIError: API request failed
            NetworkError: Network error occurred
            TimeoutError: Request timed out
        
        Example:
            >>> result = sdk.analyze('video.mp4', config)
            >>> print(result.analyzers)
            >>> if result.video_bytes:
            ...     result.save('output.mp4')
        """
        # Prepare configuration
        config_data = self._prepare_config(config)
        
        # Prepare file upload
        files = {}
        file_to_close = None
        input_path_str = None
        
        if isinstance(video_path, str):
            input_path_str = video_path
            file_obj = open(video_path, 'rb')
            files = {'video': ('video.mp4', file_obj, 'video/mp4')}
            file_to_close = file_obj
        elif isinstance(video_path, bytes):
            files = {'video': ('video.mp4', BytesIO(video_path), 'video/mp4')}
        else: # Assume BinaryIO
            files = {'video': ('video.mp4', video_path, 'video/mp4')}
        
        data = {'config': json.dumps(config_data)}
        if return_annotated:
            data['return_annotated'] = 'true'
        
        headers = {'X-API-Key': self.api_key}
        
        # Add Origin header for local testing
        if 'localhost' in self.base_url or '127.0.0.1' in self.base_url:
            headers['Origin'] = 'http://localhost:3000'
        
        # Define request function for retry logic
        def make_request():
            try:
                session = self._get_session()
                response = session.post(
                    f"{self.base_url}/api/v1/analyze",
                    headers=headers,
                    files=files,
                    data=data,
                    timeout=self.timeout
                )
                return response
            except requests.exceptions.Timeout:
                raise TimeoutError(f"Request timed out after {self.timeout} seconds")
            except requests.exceptions.ConnectionError as e:
                raise NetworkError(f"Network error: {e}")
            except requests.exceptions.RequestException as e:
                raise NetworkError(f"Request failed: {e}")
        
        # Execute with retry logic
        try:
            response = retry_with_backoff(
                make_request,
                max_retries=self.max_retries,
                initial_delay=self.retry_delay,
                retry_on=(NetworkError, TimeoutError)
            )
            return self._handle_response(response, return_annotated, video_path=input_path_str)
        finally:
            # Close file if we opened it
            if file_to_close:
                file_to_close.close()
    
    def multi_feature_analysis(
        self,
        video_path: Union[str, BinaryIO, bytes],
        features: List[Union[Dict, Any]],
        return_annotated: bool = False,
        progress_callback: Optional[Callable[[int, int], None]] = None
    ) -> Union[AnalysisResponse, bytes, Dict]:
        """
        Multi-Feature Analysis - Run multiple analyzers on a single video.
        
        Combine multiple analysis features in one pass for comprehensive insights.
        Save 25% on token costs when using 2+ features together!
        
        Args:
            video_path: Path to video file, file-like object, or bytes
            features: List of feature configuration objects or dictionaries
            return_annotated: Whether to return the annotated video as bytes
            progress_callback: Optional callback for upload progress
        
        Returns:
            AnalysisResponse object, bytes if return_annotated=True, or dict
        
        Example:
            >>> result = sdk.multi_feature_analysis(
            ...     'security-footage.mp4',
            ...     features=[
            ...         {'type': 'region_counter', 'zones': [...], 'classes': ['person']},
            ...         {'type': 'ppe', 'required_ppe': ['hardhat', 'safety_vest']},
            ...         {'type': 'fall_detection', 'confidence': 0.6}
            ...     ]
            ... )
            >>> print(result.analyzers)
        """
        if not features or len(features) < 1:
            raise ValidationError('Multi-feature analysis requires at least one feature configuration')
        
        return self.analyze(video_path, features, return_annotated, progress_callback)
    
    # Builder pattern methods
    def movement_in_zone(self, video_path: str) -> MovementInZoneBuilder:
        """
        Start building a movement in zone analysis.
        
        Example:
            >>> result = sdk.movement_in_zone('video.mp4') \\
            ...     .with_zones([{'name': 'entrance', 'points': [[0,0], [100,0], [100,100], [0,100]]}]) \\
            ...     .with_classes(['person']) \\
            ...     .with_confidence(0.7) \\
            ...     .execute()
        """
        return MovementInZoneBuilder(self, video_path)
    
    def ppe_detection(self, video_path: str) -> PPEDetectionBuilder:
        """
        Start building a PPE detection analysis.
        
        Example:
            >>> result = sdk.ppe_detection('video.mp4') \\
            ...     .with_required_ppe(['hardhat', 'safety_vest']) \\
            ...     .with_confidence(0.6) \\
            ...     .execute()
        """
        return PPEDetectionBuilder(self, video_path)
    
    def video_redaction(self, video_path: str) -> VideoRedactionBuilder:
        """
        Start building a video redaction.
        
        Example:
            >>> video_bytes = sdk.video_redaction('video.mp4') \\
            ...     .with_classes(['face', 'license_plate']) \\
            ...     .with_blur_kernel(99, 99) \\
            ...     .execute()
        """
        return VideoRedactionBuilder(self, video_path)
    
    def time_in_region(self, video_path: str) -> TimeInRegionBuilder:
        """
        Start building a time in region analysis.
        
        Example:
            >>> result = sdk.time_in_region('video.mp4') \\
            ...     .with_zones([{'name': 'zone1', 'points': [...]}]) \\
            ...     .execute()
        """
        return TimeInRegionBuilder(self, video_path)
    
    def entry_exit(self, video_path: str) -> EntryExitBuilder:
        """
        Start building an entry/exit counting analysis.
        
        Example:
            >>> result = sdk.entry_exit('video.mp4') \\
            ...     .with_lines([{'name': 'gate', 'points': [[0.5,0], [0.5,1]]}]) \\
            ...     .with_classes(['person', 'car']) \\
            ...     .execute()
        """
        return EntryExitBuilder(self, video_path)
