#
#    Copyright 2024 ARM Limited
#
# 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.
#

"""
Module for testing targets.

Sample run with log level is set to DEBUG (see
https://docs.pytest.org/en/7.1.x/how-to/logging.html#live-logs for logging details):

$ python -m pytest --log-cli-level DEBUG test_target.py
"""

import logging
import os
import pytest

from devlib import AndroidTarget, ChromeOsTarget, LinuxTarget, LocalLinuxTarget
from devlib._target_runner import NOPTargetRunner, QEMUTargetRunner
from devlib.utils.android import AdbConnection
from devlib.utils.misc import load_struct_from_yaml


logger = logging.getLogger('test_target')


def get_class_object(name):
    """
    Get associated class object from string formatted class name

    :param name: Class name
    :type name: str
    :return: Class object
    :rtype: object or None
    """
    if globals().get(name) is None:
        return None

    return globals()[name] if issubclass(globals()[name], object) else None


@pytest.fixture(scope="module")
# pylint: disable=too-many-branches
def build_target_runners():
    """Read targets from a YAML formatted config file and create runners for them"""

    logger.info("Initializing resources...")

    config_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'test_config.yml')

    test_config = load_struct_from_yaml(config_file)
    if test_config is None:
        raise ValueError(f'{config_file} looks empty!')

    target_configs = test_config.get('target-configs')
    if target_configs is None:
        raise ValueError('No targets found!')

    target_runners = []

    for entry in target_configs.values():
        key, target_info = entry.popitem()

        target_class = get_class_object(key)
        if target_class is AndroidTarget:
            logger.info('> Android target: %s', repr(target_info))
            a_target = AndroidTarget(
                connect=False,
                connection_settings=target_info['connection_settings'],
                conn_cls=lambda **kwargs: AdbConnection(adb_as_root=True, **kwargs),
            )
            a_target.connect(timeout=target_info.get('timeout', 60))
            target_runners.append(NOPTargetRunner(a_target))

        elif target_class is ChromeOsTarget:
            logger.info('> ChromeOS target: %s', repr(target_info))
            c_target = ChromeOsTarget(
                connection_settings=target_info['connection_settings'],
                working_directory='/tmp/devlib-target',
            )
            target_runners.append(NOPTargetRunner(c_target))

        elif target_class is LinuxTarget:
            logger.info('> Linux target: %s', repr(target_info))
            l_target = LinuxTarget(connection_settings=target_info['connection_settings'])
            target_runners.append(NOPTargetRunner(l_target))

        elif target_class is LocalLinuxTarget:
            logger.info('> LocalLinux target: %s', repr(target_info))
            ll_target = LocalLinuxTarget(connection_settings=target_info['connection_settings'])
            target_runners.append(NOPTargetRunner(ll_target))

        elif target_class is QEMUTargetRunner:
            logger.info('> QEMU target runner: %s', repr(target_info))

            qemu_runner = QEMUTargetRunner(
                qemu_settings=target_info.get('qemu_settings'),
                connection_settings=target_info.get('connection_settings'),
            )

            if target_info.get('ChromeOsTarget') is not None:
                # Leave termination of QEMU runner to ChromeOS target.
                target_runners.append(NOPTargetRunner(qemu_runner.target))

                logger.info('>> ChromeOS target: %s', repr(target_info["ChromeOsTarget"]))
                qemu_runner.target = ChromeOsTarget(
                    connection_settings={
                        **target_info['ChromeOsTarget']['connection_settings'],
                        **qemu_runner.target.connection_settings,
                    },
                    working_directory='/tmp/devlib-target',
                )

            target_runners.append(qemu_runner)

        else:
            raise ValueError(f'Unknown target type {key}!')

    yield target_runners

    logger.info("Destroying resources...")

    for target_runner in target_runners:
        target = target_runner.target

        # TODO: Revisit per https://github.com/ARM-software/devlib/issues/680.
        logger.debug('Removing %s...', target.working_directory)
        target.remove(target.working_directory)

        target_runner.terminate()


# pylint: disable=redefined-outer-name
def test_read_multiline_values(build_target_runners):
    """
    Test Target.read_tree_values_flat()

    Runs tests around ``Target.read_tree_values_flat()`` for ``TargetRunner`` objects.
    """

    logger.info('Running test_read_multiline_values test...')

    data = {
        'test1': '1',
        'test2': '2\n\n',
        'test3': '3\n\n4\n\n',
    }

    target_runners = build_target_runners
    for target_runner in target_runners:
        target = target_runner.target

        logger.info('target=%s os=%s hostname=%s',
                    target.__class__.__name__, target.os, target.hostname)

        with target.make_temp() as tempdir:
            logger.debug('Created %s.', tempdir)

            for key, value in data.items():
                path = os.path.join(tempdir, key)
                logger.debug('Writing %s to %s...', repr(value), path)
                target.write_value(path, value, verify=False,
                                   as_root=target.conn.connected_as_root)

            logger.debug('Reading values from target...')
            raw_result = target.read_tree_values_flat(tempdir)
            result = {os.path.basename(k): v for k, v in raw_result.items()}

        assert {k: v.strip() for k, v in data.items()} == result