"""
This is plugin for all the plugins/hooks related to OCS-CI and its
configuration.

The basic configuration is done in run_ocsci.py module casue we need to load
all the config before pytest run. This run_ocsci.py is just a wrapper for
pytest which proccess config and passes all params to pytest.
"""
import logging
import os

import pytest

from ocs_ci.framework import config as ocsci_config
from ocs_ci.framework.exceptions import ClusterPathNotProvidedError, ClusterNameNotProvidedError, ClusterNameLengthError
from ocs_ci.ocs.exceptions import CommandFailed, ResourceNotFoundError
from ocs_ci.utility.utils import (
    dump_config_to_file,
    get_cluster_version,
    get_ceph_version,
    get_csi_versions,
    get_testrun_name,
    get_ocs_build_number,
    load_config_file,
)
from ocs_ci.ocs.utils import collect_ocs_logs, collect_prometheus_metrics
from ocs_ci.ocs.resources.ocs import get_version_info
from ocs_ci.ocs.constants import (
    CLUSTER_NAME_MAX_CHARACTERS,
    CLUSTER_NAME_MIN_CHARACTERS,
    OCP_VERSION_CONF_DIR,
)

__all__ = [
    "pytest_addoption",
]

log = logging.getLogger(__name__)


def pytest_addoption(parser):
    """
    Add necessary options to initialize OCS CI library.
    """
    parser.addoption(
        '--ocsci-conf',
        dest='ocsci_conf',
        action="append",
        help="Path to config file of OCS CI",
    )
    parser.addoption(
        '--cluster-path',
        dest='cluster_path',
        help="Path to cluster directory",
    )
    parser.addoption(
        '--cluster-name',
        dest='cluster_name',
        help="Name of cluster",
    )
    parser.addoption(
        '--teardown',
        dest='teardown',
        action="store_true",
        default=False,
        help="If provided the test cluster will be destroyed after tests complete",
    )
    parser.addoption(
        '--deploy',
        dest='deploy',
        action="store_true",
        default=False,
        help="If provided a test cluster will be deployed on AWS to use for testing",
    )
    parser.addoption(
        '--live-deploy',
        dest='live_deploy',
        action="store_true",
        default=False,
        help="Deploy OCS from live registry like a customer",
    )
    parser.addoption(
        '--email',
        dest='email',
        help="Email ID to send results",
    )
    parser.addoption(
        '--collect-logs',
        dest='collect-logs',
        action="store_true",
        default=False,
        help="Collect OCS logs when test case failed",
    )
    parser.addoption(
        '--io-in-bg',
        dest='io_in_bg',
        action="store_true",
        default=False,
        help="Run IO in the background",
    )
    parser.addoption(
        '--io-load',
        dest='io_load',
        help="IOs throughput target percentage. Value should be between 0 to 100",
    )
    parser.addoption(
        '--log-cluster-utilization',
        dest='log_cluster_utilization',
        action="store_true",
        help="Enable logging of cluster utilization metrics every 10 seconds"
    )
    parser.addoption(
        '--ocs-version',
        dest='ocs_version',
        help="ocs version for which ocs-ci to be run"
    )
    parser.addoption(
        '--upgrade-ocs-version',
        dest='upgrade_ocs_version',
        help="ocs version to upgrade (e.g. 4.3)"
    )
    parser.addoption(
        '--ocp-version',
        dest='ocp_version',
        help="""
        OCP version to be used for deployment. This version will be used for
        load file from conf/ocp_version/ocp-VERSION-config.yaml. You can use
        for example those values:
        4.2: for nightly 4.2 OCP build
        4.2-ga: for latest GAed 4.2 OCP build
        4.2-ga-minus1: for latest GAed 4.2 build - 1
        """
    )
    parser.addoption(
        '--ocs-registry-image',
        dest='ocs_registry_image',
        help=(
            "ocs registry image to be used for deployment "
            "(e.g. quay.io/rhceph-dev/ocs-olm-operator:latest-4.2)"
        )
    )
    parser.addoption(
        '--upgrade-ocs-registry-image',
        dest='upgrade_ocs_registry_image',
        help=(
            "ocs registry image to be used for upgrade "
            "(e.g. quay.io/rhceph-dev/ocs-olm-operator:latest-4.3)"
        )
    )
    parser.addoption(
        '--osd-size',
        dest='osd_size',
        type=int,
        help="OSD size in GB - for 2TB pass 2048, for 0.5TB pass 512 and so on."
    )


def pytest_configure(config):
    """
    Load config files, and initialize ocs-ci library.

    Args:
        config (pytest.config): Pytest config object

    """
    if not (config.getoption("--help") or config.getoption("collectonly")):
        process_cluster_cli_params(config)
        config_file = os.path.expanduser(
            os.path.join(
                ocsci_config.RUN['log_dir'],
                f"run-{ocsci_config.RUN['run_id']}-config.yaml",
            )
        )
        dump_config_to_file(config_file)
        log.info(
            f"Dump of the consolidated config file is located here: "
            f"{config_file}"
        )
        # Add OCS related versions to the html report and remove
        # extraneous metadata
        markers_arg = config.getoption('-m')
        if ocsci_config.RUN['cli_params'].get('teardown') or (
            "deployment" in markers_arg
            and ocsci_config.RUN['cli_params'].get('deploy')
        ):
            log.info(
                "Skipping versions collecting because: Deploy or destroy of "
                "cluster is performed."
            )
            return
        elif ocsci_config.ENV_DATA['skip_ocs_deployment']:
            log.info(
                "Skipping version collection because we skipped "
                "the OCS deployment"
            )
            return
        print("Collecting Cluster versions")
        # remove extraneous metadata
        del config._metadata['Python']
        del config._metadata['Packages']
        del config._metadata['Plugins']
        del config._metadata['Platform']

        config._metadata['Test Run Name'] = get_testrun_name()
        gather_version_info_for_report(config)


def gather_version_info_for_report(config):
    """
    This function gather all version related info used for report.

    Args:
        config (pytest.config): Pytest config object
    """
    gather_version_completed = False
    try:
        # add cluster version
        clusterversion = get_cluster_version()
        config._metadata['Cluster Version'] = clusterversion

        # add ceph version
        ceph_version = get_ceph_version()
        config._metadata['Ceph Version'] = ceph_version

        # add csi versions
        csi_versions = get_csi_versions()
        config._metadata['cephfsplugin'] = csi_versions.get('csi-cephfsplugin')
        config._metadata['rbdplugin'] = csi_versions.get('csi-rbdplugin')

        # add ocs operator version
        if ocsci_config.REPORTING['us_ds'] == 'DS':
            config._metadata['OCS operator'] = (
                get_ocs_build_number()
            )
        mods = {}
        mods = get_version_info(
            namespace=ocsci_config.ENV_DATA['cluster_namespace']
        )
        skip_list = ['ocs-operator']
        for key, val in mods.items():
            if key not in skip_list:
                config._metadata[key] = val.rsplit('/')[-1]
        gather_version_completed = True
    except ResourceNotFoundError as ex:
        log.error(
            "Problem ocurred when looking for some resource! Error: %s",
            ex
        )
    except FileNotFoundError as ex:
        log.error("File not found! Error: %s", ex)
    except CommandFailed as ex:
        log.error("Failed to execute command! Error: %s", ex)
    except Exception as ex:
        log.error("Failed to gather version info! Error: %s", ex)
    finally:
        if not gather_version_completed:
            log.warning(
                "Failed to gather version details! The report of version might"
                "not be complete!"
            )


def get_cli_param(config, name_of_param, default=None):
    """
    This is helper function which store cli parameter in RUN section in
    cli_params

    Args:
        config (pytest.config): Pytest config object
        name_of_param (str): cli parameter name
        default (any): default value of parameter (default: None)

    Returns:
        any: value of cli parameter or default value

    """
    cli_param = config.getoption(name_of_param, default=default)
    ocsci_config.RUN['cli_params'][name_of_param] = cli_param
    return cli_param


def process_cluster_cli_params(config):
    """
    Process cluster related cli parameters

    Args:
        config (pytest.config): Pytest config object

    Raises:
        ClusterPathNotProvidedError: If a cluster path is missing
        ClusterNameNotProvidedError: If a cluster name is missing
        ClusterNameLengthError: If a cluster name is too short or too long
    """
    cluster_path = get_cli_param(config, 'cluster_path')
    if not cluster_path:
        raise ClusterPathNotProvidedError()
    cluster_path = os.path.expanduser(cluster_path)
    if not os.path.exists(cluster_path):
        os.makedirs(cluster_path)
    # Importing here cause once the function is invoked we have already config
    # loaded, so this is OK to import once you sure that config is loaded.
    from ocs_ci.ocs.openshift_ops import OCP
    OCP.set_kubeconfig(
        os.path.join(cluster_path, ocsci_config.RUN['kubeconfig_location'])
    )
    cluster_name = get_cli_param(config, 'cluster_name')
    ocsci_config.RUN['cli_params']['teardown'] = get_cli_param(config, "teardown", default=False)
    ocsci_config.RUN['cli_params']['deploy'] = get_cli_param(config, "deploy", default=False)
    live_deployment = get_cli_param(config, "live_deploy", default=False)
    ocsci_config.DEPLOYMENT['live_deployment'] = live_deployment or (
        ocsci_config.DEPLOYMENT.get('live_deployment', False)
    )
    io_in_bg = get_cli_param(config, 'io_in_bg')
    if io_in_bg:
        ocsci_config.RUN['io_in_bg'] = True
        io_load = get_cli_param(config, 'io_load')
        if io_load:
            ocsci_config.RUN['io_load'] = io_load
    log_utilization = get_cli_param(config, 'log_cluster_utilization')
    if log_utilization:
        ocsci_config.RUN['log_utilization'] = True
    upgrade_ocs_version = get_cli_param(config, "upgrade_ocs_version")
    if upgrade_ocs_version:
        ocsci_config.UPGRADE['upgrade_ocs_version'] = upgrade_ocs_version
    ocs_registry_image = get_cli_param(config, "ocs_registry_image")
    if ocs_registry_image:
        ocsci_config.DEPLOYMENT['ocs_registry_image'] = ocs_registry_image
    upgrade_ocs_registry_image = get_cli_param(config, "upgrade_ocs_registry_image")
    if upgrade_ocs_registry_image:
        ocsci_config.UPGRADE['upgrade_ocs_registry_image'] = upgrade_ocs_registry_image
    ocsci_config.ENV_DATA['cluster_name'] = cluster_name
    ocsci_config.ENV_DATA['cluster_path'] = cluster_path
    get_cli_param(config, 'collect-logs')
    if ocsci_config.RUN.get("cli_params").get("deploy"):
        if not cluster_name:
            raise ClusterNameNotProvidedError()
        if (
            len(cluster_name) < CLUSTER_NAME_MIN_CHARACTERS
            or len(cluster_name) > CLUSTER_NAME_MAX_CHARACTERS
        ):
            raise ClusterNameLengthError(cluster_name)
    if get_cli_param(config, 'email') and not get_cli_param(config, '--html'):
        pytest.exit("--html option must be provided to send email reports")
    get_cli_param(config, '-m')
    osd_size = get_cli_param(config, '--osd-size')
    if osd_size:
        ocsci_config.ENV_DATA['device_size'] = osd_size
    ocp_version = get_cli_param(config, '--ocp-version')
    if ocp_version:
        version_config_file = f"ocp-{ocp_version}-config.yaml"
        version_config_file_path = os.path.join(
            OCP_VERSION_CONF_DIR, version_config_file
        )
        load_config_file(version_config_file_path)


def pytest_collection_modifyitems(session, config, items):
    """
    Add Polarion ID property to test cases that are marked with one.
    """
    for item in items:
        try:
            marker = item.get_closest_marker(name="polarion_id")
            if marker:
                polarion_id = marker.args[0]
                if polarion_id:
                    item.user_properties.append(
                        ("polarion-testcase-id", polarion_id)
                    )
        except IndexError:
            log.warning(
                f"polarion_id marker found with no value for "
                f"{item.name} in {item.fspath}",
                exc_info=True
            )


@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_makereport(item, call):
    outcome = yield
    rep = outcome.get_result()
    # we only look at actual failing test calls, not setup/teardown
    if (
        rep.when == "call"
        and rep.failed
        and ocsci_config.RUN.get('cli_params').get('collect-logs')
    ):
        test_case_name = item.name
        mcg = True if any(x in item.location[0] for x in ['mcg', 'ecosystem']) else False
        collect_ocs_logs(dir_name=test_case_name, mcg=mcg)

    # Collect Prometheus metrics if specified in gather_metrics_on_fail marker
    if (
        (rep.when == "setup" or rep.when == "call")
        and rep.failed
        and item.get_closest_marker('gather_metrics_on_fail')
    ):
        metrics = item.get_closest_marker('gather_metrics_on_fail').args
        collect_prometheus_metrics(
            metrics,
            f'{item.name}-{call.when}',
            call.start,
            call.stop
        )

    # Get the performance metrics when tests fails for scale or performance tag
    from tests.helpers import collect_performance_stats
    if (
        (rep.when == "setup" or rep.when == "call")
        and rep.failed
        and (item.get_closest_marker('scale') or item.get_closest_marker('performance'))
    ):
        test_case_name = item.name
        collect_performance_stats(test_case_name)