"""Environment capture for the autodebug probe.

Captures environment information including Python version, installed
packages, environment variables, and detected frameworks.
"""

from __future__ import annotations

import os
import sys
from dataclasses import dataclass, field
from typing import Dict, List, Optional


@dataclass
class EnvironmentRecord:
    """Captures environment context at test start."""

    python_version: str
    python_path: List[str]
    installed_packages: Dict[str, str]
    env_vars: Dict[str, str]
    platform: str
    hostname: str
    working_directory: str
    detected_frameworks: List[str] = field(default_factory=list)
    config_files: List[str] = field(default_factory=list)
    cpu_count: Optional[int] = None
    memory_mb: Optional[int] = None

    def to_dict(self) -> dict:
        """Convert to dictionary for serialization."""
        return {
            "python_version": self.python_version,
            "python_path": self.python_path,
            "installed_packages": self.installed_packages,
            "env_vars": self.env_vars,
            "platform": self.platform,
            "hostname": self.hostname,
            "working_directory": self.working_directory,
            "detected_frameworks": self.detected_frameworks,
            "config_files": self.config_files,
            "cpu_count": self.cpu_count,
            "memory_mb": self.memory_mb,
        }


class EnvironmentCapture:
    """Capture environment information.

    Collects information about the Python environment, installed
    packages, environment variables, and detected frameworks.
    """

    # Patterns for sensitive environment variables
    SENSITIVE_VAR_PATTERNS = [
        "password",
        "secret",
        "key",
        "token",
        "credential",
        "api_key",
        "apikey",
        "auth",
        "private",
        "cert",
        "passphrase",
    ]

    # Known web frameworks to detect
    FRAMEWORK_MODULES = {
        "django": "django",
        "flask": "flask",
        "fastapi": "fastapi",
        "starlette": "starlette",
        "tornado": "tornado",
        "aiohttp": "aiohttp",
        "sanic": "sanic",
        "pyramid": "pyramid",
        "bottle": "bottle",
        "falcon": "falcon",
        "quart": "quart",
    }

    # Config file patterns to look for
    CONFIG_PATTERNS = [
        "pyproject.toml",
        "setup.py",
        "setup.cfg",
        ".env",
        ".env.local",
        ".env.test",
        ".env.development",
        ".env.production",
        "config.py",
        "settings.py",
        "config.yaml",
        "config.yml",
        "config.json",
        "pytest.ini",
        "tox.ini",
        "Makefile",
        "docker-compose.yml",
        "docker-compose.yaml",
        "Dockerfile",
    ]

    def capture(self) -> EnvironmentRecord:
        """Capture current environment.

        Returns:
            EnvironmentRecord with environment information
        """
        return EnvironmentRecord(
            python_version=sys.version,
            python_path=sys.path.copy(),
            installed_packages=self._get_installed_packages(),
            env_vars=self._get_redacted_env(),
            platform=sys.platform,
            hostname=self._get_hostname(),
            working_directory=os.getcwd(),
            detected_frameworks=self._detect_frameworks(),
            config_files=self._find_config_files(),
            cpu_count=self._get_cpu_count(),
            memory_mb=self._get_memory_mb(),
        )

    def _get_installed_packages(self) -> Dict[str, str]:
        """Get installed package versions.

        Returns:
            Dict mapping package names to versions
        """
        try:
            # Use importlib.metadata (Python 3.8+)
            from importlib.metadata import distributions

            return {
                d.metadata["Name"]: d.metadata["Version"]
                for d in distributions()
                if d.metadata.get("Name")
            }
        except ImportError:
            pass

        try:
            # Fallback to pkg_resources
            import pkg_resources

            return {d.project_name: d.version for d in pkg_resources.working_set}
        except ImportError:
            pass

        return {}

    def _get_redacted_env(self) -> Dict[str, str]:
        """Get environment variables with sensitive values redacted.

        Returns:
            Dict of environment variables
        """
        redacted = {}

        for key, value in os.environ.items():
            # Check if key contains sensitive pattern
            key_lower = key.lower()
            is_sensitive = any(p in key_lower for p in self.SENSITIVE_VAR_PATTERNS)

            if is_sensitive:
                redacted[key] = f"[REDACTED - {len(value)} chars]"
            elif len(value) > 200:
                redacted[key] = value[:200] + "...[truncated]"
            else:
                redacted[key] = value

        return redacted

    def _get_hostname(self) -> str:
        """Get the hostname.

        Returns:
            Hostname string
        """
        try:
            if hasattr(os, "uname"):
                return os.uname().nodename
            import socket

            return socket.gethostname()
        except Exception:
            return "unknown"

    def _detect_frameworks(self) -> List[str]:
        """Detect installed web frameworks.

        Returns:
            List of detected framework names
        """
        frameworks = []

        for name, module in self.FRAMEWORK_MODULES.items():
            try:
                __import__(module)
                frameworks.append(name)
            except ImportError:
                pass

        return frameworks

    def _find_config_files(self) -> List[str]:
        """Find configuration files in working directory.

        Returns:
            List of found config file names
        """
        found = []
        cwd = os.getcwd()

        for pattern in self.CONFIG_PATTERNS:
            path = os.path.join(cwd, pattern)
            if os.path.exists(path):
                found.append(pattern)

        return found

    def _get_cpu_count(self) -> Optional[int]:
        """Get CPU count.

        Returns:
            Number of CPUs or None
        """
        try:
            return os.cpu_count()
        except Exception:
            return None

    def _get_memory_mb(self) -> Optional[int]:
        """Get total memory in MB.

        Returns:
            Memory in MB or None
        """
        try:
            # Try psutil first
            import psutil

            return psutil.virtual_memory().total // (1024 * 1024)
        except ImportError:
            pass

        try:
            # Try /proc/meminfo on Linux
            if sys.platform.startswith("linux"):
                with open("/proc/meminfo", "r") as f:
                    for line in f:
                        if line.startswith("MemTotal:"):
                            parts = line.split()
                            return int(parts[1]) // 1024  # Convert KB to MB
        except Exception:
            pass

        return None


def capture_environment() -> EnvironmentRecord:
    """Convenience function to capture environment.

    Returns:
        EnvironmentRecord with current environment
    """
    return EnvironmentCapture().capture()


def get_package_version(package_name: str) -> Optional[str]:
    """Get the version of a specific package.

    Args:
        package_name: Name of the package

    Returns:
        Version string or None if not installed
    """
    try:
        from importlib.metadata import version

        return version(package_name)
    except ImportError:
        pass

    try:
        import pkg_resources

        return pkg_resources.get_distribution(package_name).version
    except Exception:
        pass

    return None


def get_framework_version() -> Optional[Dict[str, str]]:
    """Get versions of detected frameworks.

    Returns:
        Dict mapping framework name to version
    """
    capture = EnvironmentCapture()
    frameworks = capture._detect_frameworks()

    versions = {}
    for framework in frameworks:
        version = get_package_version(framework)
        if version:
            versions[framework] = version

    return versions if versions else None
