#
# SPDX-FileCopyrightText: Copyright (c) 1993-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

"""Dependency management for TensorRT diffusion demos.

Adds the right group's site-packages to sys.path so imports work.
"""

import os
import sys

# Marker file to indicate successful installation
# Must match the marker file used by setup.py
INSTALL_COMPLETE_MARKER = ".install_complete"

# Descriptions for user-facing messages
GROUP_DESCRIPTIONS = {
    "sd": "SD family (SD 1.4, SDXL, SD3, SD3.5, SVD, Stable Cascade)",
    "flux": "Flux family (Black Forest Labs)",
    "cosmos": "Cosmos family (NVIDIA), Wan2.2 T2V",
}

# Valid dependency groups
VALID_GROUPS = list(GROUP_DESCRIPTIONS.keys())

__all__ = [
    "configure",
    "get_configured_groups",
    "print_status",
]


def _resolve_deps_root(deps_root: str | None) -> str:
    """Resolve the dependency root path with precedence: arg > env > default."""
    if deps_root is not None:
        return deps_root
    return os.environ.get("TENSORRT_DIFFUSION_DEPS_ROOT", "/workspace/deps")

def _prepend_env_path(var: str, path: str, clean_root: str | None = None):
    """Prepend `path` to an os.pathsep-separated env var so child processes
    (e.g. the `polygraphy` CLI) inherit the group's dependencies. If
    `clean_root` is given, drop existing entries under it first."""
    def _norm(p: str) -> str:
        return os.path.abspath(os.path.expanduser(p))

    existing = [p for p in os.environ.get(var, "").split(os.pathsep) if p]
    if clean_root is not None:
        root_abs = _norm(clean_root).rstrip(os.sep) + os.sep
        existing = [p for p in existing if not _norm(p).startswith(root_abs)]
    existing = [p for p in existing if _norm(p) != _norm(path)]
    os.environ[var] = os.pathsep.join([path, *existing])


def _clean_diffusion_paths(deps_root: str = "/workspace/deps"):
    """Drop any existing paths under deps_root from sys.path."""
    # Filter out any paths that live under deps_root (robust to path forms)
    root_abs = os.path.abspath(os.path.expanduser(deps_root)).rstrip(os.sep) + os.sep
    sys.path[:] = [
        p for p in sys.path
        if not os.path.abspath(os.path.expanduser(p)).startswith(root_abs)
    ]


def configure(
    group: str,
    deps_root: str | None = None,
    verbose: bool = False,
    clean: bool = True,
    fallback: bool = False
):
    """
    Configure sys.path to use dependencies from the specified group.

    This function should be called at the top of each demo script, before
    any other imports that depend on external packages.

    Args:
        group: Dependency group name ("sd", "flux", or "cosmos")
        deps_root: Root directory where dependencies are installed.
                   If None, uses TENSORRT_DIFFUSION_DEPS_ROOT environment variable,
                   or defaults to "/workspace/deps"
        verbose: Print configuration info (default: False)
        clean: Remove other dependency group paths first (default: True)
               This prevents sys.path inflation in long-running processes.
        fallback: If True, gracefully handle missing dependencies instead of raising
                 RuntimeError. Useful for test environments using requirements.txt.
                 If False but USE_REQUIREMENTS=1 is set,
                 fallback will be automatically enabled.

    Example:
        from demo_diffusion import deps
        deps.configure("flux")

        # Or with custom location
        deps.configure("flux", deps_root="/custom/path/deps")

        # Or via environment variable
        # export TENSORRT_DIFFUSION_DEPS_ROOT=/custom/path/deps
        # deps.configure("flux")

        # For test environments using requirements.txt (installed via setup.sh)
        # export USE_REQUIREMENTS=1
        # deps.configure("sd")  # Will automatically use fallback mode
    """
    if group not in VALID_GROUPS:
        raise ValueError(
            f"Invalid dependency group: '{group}'\n"
            f"Valid groups: {', '.join(sorted(VALID_GROUPS))}"
        )

    # Auto-enable fallback mode if using requirements.txt installation
    if not fallback and os.environ.get("USE_REQUIREMENTS") == "1":
        fallback = True
        if verbose:
            print("Detected USE_REQUIREMENTS=1, enabling fallback mode")

    # Resolve deps_root consistently across the module
    deps_root = _resolve_deps_root(deps_root)

    # Clean old dependency paths first (prevents inflation)
    if clean:
        _clean_diffusion_paths(deps_root)

    # Determine Python version
    python_version = f"{sys.version_info.major}.{sys.version_info.minor}"

    # Construct path to site-packages for this group
    deps_path = os.path.join(
        deps_root,
        group,
        "lib",
        f"python{python_version}",
        "site-packages"
    )

    # Check if installation is complete
    group_dir = os.path.join(deps_root, group)
    marker_file = os.path.join(group_dir, INSTALL_COMPLETE_MARKER)

    if os.path.exists(deps_path) and os.path.exists(marker_file):
        # Insert at the beginning to override any system packages
        sys.path.insert(0, deps_path)

        # Mirror onto the environment so child processes (e.g. the polygraphy
        # CLI that engine builds shell out to) use these deps too. When clean,
        # drop other groups' entries under deps_root, matching sys.path above.
        clean_root = deps_root if clean else None
        _prepend_env_path("PYTHONPATH", deps_path, clean_root=clean_root)
        _prepend_env_path("PATH", os.path.join(group_dir, "bin"), clean_root=clean_root)

        if verbose:
            description = GROUP_DESCRIPTIONS.get(group, group)
            print(f"Configured dependencies: {description}")
            print(f"  Path: {deps_path}")
    else:
        # Dependencies not found or installation incomplete
        description = GROUP_DESCRIPTIONS.get(group, group)

        # Check if it's an incomplete installation
        if os.path.exists(group_dir) and not os.path.exists(marker_file):
            error_msg = (
                f"Dependencies for '{group}' are incomplete!\n"
                f"    Location: {group_dir}\n"
                f"    Description: {description}\n"
                f"    A previous installation failed or was interrupted.\n"
                f"    To fix, run:\n"
                f"      python setup.py {group}\n"
                f"    (This will clean up and reinstall)"
            )
            if fallback:
                if verbose:
                    print(f"Warning: {error_msg}")
                    print("Continuing with fallback mode...")
                return
            else:
                raise RuntimeError(error_msg)
        else:
            # Not installed at all
            error_msg = (
                f"Dependencies for '{group}' not found!\n"
                f"    Expected at: {deps_path}\n"
                f"    Description: {description}\n"
                f"    To install, run: python setup.py {group}\n"
                f"    If you installed elsewhere, set TENSORRT_DIFFUSION_DEPS_ROOT or pass deps_root."
            )
            if fallback:
                if verbose:
                    print(f"Warning: {error_msg}")
                    print("Continuing with fallback mode (assuming requirements.txt setup)...")
                return
            else:
                raise RuntimeError(error_msg)


def get_configured_groups(deps_root: str | None = None) -> list[str]:
    """
    Get list of dependency groups that are currently installed.

    A group is considered installed only if both:
    1. The group directory exists
    2. The .install_complete marker file exists in that directory

    Args:
        deps_root: Root directory where dependencies are installed.
                   If None, uses TENSORRT_DIFFUSION_DEPS_ROOT environment variable,
                   or defaults to "/workspace/deps"

    Returns:
        List of installed group names (e.g., ["sd", "flux"])
    """
    # Resolve deps_root
    deps_root = _resolve_deps_root(deps_root)

    installed = []
    for group in VALID_GROUPS:
        # Check if the group directory exists and has the completion marker
        group_dir = os.path.join(deps_root, group)
        marker_file = os.path.join(group_dir, INSTALL_COMPLETE_MARKER)

        # Only consider it installed if marker file exists
        if os.path.exists(group_dir) and os.path.isdir(group_dir) and os.path.exists(marker_file):
            installed.append(group)
    return sorted(installed)


def print_status(deps_root: str | None = None):
    """
    Print the status of all dependency groups.

    Args:
        deps_root: Root directory where dependencies are installed.
                   If None, uses TENSORRT_DIFFUSION_DEPS_ROOT environment variable,
                   or defaults to "/workspace/deps"
    """
    # Resolve deps_root
    deps_root = _resolve_deps_root(deps_root)

    print("Dependency Groups Status:")
    print("-" * 60)
    print(f"Location: {deps_root}")
    print("-" * 60)

    installed = get_configured_groups(deps_root)

    for group in sorted(VALID_GROUPS):
        description = GROUP_DESCRIPTIONS.get(group, group)
        status = "Installed" if group in installed else "Not installed"
        print(f"  {group:8} - {status:15} - {description}")

    print("-" * 60)

    if not installed:
        print("No dependency groups installed.")
        print("Run: python setup.py all")
    else:
        print(f"{len(installed)} group(s) installed: {', '.join(installed)}")
