from pathlib import Path
from conan import ConanFile
from conan.tools.files import get, copy, rm, rmdir, download
from conan.errors import ConanInvalidConfiguration

class Ros2Python(ConanFile):
    name = "ros2-jazzy-python"
    pyversion = "3.12.3"
    package_type = "application"
    
    # Metadata
    license = "Apache-2.0"
    author = "Your Name <your.email@example.com>"
    url = "https://github.com/yourorg/ros2-jazzy-python"
    description = "Prebuilt ROS 2 Jazzy Python interpreter with cross-compilation support."
    topics = ("ros2", "jazzy", "python", "toolchain")

    settings = "os", "arch"
    
    # We use a custom option to trigger the 'Frankenstein' mode.
    # In pure Conan 2 profiles, this is often detected by settings_build vs settings,
    # but for a pre-built binary repackage, an explicit option is safer/clearer.
    options = {
        "cross_blob": [True, False], 
    }
    default_options = {
        "cross_blob": False,
    }

    # Internal helpers for path management
    _tc_folder = "base_source"
    _cross_folder = "target_source"

    def validate(self):
        if self.settings.os != "Linux":
            raise ConanInvalidConfiguration("This recipe currently supports only Linux.")
        if self.options.cross_blob:
            if self.settings.arch == self.settings_build.arch:
                raise ConanInvalidConfiguration("This only applies for build host x64 and target armv8")

    @property
    def _python_short_ver(self):
        return ".".join(self.pyversion.split(".")[:2])  # "3.12"

    @property
    def _arch_map(self):
        return {
            "x86_64": "x86_64",
            "armv8": "aarch64",
            "aarch64": "aarch64"
        }

    def build(self):
        # 1. Identify Architectures
        # 'build_arch' is what runs the binary (Native)
        # 'host_arch' is what we link against (Target)
        
        build_arch = str(self.settings.arch)
        if self.options.cross_blob and self.settings_build:
            build_arch = str(self.settings_build.arch)

        host_arch = str(self.settings.arch)

        build_key = self._arch_map.get(build_arch, build_arch)
        host_key = self._arch_map.get(host_arch, host_arch)

        self.output.info(f"Building Hybrid Python: Runtime={build_key}, Linking={host_key}")

        # 2. Fetch Runtime (Base)
        sources = self.conan_data.get("sources", {}).get(self.version, {})
        if build_key not in sources:
            raise ConanInvalidConfiguration(f"Source not found for runtime arch: {build_key}")

        self.output.info(f"Downloading Runtime ({build_key})...")
        src_info = sources[build_key]
        # Filter out 'cross_compile' key if present to avoid passing it to get()
        # get_args = {k: v for k, v in src_info.items() if k != "cross_compile"}
        if not self.options.cross_blob:
            get_args = self.conan_data["sources"][str(self.version)][build_key]["shared"]
            get(self, **get_args, destination=self._tc_folder, strip_root=True)
        else:
            get_args = self.conan_data["sources"][str(self.version)][build_key]["static"]
            get(self, **get_args, destination=self._tc_folder, strip_root=True)

        # 3. Fetch Overlay (Target) if needed
        if self.options.cross_blob:
            # Logic: We need the TARGET artifacts. 
            # In your YAML, the 'cross_compile' entry under x86_64 IS the aarch64 tarball.
            # However, robust logic should just fetch the aarch64 entry directly.

            # target_info = sources.get(host_key)
            target_info = src_info.get("cross_compile")
            if not target_info:
                raise ConanInvalidConfiguration(f"Source not found for target arch: {host_key}")

            self.output.info(f"Downloading Target Overlay ({host_key})...")
            get_args_cross = {k: v for k, v in target_info.items() if k != "cross_compile"}
            get(self, **get_args_cross, destination=self._cross_folder, strip_root=True)

    def package(self):
        base_src = Path(self.build_folder) / self._tc_folder
        
        # 1. Install Base (Runtime)
        # We copy everything initially. This ensures bin/python and lib-dynload/ work.
        copy(self, "*", src=base_src, dst=self.package_folder)

        # 2. Overlay Target Artifacts (If Cross)
        if self.options.cross_blob:
            self.output.highlight("Applying Cross-Compilation Overlay...")
            cross_src = Path(self.build_folder) / self._cross_folder
            
            # A. Static Library (libpython3.12.a)
            # Remove base static lib to avoid confusion
            rm(self, f"libpython{self._python_short_ver}.a", str(Path(self.package_folder) / "lib"))
            
            build_arch = str(self.settings_build.arch)
            rmdir(self, Path(self.package_folder) / "lib" /  f"python{self._python_short_ver}" / f"config-{self._python_short_ver}-{self._arch_map.get(build_arch, build_arch)}-linux-gnu" )
            
            host_arch = str(self.settings.arch)
            copy(self, "*", src=cross_src / "lib" / f"python{self._python_short_ver}" / f"config-{self._python_short_ver}-{self._arch_map.get(host_arch, host_arch)}-linux-gnu", dst=Path(self.package_folder) / "lib" )
            # Copy target static and shared libs
            copy(self, f"libpython{self._python_short_ver}*",
                src=cross_src / "lib", 
                dst=Path(self.package_folder) / "lib")

            # B. Headers (pyconfig.h)
            # pyconfig.h is vital for definition of types (size_t, endianness)
            # It usually lives in include/python3.12/
            copy(self, "pyconfig.h", 
                src=cross_src / "include" / f"python{self._python_short_ver}", 
                dst=Path(self.package_folder) / "include" / f"python{self._python_short_ver}")

            # C. Sysconfig Data & Makefile
            # These live in lib/python3.12/
            # We must swap _sysconfigdata_*.py so that 'python -m sysconfig' reports TARGET flags.
            # We must NOT swap the whole folder, or we lose lib-dynload.
            
            lib_root_dst = Path(self.package_folder) / "lib" / f"python{self._python_short_ver}"
            lib_root_src = cross_src / "lib" / f"python{self._python_short_ver}"

            # Copy _sysconfigdata (usually has a suffix like _sysconfigdata_m_linux_x86_64-linux-gnu.py)
            # We copy ALL sysconfig data from target and delete base ones if names differ.
            copy(self, "_sysconfigdata_*.py", src=lib_root_src, dst=lib_root_dst)
            
            # Copy the config folder (contains Makefile and pkgconfig)
            # Pattern: config-3.12-<arch>-linux-gnu
            # We simply copy the target config folder over.
            copy(self, "config-*", src=lib_root_src, dst=lib_root_dst)

            # D. pkg-config files
            # Usually in lib/pkgconfig. Need to ensure they point to the .a file we just copied.
            copy(self, "*.pc", src=cross_src / "lib" / "pkgconfig", dst=Path(self.package_folder) / "lib" / "pkgconfig")

    def package_info(self):
        self.cpp_info.bindirs = ["bin"]
        self.cpp_info.libdirs = ["lib"]
        self.cpp_info.includedirs = ["include"]

        bindir = Path(self.package_folder) / "bin"
        python_exec = str(bindir / "python3")
        
        # 1. Environment Variables
        self.runenv_info.prepend_path("PATH", str(bindir))
        self.runenv_info.define("PYTHONHOME", self.package_folder)
        self.runenv_info.define("PYTHON", python_exec)

        self.buildenv_info.prepend_path("PATH", str(bindir))
        self.buildenv_info.define("PYTHONHOME", self.package_folder)
        self.buildenv_info.define("PYTHON", python_exec)

        # 2. Toolchain/CMake variables
        # Downstream recipes using this as a tool_requires can read this
        self.conf_info.define("user.ros2:python_interpreter", python_exec)
        
        # Helper to let consumers know this is a cross-build python
        if self.options.cross_blob:
            self.conf_info.define("user.ros2:cross_python", "True")

