# -*- coding: utf-8 -*-
'''
Tests for the archive state
'''
# Import python libs
from __future__ import absolute_import, print_function, unicode_literals
import os
import shutil
import textwrap

# Import Salt Testing libs
from tests.support.case import ModuleCase
from tests.support.unit import skipIf
from tests.support.paths import TMP
from tests.support.helpers import destructiveTest

# Import salt libs
import salt.utils.files
import salt.utils.path
import salt.utils.platform
import salt.utils.stringutils

# Import 3rd party libs
from salt.ext import six
try:
    import zipfile  # pylint: disable=W0611
    HAS_ZIPFILE = True
except ImportError:
    HAS_ZIPFILE = False


@destructiveTest
class ArchiveTest(ModuleCase):
    '''
    Validate the archive module
    '''
    # Base path used for test artifacts
    base_path = os.path.join(TMP, 'modules', 'archive')

    def _set_artifact_paths(self, arch_fmt):
        '''
        Define the paths for the source, archive, and destination files

        :param str arch_fmt: The archive format used in the test
        '''
        self.src = os.path.join(self.base_path, '{0}_src_dir'.format(arch_fmt))
        self.src_file = os.path.join(self.src, 'file')
        self.arch = os.path.join(self.base_path, 'archive.{0}'.format(arch_fmt))
        self.dst = os.path.join(self.base_path, '{0}_dst_dir'.format(arch_fmt))

    def _set_up(self, arch_fmt, unicode_filename=False):
        '''
        Create source file tree and destination directory

        :param str arch_fmt: The archive format used in the test
        '''
        self._set_artifact_paths(arch_fmt)

        # Remove the artifacts if any present
        if any([os.path.exists(f) for f in (self.src, self.arch, self.dst)]):
            self._tear_down()
            self._set_artifact_paths(arch_fmt)

        # Create source
        os.makedirs(self.src)
        if unicode_filename:
            filename = 'file®'
        else:
            filename = 'file'
        with salt.utils.files.fopen(os.path.join(self.src, filename), 'wb') as theorem:
            if six.PY3 and salt.utils.platform.is_windows():
                encoding = 'utf-8'
            else:
                encoding = None
            theorem.write(salt.utils.stringutils.to_bytes(textwrap.dedent('''\
                Compression theorem of computational complexity theory:

                Given a Gödel numbering $φ$ of the computable functions and a
                Blum complexity measure $Φ$ where a complexity class for a
                boundary function $f$ is defined as

                    $\\mathrm C(f) := \\{φ_i ∈ \\mathbb R^{(1)} | (∀^∞ x) Φ_i(x) ≤ f(x)\\}$.

                Then there exists a total computable function $f$ so that for
                all $i$

                    $\\mathrm{Dom}(φ_i) = \\mathrm{Dom}(φ_{f(i)})$

                and

                    $\\mathrm C(φ_i) ⊊ \\mathrm{C}(φ_{f(i)})$.
            '''), encoding=encoding))

        # Create destination
        os.makedirs(self.dst)

    def _tear_down(self):
        '''
        Remove source file tree, archive, and destination file tree
        '''
        for f in (self.src, self.arch, self.dst):
            if os.path.exists(f):
                if os.path.isdir(f):
                    shutil.rmtree(f, ignore_errors=True)
                else:
                    os.remove(f)
        del self.dst
        del self.src
        del self.arch
        del self.src_file

    def _assert_artifacts_in_ret(self, ret, file_only=False, unix_sep=False):
        '''
        Assert that the artifact source files are printed in the source command
        output
        '''

        def normdir(path):
            normdir = os.path.normcase(os.path.abspath(path))
            if salt.utils.platform.is_windows():
                # Remove the drive portion of path
                if len(normdir) >= 2 and normdir[1] == ':':
                    normdir = normdir.split(':', 1)[1]
            normdir = normdir.lstrip(os.path.sep)
            # Unzipped paths might have unix line endings
            if unix_sep:
                normdir = normdir.replace(os.path.sep, '/')
            return normdir

        # Try to find source directory and file in output lines
        dir_in_ret = None
        file_in_ret = None
        for line in ret:
            if normdir(self.src) in os.path.normcase(line) \
            and not normdir(self.src_file) in os.path.normcase(line):
                dir_in_ret = True
            if normdir(self.src_file) in os.path.normcase(line):
                file_in_ret = True

        # Assert number of lines, reporting of source directory and file
        self.assertTrue(len(ret) >= 1 if file_only else 2)
        if not file_only:
            self.assertTrue(dir_in_ret)
        self.assertTrue(file_in_ret)

    @skipIf(not salt.utils.path.which('tar'), 'Cannot find tar executable')
    def test_tar_pack(self):
        '''
        Validate using the tar function to create archives
        '''
        self._set_up(arch_fmt='tar')

        # Test create archive
        ret = self.run_function('archive.tar', ['-cvf', self.arch], sources=self.src)
        self.assertTrue(isinstance(ret, list), six.text_type(ret))
        self._assert_artifacts_in_ret(ret)

        self._tear_down()

    @skipIf(not salt.utils.path.which('tar'), 'Cannot find tar executable')
    def test_tar_unpack(self):
        '''
        Validate using the tar function to extract archives
        '''
        self._set_up(arch_fmt='tar')
        self.run_function('archive.tar', ['-cvf', self.arch], sources=self.src)

        # Test extract archive
        ret = self.run_function('archive.tar', ['-xvf', self.arch], dest=self.dst)
        self.assertTrue(isinstance(ret, list), six.text_type(ret))
        self._assert_artifacts_in_ret(ret)

        self._tear_down()

    @skipIf(not salt.utils.path.which('tar'), 'Cannot find tar executable')
    def test_tar_pack_unicode(self):
        '''
        Validate using the tar function to create archives
        '''
        self._set_up(arch_fmt='tar', unicode_filename=True)

        # Test create archive
        ret = self.run_function('archive.tar', ['-cvf', self.arch], sources=self.src)
        self.assertTrue(isinstance(ret, list), six.text_type(ret))
        self._assert_artifacts_in_ret(ret)

        self._tear_down()

    @skipIf(not salt.utils.path.which('tar'), 'Cannot find tar executable')
    def test_tar_unpack_unicode(self):
        '''
        Validate using the tar function to extract archives
        '''
        self._set_up(arch_fmt='tar', unicode_filename=True)
        self.run_function('archive.tar', ['-cvf', self.arch], sources=self.src)

        # Test extract archive
        ret = self.run_function('archive.tar', ['-xvf', self.arch], dest=self.dst)
        self.assertTrue(isinstance(ret, list), six.text_type(ret))
        self._assert_artifacts_in_ret(ret)

        self._tear_down()

    @skipIf(not salt.utils.path.which('tar'), 'Cannot find tar executable')
    def test_tar_list_unicode(self):
        '''
        Validate using the tar function to extract archives
        '''
        self._set_up(arch_fmt='tar', unicode_filename=True)
        self.run_function('archive.tar', ['-cvf', self.arch], sources=self.src)

        # Test list archive
        ret = self.run_function('archive.list', name=self.arch)
        self.assertTrue(isinstance(ret, list), six.text_type(ret))
        self._assert_artifacts_in_ret(ret)

        self._tear_down()

    @skipIf(not salt.utils.path.which('gzip'), 'Cannot find gzip executable')
    def test_gzip(self):
        '''
        Validate using the gzip function
        '''
        self._set_up(arch_fmt='gz')

        # Test create archive
        ret = self.run_function('archive.gzip', [self.src_file], options='-v')
        self.assertTrue(isinstance(ret, list), six.text_type(ret))
        self._assert_artifacts_in_ret(ret, file_only=True)

        self._tear_down()

    @skipIf(not salt.utils.path.which('gzip'), 'Cannot find gzip executable')
    @skipIf(not salt.utils.path.which('gunzip'), 'Cannot find gunzip executable')
    def test_gunzip(self):
        '''
        Validate using the gunzip function
        '''
        self._set_up(arch_fmt='gz')
        self.run_function('archive.gzip', [self.src_file], options='-v')

        # Test extract archive
        ret = self.run_function('archive.gunzip', [self.src_file + '.gz'], options='-v')
        self.assertTrue(isinstance(ret, list), six.text_type(ret))
        self._assert_artifacts_in_ret(ret, file_only=True)

        self._tear_down()

    @skipIf(not salt.utils.path.which('zip'), 'Cannot find zip executable')
    def test_cmd_zip(self):
        '''
        Validate using the cmd_zip function
        '''
        self._set_up(arch_fmt='zip')

        # Test create archive
        ret = self.run_function('archive.cmd_zip', [self.arch, self.src])
        self.assertTrue(isinstance(ret, list), six.text_type(ret))
        self._assert_artifacts_in_ret(ret)

        self._tear_down()

    @skipIf(not salt.utils.path.which('zip'), 'Cannot find zip executable')
    @skipIf(not salt.utils.path.which('unzip'), 'Cannot find unzip executable')
    def test_cmd_unzip(self):
        '''
        Validate using the cmd_unzip function
        '''
        self._set_up(arch_fmt='zip')
        self.run_function('archive.cmd_zip', [self.arch, self.src])

        # Test create archive
        ret = self.run_function('archive.cmd_unzip', [self.arch, self.dst])
        self.assertTrue(isinstance(ret, list), six.text_type(ret))
        self._assert_artifacts_in_ret(ret)

        self._tear_down()

    @skipIf(not HAS_ZIPFILE, 'Cannot find zipfile python module')
    def test_zip(self):
        '''
        Validate using the zip function
        '''
        self._set_up(arch_fmt='zip')

        # Test create archive
        ret = self.run_function('archive.zip', [self.arch, self.src])
        self.assertTrue(isinstance(ret, list), six.text_type(ret))
        self._assert_artifacts_in_ret(ret)

        self._tear_down()

    @skipIf(not HAS_ZIPFILE, 'Cannot find zipfile python module')
    def test_unzip(self):
        '''
        Validate using the unzip function
        '''
        self._set_up(arch_fmt='zip')
        self.run_function('archive.zip', [self.arch, self.src])

        # Test create archive
        ret = self.run_function('archive.unzip', [self.arch, self.dst])
        self.assertTrue(isinstance(ret, list), six.text_type(ret))
        self._assert_artifacts_in_ret(ret, unix_sep=False)

        self._tear_down()

    @skipIf(not salt.utils.path.which('rar'), 'Cannot find rar executable')
    def test_rar(self):
        '''
        Validate using the rar function
        '''
        self._set_up(arch_fmt='rar')

        # Test create archive
        ret = self.run_function('archive.rar', [self.arch, self.src])
        self.assertTrue(isinstance(ret, list), six.text_type(ret))
        self._assert_artifacts_in_ret(ret)

        self._tear_down()

    @skipIf(not salt.utils.path.which('rar'), 'Cannot find rar executable')
    @skipIf(not salt.utils.path.which('unrar'), 'Cannot find unrar executable')
    def test_unrar(self):
        '''
        Validate using the unrar function
        '''
        self._set_up(arch_fmt='rar')
        self.run_function('archive.rar', [self.arch, self.src])

        # Test create archive
        ret = self.run_function('archive.unrar', [self.arch, self.dst])
        self.assertTrue(isinstance(ret, list), six.text_type(ret))
        self._assert_artifacts_in_ret(ret)

        self._tear_down()
