#!/usr/bin/env python3
"""Main entry point for rocprof-compute"""

# Copyright (c) Advanced Micro Devices, Inc.
# SPDX-License-Identifier:  MIT

import re
import sys
import traceback
import warnings
from pathlib import Path

# Suppress pandas FutureWarning about chained assignment
warnings.filterwarnings("ignore", category=FutureWarning)

try:
    from importlib import metadata

    from rocprof_compute_base import RocProfCompute
    from utils.logger import console_error
except ImportError as e:
    print(f"{e}")
    # In wheel package, softlink is not supported.
    # rocprof-compute will get installed in bin and libexec/rocprofiler-compute.
    # When invoked from bin folder, the extra paths are required to import the
    # dependent scripts

    try:
        current_path = Path(__file__).resolve().parent
        additional_path = current_path / "../libexec/rocprofiler-compute"
        sys.path.append(str(additional_path.resolve()))

        from importlib import metadata

        from rocprof_compute_base import RocProfCompute
        from utils.logger import console_error
    except ImportError:
        print(
            "\n[ERROR] Could not import rocprofiler-compute modules. "
            "Please check your installation."
        )
        script_path = Path(__file__).resolve().parent
        print(f"Searched: {script_path}, {additional_path.resolve()}")
        traceback.print_exc()
        sys.exit(1)


def check_version(local_ver: str, desired_ver: str, operator: str) -> bool:
    """Check package version strings with simple operators used in companion
    requirements.txt file"""
    ops = {
        "==": lambda loc, des: loc == des,
        ">=": lambda loc, des: loc >= des,
        "<=": lambda loc, des: loc <= des,
        ">": lambda loc, des: loc > des,
        "<": lambda loc, des: loc < des,
    }
    return ops.get(operator, lambda loc, des: True)(local_ver, desired_ver)


def check_python_version() -> None:
    """Check the baseline Python version required to launch rocprofiler-compute.

    Profile mode runs on Python 3.8+ (standard library only). Analyze mode has a
    higher floor enforced separately in check_analyze_python_version().
    """
    min_python = (3, 8)
    # Check which version of python is being used
    if sys.version_info < min_python:  # noqa: UP036
        console_error(
            f"Python {min_python[0]}.{min_python[1]} or higher "
            "is required for rocprofiler-compute. "
            "The current version is "
            f"{sys.version_info[0]}.{sys.version_info[1]}."
        )


def check_analyze_python_version() -> None:
    """Enforce the analyze-mode Python floor (its dependencies require 3.9+)."""
    min_python = (3, 9)
    if sys.version_info < min_python:
        console_error(
            f"analyze mode requires Python {min_python[0]}.{min_python[1]} "
            "or higher. The current version is "
            f"{sys.version_info[0]}.{sys.version_info[1]}. "
            "Profile mode supports Python 3.8+."
        )


def verify_deps() -> None:
    """Verify that all required packages from requirements.txt are installed
    with correct versions for analyze mode execution.

    NOTE: Only called for analyze mode. Profile mode requires no external
    dependencies (uses stdlib only).
    """
    bindir = str(Path(__file__).resolve().parent)
    deps_locations = ["requirements.txt", "../requirements.txt"]

    for location in deps_locations:
        check_file = Path(bindir).joinpath(location)
        if check_file.exists():
            dependencies = check_file.read_text(encoding="utf-8").splitlines()

            error = False
            version_pattern = re.compile(r"^([^=<>]+)([=<>]+)(.*)$")

            for dependency in dependencies:
                dependency = dependency.strip()
                if not dependency or dependency.startswith("#"):
                    continue
                match = version_pattern.match(dependency)
                if match:
                    package, operator, desired_version = match.groups()
                else:
                    package, operator, desired_version = dependency, None, None

                try:
                    local_version = metadata.distribution(package).version
                except metadata.PackageNotFoundError:
                    error = True
                    console_error(
                        f"The '{dependency}' package was not found "
                        "in the current execution environment.",
                        exit=False,
                    )
                    continue

                # Check version requirement
                if desired_version and not check_version(
                    local_version, desired_version, operator
                ):
                    console_error(
                        f"the '{dependency}' distribution does not meet "
                        "version requirements to use rocprofiler-compute."
                        f"\n  --> version installed : {local_version}",
                        exit=False,
                    )
                    error = True

            if error:
                console_error(
                    "\nPlease verify all of the python dependencies called out "
                    "in the requirements file"
                    "\nare installed locally prior to running "
                    "rocprofiler-compute."
                    f"\n\nSee: {check_file}"
                )
            return


def main() -> None:
    """Main function for rocprofiler-compute"""

    # Baseline Python floor for any mode (profile is 3.8+); analyze adds its own
    check_python_version()

    rocprof_compute = RocProfCompute()
    mode = rocprof_compute.get_mode()

    # major rocprofiler-compute execution modes
    if mode == "profile":
        rocprof_compute.run_profiler()
    elif mode == "analyze":
        # Analyze mode needs a higher Python floor and external dependencies
        check_analyze_python_version()
        verify_deps()
        rocprof_compute.run_analysis()
    else:
        console_error("Unsupported execution mode")

    sys.exit(0)


if __name__ == "__main__":
    main()
