# Copyright 2018 Red Hat, Inc.
#
#    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.
import collections
import functools
import importlib
import os
import sys
from unittest import mock

from oslotest import base
from testtools.matchers import Mismatch

importlib.machinery.SOURCE_SUFFIXES.append('')
file_path = (os.path.dirname(os.path.realpath(__file__)) +
             '/../bin/package-installs-squash')
spec = importlib.util.spec_from_file_location('installs_squash', file_path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
sys.modules['installs_squash'] = module
installs_squash = module
importlib.machinery.SOURCE_SUFFIXES.pop()


class IsMatchingInstallList(object):

    def __init__(self, expected):
        self.expected = expected

    def match(self, actual):
        for phase, ops in self.expected.items():
            if phase not in actual:
                # missing the phase
                return Mismatch(
                    "Phase %d does not exist in %s" % (phase, actual))
            for op, pkgs in ops.items():
                if op not in actual[phase]:
                    # missing op (install/uninstall)
                    return Mismatch(
                        "Operation %s does not exist in %s" % (op, ops))
                # on py2 these can be out of order, we just want a match
                expected_phase_ops = sorted(self.expected[phase][op])
                actual_phase_ops = sorted(actual[phase][op])
                if expected_phase_ops != actual_phase_ops:
                    return Mismatch(
                        "Operation list %s does not match expected  %s" %
                        (actual[phase][op], self.expected[phase][op]))


class TestPackageInstall(base.BaseTestCase):
    def setUp(self):
        super(TestPackageInstall, self).setUp()
        self.final_dict = collections.defaultdict(
            functools.partial(collections.defaultdict, list))

    def test_simple(self):
        '''Test a basic package install'''
        objs = {
            'test_package': ''
        }

        result = installs_squash.collect_data(
            self.final_dict, objs, 'test_element')

        expected = {
            'install.d': {
                'install': [('test_package', 'test_element')]
            }
        }

        self.assertThat(result, IsMatchingInstallList(expected))

    @mock.patch.object(os, 'environ', dict(ARCH='arm64'))
    def test_arch(self):
        '''Exercise the arch and not-arch flags'''
        objs = {
            'test_package': '',
            'test_arm64_package': {
                'arch': 'arm64'
            },
            'do_not_install': {
                'not-arch': 'arm64'
            }
        }

        result = installs_squash.collect_data(
            self.final_dict, objs, 'test_element')

        expected = {
            'install.d': {
                'install': [('test_package', 'test_element'),
                            ('test_arm64_package', 'test_element')]
            }
        }

        self.assertThat(result, IsMatchingInstallList(expected))

    kernel_objs = {
        'linux-image-generic': [
            {
                'not-arch': 'arm64',
                'when': 'DIB_UBUNTU_KERNEL = linux-image-generic',
            },
            {
                'arch': 'arm64',
                'when': (
                    'DIB_RELEASE != xenial',
                    'DIB_UBUNTU_KERNEL = linux-image-generic',
                )
            },
        ],
        'linux-generic-hwe-16.04': {
            'arch': 'arm64',
            'when': (
                'DIB_RELEASE = xenial',
                'DIB_UBUNTU_KERNEL = linux-image-generic',
            )
        },
    }

    def _test_kernel_objs_match(self, arch, release, expected):
        with mock.patch.object(os, 'environ',
                               dict(ARCH=arch,
                                    DIB_UBUNTU_KERNEL='linux-image-generic',
                                    DIB_RELEASE=release)):
            result = installs_squash.collect_data(
                self.final_dict, self.kernel_objs, 'test_element')

        expected = {
            'install.d': {
                'install': [(expected, 'test_element')]
            }
        }
        self.assertThat(result, IsMatchingInstallList(expected))

    def test_param_list_x86(self):
        self._test_kernel_objs_match('x86_64', 'focal', 'linux-image-generic')

    def test_param_list_arm64_xenial(self):
        self._test_kernel_objs_match('arm64', 'xenial',
                                     'linux-generic-hwe-16.04')

    def test_param_list_arm64_focal(self):
        self._test_kernel_objs_match('arm64', 'focal', 'linux-image-generic')

    @mock.patch.object(os, 'environ', dict(DIB_FEATURE='1', **os.environ))
    def test_skip_when(self):
        '''Exercise the when flag'''
        objs = {
            'skipped_package': {
                'when': 'DIB_FEATURE=0'
            },
            'not_skipped_package': {
                'when': 'DIB_FEATURE=1'
            },
            'not_equal_package': {
                'when': 'DIB_FEATURE!=0'
            },
            'not_equal_skipped_package': {
                'when': 'DIB_FEATURE!=1'
            },
        }

        result = installs_squash.collect_data(
            self.final_dict, objs, 'test_element')

        expected = {
            'install.d': {
                'install': [('not_skipped_package', 'test_element'),
                            ('not_equal_package', 'test_element')]
            }
        }

        self.assertThat(result, IsMatchingInstallList(expected))

    def test_skip_no_var(self):
        '''Exercise the skip_when missing variable failure case'''
        objs = {
            'package': {
                'when': 'MISSING_VAR=1'
            },
        }

        self.assertRaises(RuntimeError, installs_squash.collect_data,
                          self.final_dict, objs, 'test_element')

    @mock.patch.object(os, 'environ',
                       dict(
                           DIB_A_FEATURE='1',
                           DIB_B_FEATURE='1',
                           DIB_C_FEATURE='1'))
    def test_skip_when_list(self):
        '''Exercise the when flag with lists'''
        objs = {
            'not_skipped_package': {
                'when': [
                    'DIB_A_FEATURE=1',
                    'DIB_B_FEATURE=1',
                    'DIB_C_FEATURE=1'
                ]
            },
            'skipped_package': {
                'when': [
                    'DIB_A_FEATURE=1',
                    'DIB_B_FEATURE=0',
                    'DIB_C_FEATURE=1',
                ]
            },
        }

        result = installs_squash.collect_data(
            self.final_dict, objs, 'test_element')

        expected = {
            'install.d': {
                'install': [('not_skipped_package', 'test_element')]
            }
        }

        self.assertThat(result, IsMatchingInstallList(expected))

    def test_install_overrides_uninstall_install_first(self):
        '''Test an install overrides uninstall'''
        objs = {
            'test_package': ''
        }

        result = installs_squash.collect_data(
            self.final_dict, objs, 'test_element1')

        expected = {
            'install.d': {
                'install': [('test_package', 'test_element1')]
            }
        }

        self.assertThat(result, IsMatchingInstallList(expected))

        objs = {
            'test_package': {'build-only': 'true'}
        }

        result = installs_squash.collect_data(
            self.final_dict, objs, 'test_element2')

        expected = {
            'install.d': {
                'install': [('test_package', 'test_element1'),
                            ('test_package', 'test_element2')]
            }
        }

        self.assertThat(result, IsMatchingInstallList(expected))

    def test_install_overrides_uninstall_uninstall_first(self):
        '''Test an install overrides uninstall'''
        objs = {
            'test_package': {'build-only': 'true'}
        }

        result = installs_squash.collect_data(
            self.final_dict, objs, 'test_element1')

        expected = {
            'install.d': {
                'install': [('test_package', 'test_element1')],
                'uninstall': [('test_package', 'test_element1')]
            }
        }

        self.assertThat(result, IsMatchingInstallList(expected))

        objs = {
            'test_package': ''
        }

        result = installs_squash.collect_data(
            self.final_dict, objs, 'test_element2')

        expected = {
            'install.d': {
                'install': [('test_package', 'test_element1'),
                            ('test_package', 'test_element2')]
            }
        }

        self.assertThat(result, IsMatchingInstallList(expected))
