# === platform_detector.py ===
import json
import os
import sys
import platform
import subprocess
import re
from pathlib import Path
from typing import Dict, List, Optional, Any
import logging


class PlatformDetectionError(Exception):
    """Ошибка определения платформы"""
    pass


class PlatformDetector:
    """
    Улучшенный детектор платформ с множеством fallback-методов
    Работает даже в самых экзотических окружениях
    """

    def __init__(self, logger=None):
        self.logger = logger or self._create_default_logger()
        self.detection_methods = [
            self._detect_via_os_release,
            self._detect_via_proc_version,
            self._detect_via_uname,
            self._detect_via_env_variables,
            self._detect_via_package_managers,
            self._detect_via_system_files,
            self._detect_via_python_platform,
            self._detect_via_fallback_heuristics
        ]

    def _create_default_logger(self):
        """Создать логгер по умолчанию"""
        logger = logging.getLogger('PlatformDetector')
        if not logger.handlers:
            handler = logging.StreamHandler()
            formatter = logging.Formatter('%(levelname)s: %(message)s')
            handler.setFormatter(formatter)
            logger.addHandler(handler)
            logger.setLevel(logging.WARNING)
        return logger

    def detect_platform(self) -> Dict[str, Any]:
        """
        Основной метод детекции с приоритетом методов
        Возвращает максимально подробную информацию
        """
        results = []

        for method in self.detection_methods:
            try:
                result = method()
                if result and self._validate_platform_result(result):
                    results.append(result)
                    self.logger.debug(f"Method {method.__name__} succeeded: {result}")
            except Exception as e:
                self.logger.debug(f"Method {method.__name__} failed: {e}")
                continue

        if not results:
            raise PlatformDetectionError("All detection methods failed")

        # Объединяем результаты с приоритетом первых методов
        final_result = self._merge_results(results)
        return final_result

    def _validate_platform_result(self, result: Dict) -> bool:
        """Валидация результата детекции"""
        required_keys = ['os_family', 'os_name']
        return all(key in result for key in required_keys)

    def _merge_results(self, results: List[Dict]) -> Dict:
        """Объединение результатов с приоритетом"""
        merged = {}

        for result in reversed(results):  # Последние имеют приоритет (более точные)
            merged.update(result)

        # Обязательные поля, если они не были обнаружены
        merged.setdefault('os_family', 'unknown')
        merged.setdefault('os_name', 'unknown')
        merged.setdefault('os_version', 'unknown')
        merged.setdefault('os_arch', platform.machine().lower())
        merged.setdefault('environment', 'native')

        return merged

    # === МЕТОДЫ ДЕТЕКЦИИ (от самых точных к самым общим) ===

    def _detect_via_os_release(self) -> Optional[Dict]:
        """Детекция через /etc/os-release (самый надежный для Linux)"""
        os_release_files = [
            '/etc/os-release',
            '/usr/lib/os-release',
            '/etc/lsb-release',
            '/etc/redhat-release',
            '/etc/debian_version'
        ]

        for file_path in os_release_files:
            if os.path.exists(file_path):
                try:
                    with open(file_path, 'r', encoding='utf-8') as f:
                        content = f.read()

                    result = {'detection_method': 'os_release'}

                    # Парсим os-release формат
                    if 'os-release' in file_path:
                        for line in content.splitlines():
                            if '=' in line:
                                key, value = line.split('=', 1)
                                key = key.strip()
                                value = value.strip().strip('"')

                                if key == 'ID':
                                    result['os_name'] = value.lower()
                                elif key == 'ID_LIKE':
                                    result['os_family'] = value.lower().split()[0]
                                elif key == 'VERSION_ID':
                                    result['os_version'] = value

                    # Fallback для специфичных файлов
                    if 'os_name' not in result:
                        if 'redhat-release' in file_path:
                            result.update({'os_name': 'rhel', 'os_family': 'rhel'})
                        elif 'debian_version' in file_path:
                            result.update({'os_name': 'debian', 'os_family': 'debian'})

                    return result

                except (IOError, UnicodeDecodeError) as e:
                    self.logger.debug(f"Failed to read {file_path}: {e}")
                    continue

        return None

    def _detect_via_proc_version(self) -> Optional[Dict]:
        """Детекция через /proc/version (работает даже в минимальных окружениях)"""
        proc_version = '/proc/version'

        if os.path.exists(proc_version):
            try:
                with open(proc_version, 'r', encoding='utf-8') as f:
                    content = f.read().lower()

                result = {'detection_method': 'proc_version'}

                # Анализ содержимого
                if 'ubuntu' in content:
                    result.update({'os_name': 'ubuntu', 'os_family': 'debian'})
                elif 'debian' in content:
                    result.update({'os_name': 'debian', 'os_family': 'debian'})
                elif 'arch' in content:
                    result.update({'os_name': 'arch', 'os_family': 'arch'})
                elif 'alpine' in content:
                    result.update({'os_name': 'alpine', 'os_family': 'alpine'})

                return result if 'os_name' in result else None

            except IOError as e:
                self.logger.debug(f"Failed to read {proc_version}: {e}")

        return None

    def _detect_via_uname(self) -> Optional[Dict]:
        """Детекция через uname команду"""
        try:
            # Пытаемся выполнить uname
            result = subprocess.run(
                ['uname', '-srmo'],
                capture_output=True,
                text=True,
                timeout=5
            )

            if result.returncode == 0:
                uname_output = result.stdout.lower()
                info = {'detection_method': 'uname'}

                # Парсим вывод uname
                if 'linux' in uname_output:
                    info['os_family'] = 'linux'

                    # Детекция специфичных окружений
                    if 'android' in uname_output or 'aarch64' in uname_output and 'linux' in uname_output:
                        info['os_name'] = 'android'
                        info['environment'] = 'termux' if 'TERMUX' in os.environ else 'android'
                    else:
                        info['os_name'] = 'linux'

                elif 'darwin' in uname_output:
                    info.update({'os_family': 'darwin', 'os_name': 'macos'})

                elif 'bsd' in uname_output:
                    info.update({'os_family': 'bsd', 'os_name': 'bsd'})

                return info

        except (subprocess.TimeoutExpired, subprocess.SubprocessError, FileNotFoundError) as e:
            self.logger.debug(f"uname detection failed: {e}")

        return None

    def _detect_via_env_variables(self) -> Optional[Dict]:
        """Детекция через переменные окружения"""
        env_info = {'detection_method': 'env_variables'}

        # Termux detection
        if 'TERMUX_VERSION' in os.environ:
            env_info.update({
                'os_family': 'linux',
                'os_name': 'android',
                'environment': 'termux',
                'os_version': os.environ.get('TERMUX_VERSION', 'unknown')
            })
            return env_info

        # WSL detection
        if 'WSL_DISTRO_NAME' in os.environ:
            env_info.update({
                'os_family': 'linux',
                'os_name': os.environ.get('WSL_DISTRO_NAME', 'wsl'),
                'environment': 'wsl'
            })
            return env_info

        # Docker container detection
        if os.path.exists('/.dockerenv'):
            env_info['environment'] = 'docker'

        # Kubernetes detection
        if 'KUBERNETES_SERVICE_HOST' in os.environ:
            env_info['environment'] = 'kubernetes'

        # Android detection (без Termux)
        if 'ANDROID_ROOT' in os.environ:
            env_info.update({
                'os_family': 'linux',
                'os_name': 'android',
                'environment': 'android'
            })
            return env_info

        return env_info if env_info.get('environment') != 'native' else None

    def _detect_via_package_managers(self) -> Optional[Dict]:
        """Детекция через наличие менеджеров пакетов"""
        package_managers = {
            'apt': {'os_family': 'debian', 'os_name': 'debian'},
            'dpkg': {'os_family': 'debian', 'os_name': 'debian'},
            'yum': {'os_family': 'rhel', 'os_name': 'centos'},
            'dnf': {'os_family': 'rhel', 'os_name': 'fedora'},
            'pacman': {'os_family': 'arch', 'os_name': 'arch'},
            'apk': {'os_family': 'alpine', 'os_name': 'alpine'},
            'zypper': {'os_family': 'suse', 'os_name': 'opensuse'},
            'opkg': {'os_family': 'openwrt', 'os_name': 'openwrt'},  # Embedded
            'ipkg': {'os_family': 'embedded', 'os_name': 'embedded'},  # Старые embedded
        }

        for pm, info in package_managers.items():
            if self._check_command_exists(pm):
                result = info.copy()
                result['detection_method'] = 'package_manager'
                result['package_manager'] = pm
                return result

        return None

    def _detect_via_system_files(self) -> Optional[Dict]:
        """Детекция через системные файлы"""
        system_files = {
            '/system/bin/toolbox': {'os_family': 'linux', 'os_name': 'android'},
            '/system/bin/toybox': {'os_family': 'linux', 'os_name': 'android'},
            '/proc/device-tree/model': {'os_family': 'linux', 'os_name': 'embedded'},  # Embedded device tree
            '/etc/openwrt_release': {'os_family': 'openwrt', 'os_name': 'openwrt'},
            '/etc/busybox-release': {'os_family': 'embedded', 'os_name': 'busybox'},
        }

        for file_path, info in system_files.items():
            if os.path.exists(file_path):
                result = info.copy()
                result['detection_method'] = 'system_files'
                return result

        return None

    def _detect_via_python_platform(self) -> Dict:
        """Детекция через стандартный модуль platform (fallback)"""
        result = {
            'detection_method': 'python_platform',
            'os_family': platform.system().lower(),
            'os_name': platform.system().lower(),
            'os_version': platform.release(),
            'os_arch': platform.machine().lower()
        }

        # Уточнение для Linux
        if result['os_family'] == 'linux':
            result['os_name'] = 'linux'  # Будет уточнено другими методами

        return result

    def _detect_via_fallback_heuristics(self) -> Dict:
        """Эвристики как последнее средство"""
        result = {'detection_method': 'heuristics'}

        # Проверяем, что это вообще Linux-like система
        if os.path.exists('/proc') and os.path.exists('/sys'):
            result.update({'os_family': 'linux', 'os_name': 'linux'})

            # Эвристики для embedded
            if os.path.exists('/proc/cpuinfo'):
                try:
                    with open('/proc/cpuinfo', 'r') as f:
                        cpuinfo = f.read()

                    # ARM устройства - вероятно embedded
                    if 'arm' in cpuinfo.lower() or 'mips' in cpuinfo.lower():
                        result['environment'] = 'embedded'
                        result['os_name'] = 'embedded_linux'
                except IOError:
                    pass

        else:
            # Если не Linux-like, пробуем определить через sys.platform
            platform_name = sys.platform.lower()
            if platform_name.startswith('win'):
                result.update({'os_family': 'windows', 'os_name': 'windows'})
            elif platform_name.startswith('darwin'):
                result.update({'os_family': 'darwin', 'os_name': 'macos'})
            else:
                result.update({'os_family': 'unknown', 'os_name': 'unknown'})

        return result

    def _check_command_exists(self, command: str) -> bool:
        """Проверить существование команды в системе"""
        try:
            subprocess.run(
                [command, '--version'] if command != 'dpkg' else ['dpkg', '--version'],
                capture_output=True,
                timeout=2
            )
            return True
        except (subprocess.TimeoutExpired, subprocess.SubprocessError, FileNotFoundError):
            return False

    def get_detailed_info(self) -> Dict[str, Any]:
        """Получить максимально подробную информацию о платформе"""
        basic_info = self.detect_platform()

        # Дополнительная детализация
        detailed_info = basic_info.copy()

        # Информация о памяти (для embedded важно)
        try:
            if os.path.exists('/proc/meminfo'):
                with open('/proc/meminfo', 'r') as f:
                    meminfo = f.read()
                # Простая проверка на маленькую память (embedded)
                if 'MemTotal:' in meminfo:
                    mem_match = re.search(r'MemTotal:\s+(\d+)\s+kB', meminfo)
                    if mem_match:
                        mem_kb = int(mem_match.group(1))
                        detailed_info['memory_total_kb'] = mem_kb
                        if mem_kb < 256 * 1024:  # Меньше 256MB
                            detailed_info['environment'] = 'embedded'
        except IOError:
            pass

        # Информация о процессоре
        try:
            if os.path.exists('/proc/cpuinfo'):
                with open('/proc/cpuinfo', 'r') as f:
                    cpuinfo = f.read()

                processor_count = cpuinfo.count('processor\t:')
                detailed_info['cpu_cores'] = processor_count if processor_count > 0 else 1

                # Определение архитектуры более точно
                if 'ARM' in cpuinfo or 'arm' in cpuinfo:
                    detailed_info['cpu_arch_family'] = 'arm'
                elif 'MIPS' in cpuinfo:
                    detailed_info['cpu_arch_family'] = 'mips'
                elif 'x86' in cpuinfo.lower():
                    detailed_info['cpu_arch_family'] = 'x86'
        except IOError:
            pass

        # Проверка на виртуализацию/контейнеризацию
        detailed_info['virtualization'] = self._detect_virtualization()

        return detailed_info

    def _detect_virtualization(self) -> str:
        """Определить тип виртуализации"""
        virtualization_indicators = {
            'docker': '/.dockerenv',
            'lxc': '/proc/1/environ',  # LXC имеет особые признаки
            'wsl': '/mnt/c/Windows',  # WSL имеет доступ к Windows файлам
            'chroot': '/proc/1/root',  # Проверка chroot
            'qemu': '/proc/device-tree/model',  # QEMU эмуляция
        }

        for vtype, indicator in virtualization_indicators.items():
            if os.path.exists(indicator):
                return vtype

        # Проверка через /proc/cpuinfo для гипервизоров
        try:
            if os.path.exists('/proc/cpuinfo'):
                with open('/proc/cpuinfo', 'r') as f:
                    content = f.read()

                if 'hypervisor' in content.lower():
                    return 'hypervisor'
        except IOError:
            pass

        return 'native'


# === УТИЛИТЫ ДЛЯ РАБОТЫ С РЕЗУЛЬТАТАМИ ДЕТЕКЦИИ ===

class PlatformProfileMatcher:
    """Сопоставление обнаруженной платформы с профилями OSAF"""

    def __init__(self, profiles_dir: str):
        self.profiles_dir = Path(profiles_dir)
        self.profiles = self._load_profiles()

    def _load_profiles(self) -> List[Dict]:
        """Загрузка профилей из JSON файлов"""
        profiles = []

        if not self.profiles_dir.exists():
            return profiles

        for json_file in self.profiles_dir.glob('*.json'):
            try:
                with open(json_file, 'r', encoding='utf-8') as f:
                    profile = json.load(f)
                    profiles.append(profile)
            except (json.JSONDecodeError, IOError) as e:
                logging.warning(f"Failed to load profile {json_file}: {e}")
                continue

        return profiles

    def find_best_match(self, platform_info: Dict) -> Optional[Dict]:
        """Найти наилучший matching профиль"""
        best_match = None
        best_score = 0

        for profile in self.profiles:
            score = self._calculate_match_score(profile, platform_info)
            if score > best_score:
                best_score = score
                best_match = profile

        return best_match if best_score > 0 else None

    def _calculate_match_score(self, profile: Dict, platform_info: Dict) -> int:
        """Вычислить score совпадения профиля с платформой"""
        score = 0

        # Совпадение по os_name - самый высокий приоритет
        if profile.get('os_name') == platform_info.get('os_name'):
            score += 10

        # Совпадение по os_family
        if profile.get('os_family') == platform_info.get('os_family'):
            score += 5

        # Совпадение по окружению (embedded, docker, etc.)
        if profile.get('environment') == platform_info.get('environment'):
            score += 3

        # Совпадение по менеджеру пакетов
        if profile.get('package_manager') == platform_info.get('package_manager'):
            score += 2

        # Дополнительные условия из профиля
        conditions = profile.get('match_conditions', {})
        for key, expected_value in conditions.items():
            if platform_info.get(key) == expected_value:
                score += 1

        return score


class EnhancedPlatformDetection:
    """Улучшенная система детекции платформ для OSAF"""

    def __init__(self, osa, common_data_dir):
        self.osa = osa
        self.detector = PlatformDetector(osa.log)
        self.matcher = PlatformProfileMatcher(f"{common_data_dir}/platform-profiles/")
        self.platform_info = None

    def detect(self):
        """Основной метод детекции"""
        try:
            # Детекция платформы
            self.platform_info = self.detector.get_detailed_info()

            # Поиск подходящего профиля
            profile = self.matcher.find_best_match(self.platform_info)

            if profile:
                self.platform_info['profile'] = profile
                self.osa.log.d(f"Matched platform profile: {profile.get('id', 'unknown')}")
            else:
                self.osa.log.w("No matching platform profile found, using defaults")
                self.platform_info['profile'] = self._create_default_profile()

            self.osa.log.d(f"Platform detection result: {self.platform_info}")
            return self.platform_info

        except Exception as e:
            self.osa.log.e(f"Platform detection failed: {e}")
            # Fallback на минимальную информацию
            return self._create_fallback_info()

    def _create_default_profile(self) -> Dict:
        """Создать профиль по умолчанию на основе обнаруженной информации"""
        return {
            'id': 'generic',
            'name': 'Generic System',
            'package_manager': self.platform_info.get('package_manager', 'unknown'),
            'file_operations': 'posix' if self.platform_info.get('os_family') != 'windows' else 'windows',
            'process_management': 'posix' if self.platform_info.get('os_family') != 'windows' else 'windows',
            'capabilities': self._determine_capabilities()
        }

    def _create_fallback_info(self) -> Dict:
        """Создать fallback информацию при полном сбое детекции"""
        return {
            'os_family': 'unknown',
            'os_name': 'unknown',
            'os_version': 'unknown',
            'os_arch': platform.machine().lower(),
            'environment': 'unknown',
            'profile': self._create_default_profile(),
            'detection_method': 'fallback'
        }

    def _determine_capabilities(self) -> List[str]:
        """Определить возможности системы на основе обнаруженной информации"""
        capabilities = []

        # Проверка доступности различных функций
        checks = [
            ('sudo', 'privilege_escalation'),
            ('systemctl', 'systemd_services'),
            ('docker', 'container_runtime'),
            ('python3', 'python3'),
            ('gcc', 'compilation'),
        ]

        for cmd, capability in checks:
            if self.detector._check_command_exists(cmd):
                capabilities.append(capability)

        # Специальные возможности для embedded
        if self.platform_info.get('environment') == 'embedded':
            capabilities.extend(['limited_resources', 'embedded_optimized'])

        # Возможности для контейнеров
        if self.platform_info.get('environment') in ['docker', 'kubernetes']:
            capabilities.extend(['containerized', 'ephemeral_storage'])

        return capabilities