#!/usr/bin/env python3
#
# Copyright (C) Advanced Micro Devices. All rights reserved.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy of
# this software and associated documentation files (the "Software"), to deal in
# the Software without restriction, including without limitation the rights to
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
# the Software, and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

import logging

from amdsmi import amdsmi_exception, amdsmi_interface


class NodeCommands:
    def node(
        self,
        args,
        multiple_devices=False,
        nodes=None,
        power_management=None,
        base_board_temps=None,
        gtt=None,
    ):
        """List node information

        Args:
            args (Namespace): Namespace containing the parsed CLI args
            multiple_devices (bool, optional): True if checking for multiple devices.
                Defaults to False.
            nodes (node_handle, optional): node_handle for target node. Defaults to None.
            power_management (bool, optional): Value override for args.power_management. Defaults to None.
            base_board_temps (bool, optional): Value override for args.base_board_temps. Defaults to None.
            gtt (bool, optional): Value override for args.gtt. Defaults to None.

        Returns:
            None: Print output via AMDSMILogger to destination
        """
        # Set args.* to passed in arguments
        if nodes:
            args.nodes = nodes
        if gtt:
            args.gtt = gtt
        # Store args that are applicable to the current platform
        current_platform_args = ["power_management", "base_board_temps", "gtt"]

        # Check if any node-specific options were passed via command line
        current_platform_values = []
        if args.power_management:
            current_platform_values += [args.power_management]
        if args.base_board_temps:
            current_platform_values += [args.base_board_temps]
        if args.gtt:
            current_platform_values += [args.gtt]

        # If no node options are passed, enable all by default
        if not any(current_platform_values):
            for arg in current_platform_args:
                setattr(args, arg, True)
        if getattr(args, "nodes", None) is None:
            args.nodes = self.node_handle

        if not self.group_check_printed:
            self.helpers.check_required_groups()
            self.group_check_printed = True

        # Initialize variables for both power management and base board temps
        npm_dict = {"limit": "N/A", "status": "N/A", "threshold": "N/A"}
        power_unit = "W"
        limit = "N/A"
        base_board_temp_dict = {}
        gtt_dict = {}

        # Get NPM info
        if args.power_management:
            if args.nodes is not None:
                try:
                    npm_info = amdsmi_interface.amdsmi_get_npm_info(args.nodes)
                except amdsmi_exception.AmdSmiLibraryException as e:
                    logging.debug("amdsmi_get_npm_info failed: %s", e.get_error_info())
                    npm_info = "N/A"
            else:
                logging.debug("No node handle available to query NPM info")
                npm_info = "N/A"

            if isinstance(npm_info, dict):
                limit = npm_info.get("limit", "N/A")
                status = npm_info.get("status", npm_info.get("current", "N/A"))
                ubb_power_threshold = npm_info.get("ubb_power_threshold", "N/A")

                if limit != "N/A":
                    npm_dict["limit"] = limit
                status = (
                    "DISABLED"
                    if status == amdsmi_interface.amdsmi_wrapper.AMDSMI_NPM_STATUS_DISABLED
                    else "ENABLED"
                )
                npm_dict.update({"status": status})
                # Add UBB power threshold if available
                if ubb_power_threshold != "N/A":
                    npm_dict["threshold"] = ubb_power_threshold

        # Get base board temperatures using node_handle
        if args.base_board_temps:
            if args.nodes is not None:
                try:
                    # Get device_handle for OAM_ID 0
                    device_handle = self.helpers.get_oam_0_device_handle()
                    gpu_id = self.helpers.get_gpu_id_from_device_handle(device_handle)
                    base_board_temp_dict = self.helpers.get_base_board_temperatures(
                        device_handle, gpu_id, self.logger
                    )
                except amdsmi_exception.AmdSmiLibraryException as e:
                    logging.debug("Failed to get device handle from node: %s", e.get_error_info())
                    base_board_temp_dict = {}

        # Get GTT (shared GPU memory) information
        if args.gtt:
            try:
                ttm_info = amdsmi_interface.amdsmi_get_ttm_info()
                logging.debug(f"TTM info: {ttm_info}")

                gtt_pages = ttm_info.get("current_pages", 0)
                gtt_gb = self.helpers.pages_to_gb(gtt_pages)

                gtt_dict = {"size_gb": gtt_gb, "size_pages": gtt_pages}

                # Detect a pending value written by `amd-smi set --gtt` that
                # will apply on the next boot (modprobe.d snippet baked into
                # initramfs). Show it so users don't confuse current vs. pending.
                pending_pages = self.helpers.read_pending_gtt_pages()
                if pending_pages is not None and pending_pages != gtt_pages:
                    pending_gb = self.helpers.pages_to_gb(pending_pages)
                    gtt_dict["pending_size_gb"] = pending_gb
                    gtt_dict["pending_size_pages"] = pending_pages
            except amdsmi_exception.AmdSmiLibraryException as e:
                logging.debug("Failed to get GTT info | %s", e.get_error_info())
                gtt_dict = {}

        # Print output
        if self.logger.is_human_readable_format() and self.logger.destination == "stdout":
            node_output = ["NODE:"]
            if args.power_management:
                node_output.append("    POWER_MANAGEMENT:")
                node_output.append(f"        LIMIT: {npm_dict.get('limit', 'N/A')} {power_unit}")
                node_output.append(f"        STATUS: {npm_dict.get('status', 'N/A')}")
                threshold = npm_dict.get("threshold", "N/A")
                node_output.append(f"        THRESHOLD: {threshold} {power_unit}")
            if args.base_board_temps and base_board_temp_dict:
                node_output.append("    BASEBOARD:")
                node_output.append("        TEMPERATURE:")
                for temp_name, temp_value in base_board_temp_dict.items():
                    node_output.append(f"            {temp_name.upper()}: {temp_value}")
            if args.gtt and gtt_dict:
                gtt_gb = gtt_dict.get("size_gb", 0)
                gtt_pages = gtt_dict.get("size_pages", 0)
                node_output.append("    GTT:")
                node_output.append(f"        SIZE: {gtt_gb:.2f} GB ({gtt_pages} pages)")
                if "pending_size_gb" in gtt_dict:
                    p_gb = gtt_dict["pending_size_gb"]
                    p_pages = gtt_dict["pending_size_pages"]
                    node_output.append(
                        f"        PENDING (after reboot): {p_gb:.2f} GB ({p_pages} pages)"
                    )
            print("\n".join(node_output))
        else:
            if self.logger.is_csv_format():
                csv_dict = {}
                if args.power_management:
                    csv_dict["limit"] = npm_dict.get("limit", "N/A")
                    csv_dict["status"] = npm_dict.get("status", "N/A")
                    csv_dict["threshold"] = npm_dict.get("threshold", "N/A")
                if args.base_board_temps and base_board_temp_dict:
                    csv_dict.update(base_board_temp_dict)
                if args.gtt and gtt_dict:
                    csv_dict["gtt_gb"] = gtt_dict.get("size_gb", "N/A")
                    csv_dict["gtt_pages"] = gtt_dict.get("size_pages", "N/A")
                    if "pending_size_gb" in gtt_dict:
                        csv_dict["gtt_pending_gb"] = gtt_dict["pending_size_gb"]
                        csv_dict["gtt_pending_pages"] = gtt_dict["pending_size_pages"]
                self.logger.output = csv_dict
            else:
                # For JSON and human readable format with file output
                node_output = {}
                if args.power_management:
                    npm_dict["limit"] = self.helpers.unit_format(self.logger, limit, power_unit)
                    threshold = npm_dict.get("threshold", "N/A")
                    if threshold != "N/A":
                        npm_dict["threshold"] = self.helpers.unit_format(
                            self.logger, threshold, power_unit
                        )
                    node_output["power_management"] = npm_dict
                if args.base_board_temps and base_board_temp_dict:
                    node_output["base_board"] = {"temperature": base_board_temp_dict}
                if args.gtt and gtt_dict:
                    node_output["gtt"] = gtt_dict
                self.logger.output = {"node": node_output}
                if multiple_devices:
                    self.logger.store_multiple_device_output()
                    return
            self.logger.print_output()
