# MIT License
#
# Copyright (c) 2018-2019 Red Hat, Inc.
# 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 subprocess
import time
import json
import logging

from typing import Any

from container_ci_suite.utils import ContainerTestLibUtils

logger = logging.getLogger(__name__)


class PodmanCLIWrapper(object):
    """
    Podman CLI Wrapper - Class for wrapping podman commands.
    """

    @staticmethod
    def call_podman_command(
        cmd,
        return_output: bool = True,
        ignore_error: bool = False,
        shell: bool = True,
        debug: bool = False,
        stderr=subprocess.STDOUT,
    ):
        """
        Run docker command:

        Args:
            cmd: The command to run
            return_output: Whether to return the output of the command
            ignore_error: Whether to ignore errors
            shell: Whether to run the command in a shell
            debug: Whether to print the command
            stderr: The stderr to use

        Returns:
            The output of the command
        """
        return ContainerTestLibUtils.run_command(
            f"podman {cmd}",
            return_output=return_output,
            ignore_error=ignore_error,
            shell=shell,
            debug=debug,
            stderr=stderr,
        )

    @staticmethod
    def podman_image_exists(image_name: str) -> bool:
        """
        Check if docker image exists or not
        :param image_name: image to check
        :return True: In case if image is present
                False: In case if image is not present
        """
        output = PodmanCLIWrapper.call_podman_command(
            f"images -q {image_name}", ignore_error=True, return_output=True
        )
        return True if output != "" else False

    @staticmethod
    def podman_inspect(field: str, src_image: str) -> str:
        """
        Inspect the container or image.

        Args:
            field: The field to inspect
            src_image: The image to inspect

        Returns:
            The output of the command
        """
        return PodmanCLIWrapper.call_podman_command(f"inspect -f '{field}' {src_image}")

    @staticmethod
    def podman_run_command(cmd, ignore_error: bool = False, return_output: bool = True):
        """
        Run a podman command.

        Args:
            cmd: The command to run
            ignore_error: Whether to ignore errors
            return_output: Whether to return the output of the command
        Returns:
            The output of the command
        """
        return PodmanCLIWrapper.call_podman_command(
            f"run {cmd}", ignore_error=ignore_error, return_output=return_output
        )

    @staticmethod
    def podman_exec_shell_command(
        cid_file_name: str,
        cmd: str,
        used_shell: str = "/bin/bash",
        return_output: bool = True,
        debug: bool = False,
        not_shell: bool = False,
        ignore_error: bool = False,
    ):
        """
        Function executes shell command if image_name is present in system.

        Args:
            cid_file_name: image to check specified by cid_file_name
            cmd: command that will be executed in image
            used_shell: which shell will be used /bin/bash or /bin/sh
            return_output: Whether to return the output of the command
            debug: Whether to print the command
            not_shell: if True, the command will be executed without a shell

        Returns:
            The output of the command
        """
        if not_shell:
            cmd = f"exec {cid_file_name} {cmd}"
        else:
            cmd = f"exec {cid_file_name} {used_shell} -c '{cmd}'"
        logger.info("podman command is: %s", cmd)
        try:
            output = PodmanCLIWrapper.call_podman_command(
                cmd=cmd,
                return_output=return_output,
                debug=debug,
                ignore_error=ignore_error,
            )
        except subprocess.CalledProcessError as cpe:
            logger.error("podman_exec_shell_command: %s failed. See '%s'", cmd, cpe)
            return False
        return output

    @staticmethod
    def podman_run_command_and_remove(
        cid_file_name: str,
        cmd: str,
        return_output: bool = True,
        debug: bool = False,
        ignore_error: bool = False,
    ):
        """
        Run a shell command if the image name is present in the system.

        Args:
            cid_file_name: image to check specified by cid_file_name
            cmd: command that will be executed in image
            return_output: Whether to return the output of the command
            debug: Whether to print the command
            ignore_error: Whether to ignore errors

        Returns:
            True if the command was executed successfully, False otherwise
            The output of the command
        """
        cmd = f"run --rm {cid_file_name} /bin/bash -c '{cmd}'"
        logger.info("podman command is: '%s'", cmd)
        try:
            output = PodmanCLIWrapper.call_podman_command(
                cmd=cmd,
                return_output=return_output,
                ignore_error=ignore_error,
                debug=debug,
            )
        except subprocess.CalledProcessError as cpe:
            logger.error("podman_run_command_and_remove: %s failed. See '%s'", cmd, cpe)
            return False
        return output

    @staticmethod
    def podman_get_user_id(src_image, user):
        """
        Get the user ID of a user in a container.

        Args:
            src_image: The image to get the user ID from
            user: The user to get the user ID of

        Returns:
            The user ID of the user
            The output of the command
        """
        return PodmanCLIWrapper.call_podman_command(
            f"--rm {src_image} /bin/bash -c 'id -u {user}' 2>/dev/null"
        ).strip()

    @staticmethod
    def podman_pull_image(image_name: str, loops: int = 10) -> bool:
        """
        Function checks if image_name is present in system.
        In case it isn't, try to pull it for specific count of loops
        Default is 10.

        Args:
            image_name: The name of the image to pull
            loops: The number of loops to try to pull the image

        Returns:
            True if the image was pulled successfully, False otherwise
        """
        if PodmanCLIWrapper.podman_image_exists(image_name=image_name):
            logger.info("Pulled image already exists.")
            return True
        for loop in range(loops):
            try:
                ret_val = PodmanCLIWrapper.call_podman_command(
                    cmd=f"pull {image_name}", return_output=False
                )
            except subprocess.CalledProcessError:
                ret_val = 1
            if ret_val == 0 and PodmanCLIWrapper.podman_image_exists(
                image_name=image_name
            ):
                return True
            PodmanCLIWrapper.call_podman_command("images", return_output=True)
            logger.error(
                "Pulling of image %s failed. Let's wait %s seconds and try again.",
                image_name,
                loop * 5,
            )
            time.sleep(loop * 5)
        return False

    @staticmethod
    def podman_inspect_ip_address(container_id: str) -> Any:
        """
        Get the IP address of a container.

        Args:
            container_id: The ID of the container to get the IP address of

        Returns:
            The IP address of the container
        """
        output = PodmanCLIWrapper.call_podman_command(f"inspect {container_id}")

        json_output = json.loads(output)
        if len(json_output) == 0:
            return None
        if "NetworkSettings" not in json_output[0]:
            return None
        return json_output[0]["NetworkSettings"]["IPAddress"]

    @staticmethod
    def podman_exit_status(image_name: str) -> str:
        """
        Get the exit status of a container.

        Args:
            image_name: The name of the image to get the exit status of

        Returns:
            The exit status of the container
        """
        return PodmanCLIWrapper.call_podman_command(
            cmd=f"inspect --format='{{{{.State.ExitCode}}}}' {image_name}",
            return_output=True,
        ).strip()

    @staticmethod
    def podman_get_user(image_name: str) -> Any:
        """
        Get the user of a container.

        Args:
            image_name: The name of the image to get the user of

        Returns:
            The user of the container
        """
        output = PodmanCLIWrapper.call_podman_command(f"inspect {image_name}")

        json_output = json.loads(output)
        if len(json_output) == 0:
            return None
        if "Config" not in json_output[0]:
            return None
        return json_output[0]["Config"]["User"]

    @staticmethod
    def podman_get_file_content(cid_file_name: str, filename: str) -> str:
        """
        Get the content of a file in a container.

        Args:
            cid_file_name: The name of the container to get the file content of
            filename: The name of the file to get the content of

        Returns:
            The content of the file
        """
        return PodmanCLIWrapper.call_podman_command(
            cmd=f"exec {cid_file_name} cat {filename}"
        )

    @staticmethod
    def podman_logs(
        container_id: str,
    ):
        """
        Get the logs of a container.

        Args:
            container_id: The ID of the container to get the logs of

        Returns:
            The logs of the container
        """
        return PodmanCLIWrapper.call_podman_command(f"logs {container_id}")
