import requests
import os
import sys
from pathlib import Path
from typing import Dict, Tuple, Optional
from openspace.utils.logging import Logger
from openspace.local_server.feature_checker import FeatureChecker

logger = Logger.get_logger(__name__)

from openspace.utils.display import colorize as _c


class HealthStatus:
    """Health status"""
    def __init__(self, feature_available: bool, endpoint_available: Optional[bool], 
                 endpoint_detail: str = ""):
        self.feature_available = feature_available
        self.endpoint_available = endpoint_available
        self.endpoint_detail = endpoint_detail
    
    @property
    def fully_available(self) -> bool:
        """Fully available: feature and endpoint are available"""
        return self.feature_available and (self.endpoint_available == True)
    
    def __str__(self):
        if not self.feature_available:
            return "Feature N/A"
        elif self.endpoint_available is None:
            return "Feature OK (endpoint not tested)"
        elif self.endpoint_available:
            return f"OK ({self.endpoint_detail})"
        else:
            return f"Endpoint failed: {self.endpoint_detail}"


class HealthChecker:
    """Health checker with functional testing"""
    
    def __init__(self, feature_checker: FeatureChecker, 
                 base_url: str = "http://127.0.0.1:5000",
                 auto_cleanup: bool = True,
                 test_output_dir: str = None):
        self.feature_checker = feature_checker
        self.base_url = base_url
        self.results = {}
        self.auto_cleanup = auto_cleanup
        
        # set the test output directory
        if test_output_dir:
            self.test_output_dir = Path(test_output_dir)
        else:
            current_dir = Path(__file__).parent
            self.test_output_dir = current_dir / "temp"
        
        # create the directory
        self.test_output_dir.mkdir(exist_ok=True)
        
        self.temp_files = []  # Track temporary files for cleanup
        
        logger.info(f"Health checker initialized. Test output: {self.test_output_dir}, Auto-cleanup: {auto_cleanup}")

    def _auth_headers(self) -> dict[str, str]:
        token = os.environ.get("OPENSPACE_LOCAL_SERVER_TOKEN", "").strip()
        return {"Authorization": f"Bearer {token}"} if token else {}
    
    def _get_test_file_path(self, filename: str) -> str:
        """Get path for a test file"""
        filepath = str(self.test_output_dir / filename)
        self._register_temp_file(filepath)
        return filepath
    
    def _register_temp_file(self, filepath: str):
        """Register a temporary file for later cleanup"""
        if filepath and filepath not in self.temp_files:
            self.temp_files.append(filepath)
    
    def cleanup_temp_files(self):
        """Clean up all temporary test files"""
        if not self.auto_cleanup:
            logger.info(f"Auto-cleanup disabled. Test files kept in: {self.test_output_dir}")
            return
        
        cleaned = 0
        for filepath in self.temp_files:
            try:
                if os.path.exists(filepath):
                    os.remove(filepath)
                    cleaned += 1
                    logger.debug(f"Cleaned up: {filepath}")
            except Exception as e:
                logger.warning(f"Failed to clean up {filepath}: {e}")
        
        self.temp_files.clear()
        
        # if the directory is empty, delete it
        try:
            if self.test_output_dir.exists() and not any(self.test_output_dir.iterdir()):
                self.test_output_dir.rmdir()
                logger.debug(f"Removed empty directory: {self.test_output_dir}")
        except:
            pass
        
        if cleaned > 0:
            logger.info(f"Cleaned up {cleaned} test files")
    
    def check_screenshot(self) -> Tuple[bool, str]:
        """Functionally test screenshot - actually take a screenshot and verify"""
        # 1. Check feature first
        if not self.feature_checker.check_screenshot_available():
            return False, "Feature N/A"
        
        # 2. Save screenshot to test directory
        screenshot_path = self._get_test_file_path("test_screenshot.png")
        
        try:
            response = requests.get(
                f"{self.base_url}/screenshot",
                timeout=10,
                headers=self._auth_headers(),
            )
            
            if response.status_code != 200:
                return False, f"HTTP {response.status_code}"
            
            # 3. Save to file
            with open(screenshot_path, 'wb') as f:
                f.write(response.content)
            
            # 4. Verify it's actually an image
            content_type = response.headers.get('Content-Type', '')
            if 'image' not in content_type:
                return False, f"Invalid content type: {content_type}"
            
            # 5. Check file size (should be > 1KB)
            size_kb = len(response.content) / 1024
            if size_kb < 1:
                return False, "Image too small"
            
            logger.info(f"Screenshot saved: {screenshot_path} ({size_kb:.1f}KB)")
            return True, f"OK ({size_kb:.1f}KB)"
            
        except requests.exceptions.Timeout:
            return False, "Timeout"
        except Exception as e:
            return False, f"Error: {str(e)[:30]}"
    
    def check_cursor_position(self) -> Tuple[bool, str]:
        """Test cursor position"""
        if not self.feature_checker.check_screenshot_available():
            return False, "Feature N/A"
        
        try:
            response = requests.get(
                f"{self.base_url}/cursor_position",
                timeout=5,
                headers=self._auth_headers(),
            )
            if response.status_code != 200:
                return False, f"HTTP {response.status_code}"
            
            data = response.json()
            if 'x' in data and 'y' in data:
                return True, f"({data['x']}, {data['y']})"
            return False, "Invalid response"
        except Exception as e:
            return False, str(e)[:30]
    
    def check_screen_size(self) -> Tuple[bool, str]:
        """Test screen size"""
        if not self.feature_checker.check_screenshot_available():
            return False, "Feature N/A"
        
        try:
            response = requests.get(
                f"{self.base_url}/screen_size",
                timeout=5,
                headers=self._auth_headers(),
            )
            if response.status_code != 200:
                return False, f"HTTP {response.status_code}"
            
            data = response.json()
            if 'width' in data and 'height' in data:
                return True, f"{data['width']}x{data['height']}"
            return False, "Invalid response"
        except Exception as e:
            return False, str(e)[:30]
    
    def check_gui_execute_endpoint(self) -> Tuple[bool, str]:
        """Functionally test the GUI-only execute endpoint"""
        try:
            response = requests.post(
                f"{self.base_url}/execute",
                json={
                    "command": [
                        sys.executable,
                        "-c",
                        (
                            "import pyautogui; import time; "
                            "pyautogui.FAILSAFE = False; print('hello_test')"
                        ),
                    ],
                    "shell": False,
                },
                timeout=5,
                headers=self._auth_headers(),
            )
            
            if response.status_code != 200:
                return False, f"HTTP {response.status_code}"
            
            data = response.json()
            output = data.get('output', '').strip()
            
            # Verify the command actually executed
            if 'hello_test' in output:
                return True, "GUI execute endpoint works"
            return False, "Command failed"
            
        except Exception as e:
            return False, str(e)[:30]
    
    def check_python_execution(self) -> Tuple[bool, str]:
        """Functionally test Python code execution"""
        if not self.feature_checker.check_python_available():
            return False, "Feature N/A"
        
        try:
            test_code = 'print("test_output_123")'
            response = requests.post(
                f"{self.base_url}/run_python",
                json={"code": test_code},
                timeout=5,
                headers=self._auth_headers(),
            )
            
            if response.status_code != 200:
                return False, f"HTTP {response.status_code}"
            
            data = response.json()
            content = data.get('content', '')
            
            # Verify Python executed correctly
            if 'test_output_123' in content:
                return True, "Python executed"
            return False, "Execution failed"
            
        except Exception as e:
            return False, str(e)[:30]
    
    def check_file_operations(self) -> Tuple[bool, str]:
        """Test file operations"""
        if not self.feature_checker.check_file_ops_available():
            return False, "Feature N/A"
        
        try:
            # Test list directory
            response = requests.post(
                f"{self.base_url}/list_directory",
                json={"path": "."},
                timeout=5,
                headers=self._auth_headers(),
            )
            
            if response.status_code != 200:
                return False, f"HTTP {response.status_code}"
            
            data = response.json()
            if 'items' in data and isinstance(data['items'], list):
                return True, f"{len(data['items'])} items"
            return False, "Invalid response"
            
        except Exception as e:
            return False, str(e)[:30]
    
    def check_desktop_path(self) -> Tuple[bool, str]:
        """Test desktop path"""
        if not self.feature_checker.check_file_ops_available():
            return False, "Feature N/A"
        
        try:
            response = requests.get(
                f"{self.base_url}/desktop_path",
                timeout=5,
                headers=self._auth_headers(),
            )
            if response.status_code != 200:
                return False, f"HTTP {response.status_code}"
            
            data = response.json()
            path = data.get('path', '')
            if path and os.path.exists(path):
                return True, "Path valid"
            return False, "Path not found"
        except Exception as e:
            return False, str(e)[:30]
    
    def check_window_management(self) -> Tuple[bool, str]:
        """Test window management"""
        if not self.feature_checker.check_window_mgmt_available():
            return False, "Feature N/A"
        
        try:
            # Just test if endpoint responds (window may not exist)
            response = requests.post(
                f"{self.base_url}/setup/activate_window",
                json={"window_name": "NonExistentWindow"},
                timeout=5,
                headers=self._auth_headers(),
            )
            
            # 200 (success), 404 (not found), 501 (not supported) are all acceptable
            if response.status_code in [200, 404, 501]:
                return True, f"API available"
            return False, f"HTTP {response.status_code}"
        except Exception as e:
            return False, str(e)[:30]
    
    def check_recording(self) -> Tuple[bool, str]:
        """Functionally test recording - actually start and stop recording"""
        if not self.feature_checker.check_recording_available():
            return False, "Feature N/A"
        
        recording_path = self._get_test_file_path("test_recording.mp4")
        
        try:
            # 1. Start recording
            response = requests.post(
                f"{self.base_url}/start_recording",
                json={},
                timeout=10,
                headers=self._auth_headers(),
            )
            
            if response.status_code == 501:
                return False, "Not supported"
            
            if response.status_code != 200:
                return False, f"Start failed: {response.status_code}"
            
            # 2. Wait a bit
            import time
            time.sleep(3.0)  # Record for 3 seconds
            
            # 3. Stop recording
            response = requests.post(
                f"{self.base_url}/end_recording",
                json={},
                timeout=15,
                headers=self._auth_headers(),
            )
            
            if response.status_code == 200:
                # Save the recording file
                with open(recording_path, 'wb') as f:
                    f.write(response.content)
                
                size_kb = len(response.content) / 1024
                logger.info(f"Recording saved: {recording_path} ({size_kb:.1f}KB)")
                return True, f"OK ({size_kb:.1f}KB)"
            else:
                return False, f"Stop failed: {response.status_code}"
                
        except Exception as e:
            # Try to stop recording in case of error
            try:
                requests.post(
                    f"{self.base_url}/end_recording",
                    json={},
                    timeout=5,
                    headers=self._auth_headers(),
                )
            except:
                pass
            return False, str(e)[:30]
    
    def check_accessibility(self) -> Tuple[bool, str]:
        """Test accessibility tree"""
        if not self.feature_checker.check_accessibility_available():
            return False, "Feature N/A"
        
        try:
            response = requests.get(
                f"{self.base_url}/accessibility?max_depth=1",
                timeout=10,
                headers=self._auth_headers(),
            )
            
            if response.status_code != 200:
                return False, f"HTTP {response.status_code}"
            
            data = response.json()
            if 'error' in data:
                return False, "Permission denied"
            
            # Should have some tree structure
            if 'platform' in data or 'children' in data:
                return True, "Tree available"
            return False, "Invalid response"
            
        except Exception as e:
            return False, str(e)[:30]
    
    def check_health_endpoint(self) -> Tuple[bool, str]:
        """Test health check endpoint"""
        try:
            response = requests.get(f"{self.base_url}/", timeout=5)
            if response.status_code == 200:
                data = response.json()
                if data.get('status') == 'ok':
                    return True, "OK"
            return False, f"HTTP {response.status_code}"
        except Exception as e:
            return False, str(e)[:30]
    
    def check_platform_info(self) -> Tuple[bool, str]:
        """Test platform info endpoint"""
        try:
            response = requests.get(f"{self.base_url}/platform", timeout=5)
            if response.status_code == 200:
                data = response.json()
                if 'system' in data:
                    return True, data['system']
            return False, f"HTTP {response.status_code}"
        except Exception as e:
            return False, str(e)[:30]
    
    def check_all(self, test_endpoints: bool = True) -> Dict[str, HealthStatus]:
        """
        Check all features with functional testing
        
        Args:
            test_endpoints: Whether to test endpoints (False only checks features)
        
        Returns:
            {Feature name: HealthStatus}
        """
        results = {}
        
        if not test_endpoints:
            # Only check features, not endpoints
            feature_results = self.feature_checker.check_all_features()
            for name, available in feature_results.items():
                results[name] = HealthStatus(available, None, "")
            self.results = results
            return results
        
        # Functional tests
        test_functions = {
            'Health Check': self.check_health_endpoint,
            'Platform Info': self.check_platform_info,
            'Screenshot': self.check_screenshot,
            'Cursor Position': self.check_cursor_position,
            'Screen Size': self.check_screen_size,
            'GUI Execute': self.check_gui_execute_endpoint,
            'Python Execution': self.check_python_execution,
            'File Operations': self.check_file_operations,
            'Desktop Path': self.check_desktop_path,
            'Window Management': self.check_window_management,
            'Recording': self.check_recording,
            'Accessibility': self.check_accessibility,
        }
        
        for name, test_func in test_functions.items():
            success, detail = test_func()
            
            # Determine feature availability
            if detail == "Feature N/A":
                feature_available = False
                endpoint_available = None
            else:
                feature_available = True
                endpoint_available = success
            
            results[name] = HealthStatus(feature_available, endpoint_available, detail)
        
        # Clean up temporary files
        self.cleanup_temp_files()
        
        self.results = results
        return results
    
    def print_results(self, results: Dict[str, HealthStatus] = None, 
                     show_endpoint_details: bool = False):
        """Print check results"""
        if results is None:
            results = self.results
        
        if not results:
            return
        
        total = len(results)
        feature_available = sum(1 for s in results.values() if s.feature_available)
        fully_available = sum(1 for s in results.values() if s.fully_available)
        
        # Categorize
        basic = ['Health Check', 'Platform Info']
        
        # Basic Features
        print()
        print(_c("  - Basic", 'c', bold=True))
        basic_items = []
        for name in basic:
            if name in results:
                status = results[name]
                # Use colored dot instead of emoji
                if status.fully_available:
                    icon = _c("●", 'g')
                elif not status.feature_available:
                    icon = _c("●", 'rd')
                elif status.endpoint_available is None:
                    icon = _c("●", 'y')
                else:
                    icon = _c("●", 'y')
                
                text = _c(name, 'gr' if not status.feature_available else '')
                basic_items.append((icon, text, status))
        
        # Display in rows of 4
        for i in range(0, len(basic_items), 4):
            line_items = []
            for j in range(4):
                if i + j < len(basic_items):
                    icon, text, status = basic_items[i + j]
                    line_items.append(f"{icon} {text:<15}")
            print("     " + " ".join(line_items))
        
        # Show details if requested
        if show_endpoint_details:
            for name in basic:
                if name in results:
                    status = results[name]
                    print(f"       {_c('·', 'gr')} {name}: {_c(str(status), 'gr')}")
        
        # Advanced Features
        print()
        print(_c("  - Advanced", 'c', bold=True))
        advanced_items = []
        for name, status in results.items():
            if name not in basic:
                # Use colored dot instead of emoji
                if status.fully_available:
                    icon = _c("●", 'g')
                elif not status.feature_available:
                    icon = _c("●", 'rd')
                elif status.endpoint_available is None:
                    icon = _c("●", 'y')
                else:
                    icon = _c("●", 'y')
                
                text = _c(name, 'gr' if not status.feature_available else '')
                advanced_items.append((icon, text, status))
        
        # Display in rows of 4
        for i in range(0, len(advanced_items), 4):
            line_items = []
            for j in range(4):
                if i + j < len(advanced_items):
                    icon, text, _ = advanced_items[i + j]
                    line_items.append(f"{icon} {text:<15}")
            print("     " + " ".join(line_items))
        
        # Show details if requested
        if show_endpoint_details:
            for name, status in results.items():
                if name not in basic:
                    print(f"       {_c('·', 'gr')} {name}: {_c(str(status), 'gr')}")
        
        # Summary
        from openspace.utils.display import print_separator
        print()
        print_separator()
        print(f"  {_c('Summary:', 'c', bold=True)} {_c(str(feature_available) + '/' + str(total), 'g' if feature_available == total else 'y')} features available", end='')
        if any(s.endpoint_available is not None for s in results.values()):
            print(f", {_c(str(fully_available) + '/' + str(total), 'g' if fully_available == total else 'y')} fully functional")
        else:
            print()
        print_separator()
        
        # Legend
        print(f"  {_c('Legend:', 'gr')} {_c('●', 'g')} Available  {_c('●', 'y')} Partial/Untested  {_c('●', 'rd')} Unavailable")
        
        # Test files info
        if self.temp_files and not self.auto_cleanup:
            print()
            print(f"  {_c('Test files saved:', 'y')} {self.test_output_dir}")
            print(f"  {_c(str(len(self.temp_files)) + ' file(s) available for inspection', 'gr')}")
        
        print()
    
    def get_summary(self) -> dict:
        """Get summary"""
        if not self.results:
            return {}
        
        total = len(self.results)
        feature_available = sum(1 for s in self.results.values() if s.feature_available)
        fully_available = sum(1 for s in self.results.values() if s.fully_available)
        
        return {
            'total': total,
            'feature_available': feature_available,
            'fully_available': fully_available,
            'details': {k: str(v) for k, v in self.results.items()}
        }
    
    def get_simple_features_dict(self) -> Dict[str, bool]:
        """Get simple feature dict (for banner display)"""
        return self.feature_checker.check_all_features()
