# -*- coding: utf-8 -*-
'''
    :codeauthor: :email:`Bo Maryniuk <bo@suse.de>`
'''

# Import Python Libs
from __future__ import absolute_import

# Import Salt Testing Libs
from salttesting import TestCase, skipIf
from salttesting.mock import (
    Mock,
    MagicMock,
    call,
    patch,
    NO_MOCK,
    NO_MOCK_REASON
)
from salt.exceptions import CommandExecutionError

import os
from salt.ext.six.moves import configparser
import StringIO

from salttesting.helpers import ensure_in_syspath

ensure_in_syspath('../../')


class ZyppCallMock(object):
    def __init__(self, return_value=None):
        self.__return_value = return_value

    def __getattr__(self, item):
        return self

    def __call__(self, *args, **kwargs):
        return MagicMock(return_value=self.__return_value)()


def get_test_data(filename):
    '''
    Return static test data
    '''
    return open(os.path.join(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'zypp'), filename)).read()


# Import Salt Libs
from salt.modules import zypper

# Globals
zypper.__salt__ = dict()
zypper.__context__ = dict()
zypper.rpm = None


@skipIf(NO_MOCK, NO_MOCK_REASON)
class ZypperTestCase(TestCase):

    '''
    Test cases for salt.modules.zypper
    '''

    def setUp(self):
        self.new_repo_config = dict(
            name='mock-repo-name',
            url='http://repo.url/some/path'
        )
        side_effect = [
            Mock(**{'sections.return_value': []}),
            Mock(**{'sections.return_value': [self.new_repo_config['name']]})
        ]
        self.zypper_patcher_config = {
            '_get_configured_repos': Mock(side_effect=side_effect),
            '__zypper__': Mock(),
            'get_repo': Mock(return_value={})
        }

    def test_list_upgrades(self):
        '''
        List package upgrades
        :return:
        '''
        ref_out = {
            'stdout': get_test_data('zypper-updates.xml'),
            'stderr': None,
            'retcode': 0
        }
        with patch.dict(zypper.__salt__, {'cmd.run_all': MagicMock(return_value=ref_out)}):
            upgrades = zypper.list_upgrades(refresh=False)
            self.assertEqual(len(upgrades), 3)
            for pkg, version in {'SUSEConnect': '0.2.33-7.1',
                                 'bind-utils': '9.9.6P1-35.1',
                                 'bind-libs': '9.9.6P1-35.1'}.items():
                self.assertIn(pkg, upgrades)
                self.assertEqual(upgrades[pkg], version)

    def test_zypper_caller(self):
        '''
        Test Zypper caller.
        :return:
        '''
        class RunSniffer(object):
            def __init__(self, stdout=None, stderr=None, retcode=None):
                self.calls = list()
                self._stdout = stdout or ''
                self._stderr = stderr or ''
                self._retcode = retcode or 0

            def __call__(self, *args, **kwargs):
                self.calls.append({'args': args, 'kwargs': kwargs})
                return {'stdout': self._stdout,
                        'stderr': self._stderr,
                        'retcode': self._retcode}

        stdout_xml_snippet = '<?xml version="1.0"?><test foo="bar"/>'
        sniffer = RunSniffer(stdout=stdout_xml_snippet)
        with patch.dict('salt.modules.zypper.__salt__', {'cmd.run_all': sniffer}):
            self.assertEqual(zypper.__zypper__.call('foo'), stdout_xml_snippet)
            self.assertEqual(len(sniffer.calls), 1)

            zypper.__zypper__.call('bar')
            self.assertEqual(len(sniffer.calls), 2)
            self.assertEqual(sniffer.calls[0]['args'][0], ['zypper', '--non-interactive', '--no-refresh', 'foo'])
            self.assertEqual(sniffer.calls[1]['args'][0], ['zypper', '--non-interactive', '--no-refresh', 'bar'])

            dom = zypper.__zypper__.xml.call('xml-test')
            self.assertEqual(sniffer.calls[2]['args'][0], ['zypper', '--non-interactive', '--xmlout',
                                                           '--no-refresh', 'xml-test'])
            self.assertEqual(dom.getElementsByTagName('test')[0].getAttribute('foo'), 'bar')

            zypper.__zypper__.refreshable.call('refresh-test')
            self.assertEqual(sniffer.calls[3]['args'][0], ['zypper', '--non-interactive', 'refresh-test'])

            zypper.__zypper__.nolock.call('no-locking-test')
            self.assertEqual(sniffer.calls[4].get('kwargs', {}).get('env', {}).get('ZYPP_READONLY_HACK'), "1")
            self.assertEqual(sniffer.calls[4].get('kwargs', {}).get('env', {}).get('SALT_RUNNING'), "1")

            zypper.__zypper__.call('locking-test')
            self.assertEqual(sniffer.calls[5].get('kwargs', {}).get('env', {}).get('ZYPP_READONLY_HACK'), None)
            self.assertEqual(sniffer.calls[5].get('kwargs', {}).get('env', {}).get('SALT_RUNNING'), "1")

        # Test exceptions
        stdout_xml_snippet = '<?xml version="1.0"?><stream><message type="error">Booya!</message></stream>'
        sniffer = RunSniffer(stdout=stdout_xml_snippet, retcode=1)
        with patch.dict('salt.modules.zypper.__salt__', {'cmd.run_all': sniffer}):
            with self.assertRaisesRegexp(CommandExecutionError, '^Zypper command failure: Booya!$'):
                zypper.__zypper__.xml.call('crashme')

            with self.assertRaisesRegexp(CommandExecutionError, "^Zypper command failure: Check Zypper's logs.$"):
                zypper.__zypper__.call('crashme again')

            zypper.__zypper__.noraise.call('stay quiet')
            self.assertEqual(zypper.__zypper__.error_msg, "Check Zypper's logs.")

    def test_list_upgrades_error_handling(self):
        '''
        Test error handling in the list package upgrades.
        :return:
        '''
        # Test handled errors
        ref_out = {
            'stdout': '''<?xml version='1.0'?>
<stream>
 <message type="info">Refreshing service &apos;container-suseconnect&apos;.</message>
 <message type="error">Some handled zypper internal error</message>
 <message type="error">Another zypper internal error</message>
</stream>
            ''',
            'stderr': '',
            'retcode': 1,
        }
        with patch.dict('salt.modules.zypper.__salt__', {'cmd.run_all': MagicMock(return_value=ref_out)}):
            with self.assertRaisesRegexp(CommandExecutionError,
                    "^Zypper command failure: Some handled zypper internal error\nAnother zypper internal error$"):
                zypper.list_upgrades(refresh=False)

        # Test unhandled error
        ref_out = {
            'retcode': 1,
            'stdout': '',
            'stderr': ''
        }
        with patch.dict('salt.modules.zypper.__salt__', {'cmd.run_all': MagicMock(return_value=ref_out)}):
            with self.assertRaisesRegexp(CommandExecutionError, "^Zypper command failure: Check Zypper's logs.$"):
                zypper.list_upgrades(refresh=False)

    def test_list_products(self):
        '''
        List products test.
        '''
        for filename, test_data in {
            'zypper-products-sle12sp1.xml': {
                'name': ['SLES', 'SLES', 'SUSE-Manager-Proxy',
                         'SUSE-Manager-Server', 'sle-manager-tools-beta',
                         'sle-manager-tools-beta-broken-eol', 'sle-manager-tools-beta-no-eol'],
                'vendor': 'SUSE LLC <https://www.suse.com/>',
                'release': ['0', '0', '0', '0', '0', '0', '0'],
                'productline': [None, None, None, None, None, None, 'sles'],
                'eol_t': [None, 0, 1509408000, 1522454400, 1522454400, 1730332800, 1730332800],
                'isbase': [False, False, False, False, False, False, True],
                'installed': [False, False, False, False, False, False, True],
                'registerrelease': [None, None, None, None, None, None, '123'],
            },
            'zypper-products-sle11sp3.xml': {
                'name': ['SUSE-Manager-Server', 'SUSE-Manager-Server', 'SUSE-Manager-Server-Broken-EOL',
                         'SUSE_SLES', 'SUSE_SLES', 'SUSE_SLES', 'SUSE_SLES-SP4-migration'],
                'vendor': 'SUSE LINUX Products GmbH, Nuernberg, Germany',
                'release': ['1.138', '1.2', '1.2', '1.2', '1.201', '1.201', '1.4'],
                'productline': [None, None, None, None, None, 'manager', 'manager'],
                'eol_t': [None, 0, 0, 0, 0, 0, 0],
                'isbase': [False, False, False, False, False, True, True],
                'installed': [False, False, False, False, False, True, True],
                'registerrelease': [None, None, None, None, None, None, "42"],
            }}.items():

            ref_out = {
                    'retcode': 0,