"""
Screen capture and Chromecast streaming component
Integrates FFmpeg screen capture with Chromecast streaming functionality
"""

import os
import sys
import time
import logging
import threading
import platform
import http.server
import socketserver
import socket
from typing import Optional, Dict, Any
from pathlib import Path

try:
    import ffmpeg
except ImportError:
    ffmpeg = None

try:
    import pychromecast
except ImportError:
    pychromecast = None

from .thread_manager import ThreadedComponent
from .message_bus import MessageBus, Message, MessageType, MessageBuilder

logger = logging.getLogger(__name__)


class StreamHandler(http.server.SimpleHTTPRequestHandler):
    """Custom HTTP handler for serving the video stream"""
    
    def __init__(self, *args, stream_file_path: str = "stream.mp4", **kwargs):
        self.stream_file_path = stream_file_path
        super().__init__(*args, **kwargs)
    
    def do_GET(self):
        """Handle GET requests for the video stream"""
        self.send_response(200)
        self.send_header("Content-type", "video/mp4")
        self.send_header("Access-Control-Allow-Origin", "*")
        self.end_headers()
        
        try:
            with open(self.stream_file_path, "rb") as f:
                chunk_size = 8192
                while True:
                    chunk = f.read(chunk_size)
                    if not chunk:
                        break
                    self.wfile.write(chunk)
        except FileNotFoundError:
            self.send_error(404, "Stream not ready")
        except Exception as e:
            logger.error(f"Error serving stream: {e}")
            self.send_error(500, "Server error")
    
    def log_message(self, format, *args):
        """Override to reduce logging verbosity"""
        pass


class ScreenCastComponent(ThreadedComponent):
    """Component for screen capture and Chromecast streaming"""
    
    def __init__(self, message_bus: MessageBus, stream_port: int = 8000, 
                 chromecast_name: Optional[str] = None, output_dir: Optional[str] = None):
        super().__init__("screen_cast", message_bus)
        
        self.stream_port = stream_port
        self.chromecast_name = chromecast_name or "Living Room"  # Default name
        self.output_dir = Path(output_dir) if output_dir else Path.cwd()
        self.stream_file = self.output_dir / "stream.mp4"
        
        # Components
        self.http_server: Optional[socketserver.TCPServer] = None
        self.chromecast = None
        self.media_controller = None
        self.browser = None
        self.ffmpeg_process = None
        
        # Control flags
        self.capture_active = False
        self.streaming_active = False
        self.server_thread: Optional[threading.Thread] = None
        self.capture_thread: Optional[threading.Thread] = None
        
        # Status
        self.last_error = None
        self._local_ip = None
        
    def initialize(self) -> bool:
        """Initialize the screen cast component"""
        try:
            logger.info("Initializing ScreenCast component...")
            
            # Check dependencies
            if not self._check_dependencies():
                return False
            
            # Ensure output directory exists
            self.output_dir.mkdir(parents=True, exist_ok=True)
            
            # Get local IP address for Chromecast streaming
            self._local_ip = self._get_local_ip()
            logger.info(f"Using local IP address: {self._local_ip}")
            
            # Initialize HTTP server
            if not self._initialize_http_server():
                return False
            
            # Register with message bus to receive messages
            self.message_bus.register_component(self.name)
            
            # Subscribe to messages
            self.message_bus.subscribe(self.name, MessageType.SYSTEM_SHUTDOWN, self._handle_shutdown)
            
            logger.info("ScreenCast component initialized successfully")
            return True
            
        except Exception as e:
            logger.error(f"ScreenCast initialization failed: {e}")
            self.last_error = str(e)
            return False
    
    def _check_dependencies(self) -> bool:
        """Check if required dependencies are available"""
        missing_deps = []
        
        if ffmpeg is None:
            missing_deps.append("ffmpeg-python")
        
        if pychromecast is None:
            missing_deps.append("pychromecast")
        
        if missing_deps:
            logger.error(f"Missing required dependencies: {missing_deps}")
            logger.error("Install with: pip install " + " ".join(missing_deps))
            return False
        
        return True
    
    def _initialize_http_server(self) -> bool:
        """Initialize HTTP server for streaming"""
        try:
            # Create custom handler class with stream file path
            def handler(*args, **kwargs):
                return StreamHandler(*args, stream_file_path=str(self.stream_file), **kwargs)
            
            self.http_server = socketserver.TCPServer(("", self.stream_port), handler)
            logger.info(f"HTTP server initialized on port {self.stream_port}")
            return True
            
        except Exception as e:
            logger.error(f"Failed to initialize HTTP server: {e}")
            return False
    
    def run(self):
        """Main run loop"""
        try:
            logger.info("ScreenCast component thread started")
            
            # Send ready status
            ready_message = MessageBuilder.system_status(
                sender=self.name,
                status="ready",
                details={
                    "stream_port": self.stream_port,
                    "chromecast_name": self.chromecast_name,
                    "output_dir": str(self.output_dir)
                }
            )
            self.message_bus.publish(ready_message)
            
            # Start HTTP server
            self._start_http_server()
            
            # Try to connect to Chromecast
            self._connect_chromecast()
            
            # Main loop - monitor and restart capture if needed
            while self.running:
                try:
                    # Process messages
                    message = self.message_bus.get_message(self.name, timeout=1.0)
                    if message:
                        self._process_message(message)
                    
                    # Check capture health
                    self._check_capture_health()
                    
                    # Update heartbeat
                    self.heartbeat()
                    
                    time.sleep(1.0)
                    
                except Exception as e:
                    logger.error(f"ScreenCast run loop error: {e}")
                    time.sleep(1.0)
                    
        except Exception as e:
            logger.error(f"ScreenCast run failed: {e}")
        finally:
            logger.info("ScreenCast component thread ended")
    
    def _start_http_server(self):
        """Start HTTP server in separate thread"""
        try:
            self.server_thread = threading.Thread(
                target=self._run_http_server,
                name="ScreenCastHTTPServer",
                daemon=True
            )
            self.server_thread.start()
            logger.info(f"HTTP server started on http://localhost:{self.stream_port}")
            
        except Exception as e:
            logger.error(f"Failed to start HTTP server: {e}")
    
    def _run_http_server(self):
        """Run HTTP server"""
        try:
            self.http_server.serve_forever()
        except Exception as e:
            if self.running:  # Only log if not shutting down
                logger.error(f"HTTP server error: {e}")
    
    def _connect_chromecast(self):
        """Connect to Chromecast device"""
        try:
            logger.info("Starting Chromecast discovery...")
            
            # Discover all available Chromecasts with timeout
            all_chromecasts, self.browser = pychromecast.get_chromecasts(timeout=10)
            
            if not all_chromecasts:
                logger.warning("No Chromecast devices found on network")
                return False
            
            # Log all discovered devices
            logger.info(f"Found {len(all_chromecasts)} Chromecast device(s):")
            for i, cast in enumerate(all_chromecasts):
                try:
                    cast.wait(timeout=5)
                    # Get host from socket_client
                    host = getattr(cast.socket_client, 'host', 'Unknown') if hasattr(cast, 'socket_client') else 'Unknown'
                    
                    # Get device name safely
                    name = f"Device-{host}"
                    if hasattr(cast, 'device') and cast.device:
                        name = getattr(cast.device, 'friendly_name', name)
                    elif hasattr(cast, '_device') and cast._device:
                        name = getattr(cast._device, 'friendly_name', name)
                    elif hasattr(cast, 'name'):
                        name = cast.name
                        
                    logger.info(f"  {i+1}. {name} at {host}")
                except Exception as e:
                    # Try to get host even if device info fails
                    try:
                        host = getattr(cast.socket_client, 'host', 'Unknown') if hasattr(cast, 'socket_client') else 'Unknown'
                        logger.info(f"  {i+1}. Device at {host} (name unavailable)")
                    except:
                        logger.info(f"  {i+1}. Device (host unavailable)")
            
            # Try to find the specified device by name
            selected_cast = None
            if self.chromecast_name and self.chromecast_name != "Living Room":  # Skip default value
                for cast in all_chromecasts:
                    try:
                        cast.wait(timeout=5)
                        # Get device name safely for comparison
                        device_name = None
                        if hasattr(cast, 'device') and cast.device:
                            device_name = getattr(cast.device, 'friendly_name', None)
                        elif hasattr(cast, '_device') and cast._device:
                            device_name = getattr(cast._device, 'friendly_name', None)
                        elif hasattr(cast, 'name'):
                            device_name = cast.name
                            
                        if device_name and device_name.lower() == self.chromecast_name.lower():
                            selected_cast = cast
                            logger.info(f"Found specified Chromecast: {device_name}")
                            break
                    except:
                        continue
                
                if not selected_cast:
                    logger.warning(f"Specified Chromecast '{self.chromecast_name}' not found")
            
            # If no specific device found or specified, use the first available
            if not selected_cast:
                selected_cast = all_chromecasts[0]
                try:
                    selected_cast.wait(timeout=5)
                    # Get device name safely
                    name = 'Unknown'
                    if hasattr(selected_cast, 'device') and selected_cast.device:
                        name = getattr(selected_cast.device, 'friendly_name', 'Unknown')
                    elif hasattr(selected_cast, '_device') and selected_cast._device:
                        name = getattr(selected_cast._device, 'friendly_name', 'Unknown')
                    elif hasattr(selected_cast, 'name'):
                        name = selected_cast.name
                        
                    host = getattr(selected_cast.socket_client, 'host', 'Unknown') if hasattr(selected_cast, 'socket_client') else 'Unknown'
                    logger.info(f"Using first available Chromecast: {name}")
                except:
                    try:
                        host = getattr(selected_cast.socket_client, 'host', 'Unknown') if hasattr(selected_cast, 'socket_client') else 'Unknown'
                        logger.info(f"Using Chromecast at {host}")
                    except:
                        logger.info("Using discovered Chromecast")
            
            self.chromecast = selected_cast
            self.media_controller = self.chromecast.media_controller
            
            # Get device info for status update
            device_name = "Unknown"
            device_type = "Chromecast"
            try:
                self.chromecast.wait(timeout=5)
                device_name = self.chromecast.device.friendly_name
                device_type = str(self.chromecast.device.cast_type) if self.chromecast.device.cast_type else "Chromecast"
            except Exception as e:
                logger.warning(f"Could not get device info: {e}")
            
            # Get connection details
            host = getattr(self.chromecast.socket_client, 'host', 'Unknown') if hasattr(self.chromecast, 'socket_client') else 'Unknown'
            port = getattr(self.chromecast.socket_client, 'port', 8009) if hasattr(self.chromecast, 'socket_client') else 8009
            
            logger.info(f"Connected to Chromecast: {device_name} at {host}")
            
            # Send status update
            status_message = MessageBuilder.system_status(
                sender=self.name,
                status="chromecast_connected",
                details={
                    "device_name": device_name,
                    "device_type": device_type,
                    "host": host,
                    "port": port
                }
            )
            self.message_bus.publish(status_message)
            
            return True
            
        except Exception as e:
            logger.error(f"Failed to connect to Chromecast: {e}")
            return False
    
    def start_capture(self, resolution: str = "1280x720", framerate: int = 15) -> bool:
        """Start screen capture"""
        try:
            if self.capture_active:
                logger.warning("Screen capture is already active")
                return True
            
            logger.info("Starting screen capture...")
            
            # Stop any existing capture
            self.stop_capture()
            
            # Start capture in separate thread
            self.capture_thread = threading.Thread(
                target=self._capture_loop,
                args=(resolution, framerate),
                name="ScreenCapture",
                daemon=True
            )
            self.capture_active = True
            self.capture_thread.start()
            
            logger.info("Screen capture started")
            return True
            
        except Exception as e:
            logger.error(f"Failed to start capture: {e}")
            self.last_error = str(e)
            return False
    
    def stop_capture(self):
        """Stop screen capture"""
        try:
            if not self.capture_active:
                return
            
            logger.info("Stopping screen capture...")
            self.capture_active = False
            
            # Kill FFmpeg process if running
            if self.ffmpeg_process:
                try:
                    self.ffmpeg_process.terminate()
                    self.ffmpeg_process.wait(timeout=5)
                except Exception as e:
                    logger.warning(f"Error terminating FFmpeg process: {e}")
                    try:
                        self.ffmpeg_process.kill()
                    except:
                        pass
                finally:
                    self.ffmpeg_process = None
            
            # Wait for capture thread
            if self.capture_thread and self.capture_thread.is_alive():
                self.capture_thread.join(timeout=5.0)
            
            # Clean up stream file
            if self.stream_file.exists():
                try:
                    self.stream_file.unlink()
                except Exception as e:
                    logger.warning(f"Failed to remove stream file: {e}")
            
            logger.info("Screen capture stopped")
            
        except Exception as e:
            logger.error(f"Error stopping capture: {e}")
    
    def _capture_loop(self, resolution: str, framerate: int):
        """Screen capture loop with automatic restart"""
        system = platform.system()
        
        while self.capture_active and self.running:
            try:
                logger.info(f"Starting FFmpeg capture - {resolution} @ {framerate}fps")
                
                # Get platform-specific input sources
                video_input, audio_input = self._get_input_sources(system, resolution)
                
                if video_input is None:
                    logger.error(f"Unsupported platform: {system}")
                    break
                
                # Build FFmpeg command
                inputs = [video_input]
                if audio_input:
                    inputs.append(audio_input)
                
                stream = ffmpeg.output(
                    *inputs, str(self.stream_file),
                    format="mp4",
                    vcodec="libx264",
                    acodec="aac" if audio_input else None,
                    pix_fmt="yuv420p",
                    r=framerate,
                    preset="ultrafast",
                    tune="zerolatency",
                    movflags="frag_keyframe+empty_moov",
                    ac=2 if audio_input else None,
                    ar=44100 if audio_input else None
                )
                stream = ffmpeg.overwrite_output(stream)
                
                # Start FFmpeg process
                self.ffmpeg_process = ffmpeg.run_async(stream, pipe_stderr=True)
                
                # Start streaming to Chromecast if connected
                if self.chromecast and self.media_controller:
                    self._start_chromecast_streaming()
                
                # Wait for process to finish or be terminated
                self.ffmpeg_process.wait()
                
                if self.capture_active:
                    logger.warning("FFmpeg process ended unexpectedly")
                
            except Exception as e:
                logger.error(f"FFmpeg capture error: {e}")
                self.last_error = str(e)
                
            finally:
                self.ffmpeg_process = None
                
                # Clean up before restart
                if self.stream_file.exists():
                    try:
                        self.stream_file.unlink()
                    except:
                        pass
                
                if self.capture_active:
                    logger.info("Restarting FFmpeg capture in 5 seconds...")
                    time.sleep(5)
    
    def _get_input_sources(self, system: str, resolution: str):
        """Get platform-specific input sources"""
        video_input = None
        audio_input = None
        
        try:
            if system == "Linux":
                video_input = ffmpeg.input(":0.0+0,0", format="x11grab", s=resolution)
                try:
                    # Try PulseAudio first
                    audio_input = ffmpeg.input("default", format="pulse")
                except:
                    try:
                        # Fallback to ALSA
                        audio_input = ffmpeg.input("hw:0", format="alsa")
                    except:
                        logger.warning("No audio input available")
                        audio_input = None
                        
            elif system == "Windows":
                video_input = ffmpeg.input("desktop", format="gdigrab", s=resolution)
                try:
                    audio_input = ffmpeg.input("audio=Stereo Mix", format="dshow")
                except:
                    logger.warning("No audio input available - configure Stereo Mix")
                    audio_input = None
                    
            elif system == "Darwin":  # macOS
                video_input = ffmpeg.input("1:none", format="avfoundation", capture_cursor=1, s=resolution)
                try:
                    audio_input = ffmpeg.input("0", format="avfoundation")
                except:
                    logger.warning("No audio input available")
                    audio_input = None
                    
        except Exception as e:
            logger.error(f"Error setting up input sources: {e}")
            
        return video_input, audio_input
    
    def _get_local_ip(self) -> str:
        """Get local IP address for network communication"""
        try:
            # Connect to a remote address to determine local IP
            # This doesn't actually send data, just opens a socket
            with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
                # Connect to Google's DNS server to get the route
                s.connect(("8.8.8.8", 80))
                local_ip = s.getsockname()[0]
                return local_ip
        except Exception as e:
            logger.warning(f"Could not determine local IP address: {e}")
            # Fallback to localhost
            return "127.0.0.1"
    
    def _start_chromecast_streaming(self):
        """Start streaming to Chromecast"""
        try:
            if not self.chromecast or not self.media_controller:
                return
            
            # Give FFmpeg a moment to start generating the file
            time.sleep(2)
            
            # Use network IP instead of localhost for Chromecast compatibility
            stream_url = f"http://{self._local_ip}:{self.stream_port}/stream.mp4"
            logger.info(f"Starting Chromecast playback: {stream_url}")
            
            self.media_controller.play_media(stream_url, "video/mp4")
            self.media_controller.block_until_active()
            
            self.streaming_active = True
            
            # Send status update
            status_message = MessageBuilder.system_status(
                sender=self.name,
                status="streaming_started",
                details={"stream_url": stream_url}
            )
            self.message_bus.publish(status_message)
            
        except Exception as e:
            logger.error(f"Failed to start Chromecast streaming: {e}")
    
    def stop_streaming(self):
        """Stop Chromecast streaming but keep connection"""
        try:
            if self.streaming_active and self.chromecast:
                # Stop the current media session
                if self.media_controller:
                    self.media_controller.stop()
                    logger.info("Media playback stopped")
                
                # Quit the current app to reset its state
                try:
                    self.chromecast.quit_app()
                    logger.info("Chromecast app quit for clean state")
                    
                    # Wait for the app to quit
                    time.sleep(2)
                    
                    # Wait for the Chromecast to be ready again
                    self.chromecast.wait(timeout=5)
                    
                    # Refresh the media controller
                    self.media_controller = self.chromecast.media_controller
                    logger.info("Media controller refreshed")
                    
                except Exception as e:
                    logger.warning(f"Could not quit/restart Chromecast app: {e}")
                
                self.streaming_active = False
                logger.info("Chromecast streaming stopped, ready for new session")
                
                # Send streaming stopped status update
                status_message = MessageBuilder.system_status(
                    sender=self.name,
                    status="streaming_stopped",
                    details={"chromecast_still_connected": True}
                )
                self.message_bus.publish(status_message)
                
        except Exception as e:
            logger.error(f"Error stopping streaming: {e}")
    
    def disconnect_chromecast(self):
        """Disconnect from Chromecast device"""
        try:
            if self.chromecast:
                try:
                    # Stop streaming first if active
                    if self.streaming_active:
                        self.stop_streaming()
                    
                    # Quit the Chromecast app
                    self.chromecast.quit_app()
                    logger.info("Chromecast app quit")
                except Exception as e:
                    logger.warning(f"Error quitting Chromecast app: {e}")
                
                # Reset Chromecast connection
                self.chromecast = None
                self.media_controller = None
                logger.info("Chromecast disconnected")
                
                # Send disconnection status update
                status_message = MessageBuilder.system_status(
                    sender=self.name,
                    status="chromecast_disconnected",
                    details={"reason": "manual_disconnect"}
                )
                self.message_bus.publish(status_message)
                
        except Exception as e:
            logger.error(f"Error disconnecting Chromecast: {e}")
    
    def _check_capture_health(self):
        """Check if capture is healthy and restart if needed"""
        if self.capture_active and self.capture_thread:
            if not self.capture_thread.is_alive():
                logger.warning("Capture thread died, restarting...")
                self.start_capture()
    
    def _process_message(self, message: Message):
        """Process received messages"""
        try:
            if message.type == MessageType.SYSTEM_SHUTDOWN:
                self._handle_shutdown(message)
            elif message.type == MessageType.WEB_ACTION:
                self._handle_web_action(message)
            elif message.type == MessageType.CONFIG_REQUEST:
                self._handle_config_request(message)
                
        except Exception as e:
            logger.error(f"Error processing message: {e}")
    
    def _handle_web_action(self, message: Message):
        """Handle web dashboard actions"""
        try:
            action = message.data.get("action")
            logger.info(f"Received web action: {action} from {message.sender}")
            
            if action == "start_capture":
                resolution = message.data.get("resolution", "1280x720")
                framerate = message.data.get("framerate", 15)
                success = self.start_capture(resolution, framerate)
                logger.info(f"Start capture web action result: {success}")
                
            elif action == "stop_capture":
                self.stop_capture()
                logger.info("Stop capture web action executed")
                
            elif action == "start_streaming":
                if self.chromecast and self.media_controller and self.stream_file.exists():
                    self._start_chromecast_streaming()
                    logger.info("Start streaming web action executed")
                else:
                    logger.warning("Cannot start streaming - chromecast not connected or no stream file")
                    self.last_error = "Chromecast not connected or no active capture"
                
            elif action == "stop_streaming":
                self.stop_streaming()
                logger.info("Stop streaming web action executed")
                
            elif action == "disconnect_chromecast":
                self.disconnect_chromecast()
                logger.info("Disconnect Chromecast web action executed")
                
        except Exception as e:
            logger.error(f"Error handling web action: {e}")
            self.last_error = str(e)
    
    def _handle_config_request(self, message: Message):
        """Handle configuration/status requests"""
        try:
            request_type = message.data.get("request_type")
            logger.debug(f"Received config request: {request_type} from {message.sender}")
            
            if request_type == "status":
                # Send status update
                status_data = self.get_status()
                status_message = MessageBuilder.system_status(
                    sender=self.name,
                    status="status_response",
                    details=status_data,
                    correlation_id=message.correlation_id
                )
                self.message_bus.publish(status_message)
                
        except Exception as e:
            logger.error(f"Error handling config request: {e}")
    
    def _handle_shutdown(self, message: Message):
        """Handle shutdown message"""
        logger.info("Shutdown message received")
        self.running = False
        self.shutdown_event.set()
    
    def shutdown(self):
        """Shutdown the component"""
        try:
            logger.info("Shutting down ScreenCast component...")
            
            # Stop capture
            self.stop_capture()
            
            # Stop streaming
            self.stop_streaming()
            
            # Disconnect from Chromecast
            if self.chromecast:
                try:
                    self.chromecast.quit_app()
                except:
                    pass
            
            if self.browser:
                try:
                    pychromecast.discovery.stop_discovery(self.browser)
                except:
                    pass
            
            # Shutdown HTTP server
            if self.http_server:
                try:
                    self.http_server.shutdown()
                    self.http_server.server_close()
                except:
                    pass
            
            # Clean up stream file
            if self.stream_file.exists():
                try:
                    self.stream_file.unlink()
                except:
                    pass
            
            logger.info("ScreenCast component shutdown completed")
            
        except Exception as e:
            logger.error(f"Error during ScreenCast shutdown: {e}")
    
    def get_status(self) -> Dict[str, Any]:
        """Get component status"""
        base_status = super().get_status()
        base_status.update({
            "capture_active": self.capture_active,
            "streaming_active": self.streaming_active,
            "stream_port": self.stream_port,
            "chromecast_connected": self.chromecast is not None,
            "chromecast_name": self.chromecast_name,
            "last_error": self.last_error,
            "stream_file_exists": self.stream_file.exists() if self.stream_file else False,
            "local_ip": self._local_ip
        })
        
        if self.chromecast:
            # Safely get device name
            device_name = "Unknown"
            try:
                if hasattr(self.chromecast, 'device') and self.chromecast.device:
                    device_name = getattr(self.chromecast.device, 'friendly_name', 'Unknown')
                elif hasattr(self.chromecast, '_device') and self.chromecast._device:
                    device_name = getattr(self.chromecast._device, 'friendly_name', 'Unknown')
                elif hasattr(self.chromecast, 'name'):
                    device_name = self.chromecast.name
            except Exception as e:
                logger.debug(f"Could not get chromecast device name: {e}")
            
            base_status["chromecast_device"] = device_name
            
        return base_status