#!/usr/bin/env python3

# http://www.drdobbs.com/testing/unit-testing-with-python/240165163

import git
import inspect
import logging
import optparse
import os
import shutil
import sys
import tempfile
import unittest
import yaml
from binascii import unhexlify

localmodule = os.path.realpath(
    os.path.join(os.path.dirname(inspect.getfile(inspect.currentframe())), '..'))
print('localmodule: ' + localmodule)
if localmodule not in sys.path:
    sys.path.insert(0, localmodule)

import fdroidserver.common
import fdroidserver.metadata
import fdroidserver.update
from fdroidserver.common import FDroidPopen


class UpdateTest(unittest.TestCase):
    '''fdroid update'''

    def testInsertStoreMetadata(self):
        config = dict()
        fdroidserver.common.fill_config_defaults(config)
        config['accepted_formats'] = ('txt', 'yml')
        fdroidserver.update.config = config
        fdroidserver.update.options = fdroidserver.common.options
        os.chdir(os.path.join(localmodule, 'tests'))

        apps = dict()
        for packageName in ('info.guardianproject.urzip', 'org.videolan.vlc', 'obb.mainpatch.current'):
            apps[packageName] = dict()
            apps[packageName]['id'] = packageName
            apps[packageName]['CurrentVersionCode'] = 0xcafebeef
        apps['info.guardianproject.urzip']['CurrentVersionCode'] = 100
        fdroidserver.update.insert_localized_app_metadata(apps)

        self.assertEqual(3, len(apps))
        for packageName, app in apps.items():
            self.assertTrue('localized' in app)
            self.assertTrue('en-US' in app['localized'])
            self.assertEqual(1, len(app['localized']))
            if packageName == 'info.guardianproject.urzip':
                self.assertEqual(5, len(app['localized']['en-US']))
                self.assertEqual('full description\n', app['localized']['en-US']['description'])
                self.assertEqual('title\n', app['localized']['en-US']['name'])
                self.assertEqual('short description\n', app['localized']['en-US']['summary'])
                self.assertEqual('video\n', app['localized']['en-US']['video'])
                self.assertEqual('100\n', app['localized']['en-US']['whatsNew'])
            elif packageName == 'org.videolan.vlc':
                self.assertEqual('icon.png', app['localized']['en-US']['icon'])
                self.assertEqual(9, len(app['localized']['en-US']['phoneScreenshots']))
                self.assertEqual(15, len(app['localized']['en-US']['sevenInchScreenshots']))
            elif packageName == 'obb.mainpatch.current':
                self.assertEqual('icon.png', app['localized']['en-US']['icon'])
                self.assertEqual('featureGraphic.png', app['localized']['en-US']['featureGraphic'])
                self.assertEqual(1, len(app['localized']['en-US']['phoneScreenshots']))
                self.assertEqual(1, len(app['localized']['en-US']['sevenInchScreenshots']))

    def test_insert_triple_t_metadata(self):
        importer = os.path.join(localmodule, 'tests', 'tmp', 'importer')
        packageName = 'org.fdroid.ci.test.app'
        if not os.path.isdir(importer):
            logging.warning('skipping test_insert_triple_t_metadata, import.TestCase must run first!')
            return
        tmpdir = os.path.join(localmodule, '.testfiles')
        if not os.path.exists(tmpdir):
            os.makedirs(tmpdir)
        tmptestsdir = tempfile.mkdtemp(prefix='test_insert_triple_t_metadata-', dir=tmpdir)
        packageDir = os.path.join(tmptestsdir, 'build', packageName)
        shutil.copytree(importer, packageDir)

        # always use the same commit so these tests work when ci-test-app.git is updated
        repo = git.Repo(packageDir)
        for remote in repo.remotes:
            remote.fetch()
        repo.git.reset('--hard', 'b9e5d1a0d8d6fc31d4674b2f0514fef10762ed4f')
        repo.git.clean('-fdx')

        os.mkdir(os.path.join(tmptestsdir, 'metadata'))
        metadata = dict()
        metadata['Description'] = 'This is just a test app'
        with open(os.path.join(tmptestsdir, 'metadata', packageName + '.yml'), 'w') as fp:
            yaml.dump(metadata, fp)

        config = dict()
        fdroidserver.common.fill_config_defaults(config)
        config['accepted_formats'] = ('yml')
        fdroidserver.common.config = config
        fdroidserver.update.config = config
        fdroidserver.update.options = fdroidserver.common.options
        os.chdir(tmptestsdir)

        apps = fdroidserver.metadata.read_metadata(xref=True)
        fdroidserver.update.copy_triple_t_store_metadata(apps)

        # TODO ideally, this would compare the whole dict like in metadata.TestCase's test_read_metadata()
        correctlocales = [
            'ar', 'ast_ES', 'az', 'ca', 'ca_ES', 'cs-CZ', 'cs_CZ', 'da',
            'da-DK', 'de', 'de-DE', 'el', 'en-US', 'es', 'es-ES', 'es_ES', 'et',
            'fi', 'fr', 'fr-FR', 'he_IL', 'hi-IN', 'hi_IN', 'hu', 'id', 'it',
            'it-IT', 'it_IT', 'iw-IL', 'ja', 'ja-JP', 'kn_IN', 'ko', 'ko-KR',
            'ko_KR', 'lt', 'nb', 'nb_NO', 'nl', 'nl-NL', 'no', 'pl', 'pl-PL',
            'pl_PL', 'pt', 'pt-BR', 'pt-PT', 'pt_BR', 'ro', 'ro_RO', 'ru-RU',
            'ru_RU', 'sv-SE', 'sv_SE', 'te', 'tr', 'tr-TR', 'uk', 'uk_UA', 'vi',
            'vi_VN', 'zh-CN', 'zh_CN', 'zh_TW',
        ]
        locales = sorted(list(apps['org.fdroid.ci.test.app']['localized'].keys()))
        self.assertEqual(correctlocales, locales)

    def javagetsig(self, apkfile):
        getsig_dir = os.path.join(os.path.dirname(__file__), 'getsig')
        if not os.path.exists(getsig_dir + "/getsig.class"):
            logging.critical("getsig.class not found. To fix: cd '%s' && ./make.sh" % getsig_dir)
            sys.exit(1)
        # FDroidPopen needs some config to work
        config = dict()
        fdroidserver.common.fill_config_defaults(config)
        fdroidserver.common.config = config
        p = FDroidPopen(['java', '-cp', os.path.join(os.path.dirname(__file__), 'getsig'),
                         'getsig', os.path.join(os.getcwd(), apkfile)])
        sig = None
        for line in p.output.splitlines():
            if line.startswith('Result:'):
                sig = line[7:].strip()
                break
        if p.returncode == 0:
            return sig
        else:
            return None

    def testGoodGetsig(self):
        # config needed to use jarsigner and keytool
        config = dict()
        fdroidserver.common.fill_config_defaults(config)
        fdroidserver.update.config = config
        apkfile = os.path.join(os.path.dirname(__file__), 'urzip.apk')
        sig = self.javagetsig(apkfile)
        self.assertIsNotNone(sig, "sig is None")
        pysig = fdroidserver.update.getsig(apkfile)
        self.assertIsNotNone(pysig, "pysig is None")
        self.assertEqual(sig, fdroidserver.update.getsig(apkfile),
                         "python sig not equal to java sig!")
        self.assertEqual(len(sig), len(pysig),
                         "the length of the two sigs are different!")
        try:
            self.assertEqual(unhexlify(sig), unhexlify(pysig),
                             "the length of the two sigs are different!")
        except TypeError as e:
            print(e)
            self.assertTrue(False, 'TypeError!')

    def testBadGetsig(self):
        # config needed to use jarsigner and keytool
        config = dict()
        fdroidserver.common.fill_config_defaults(config)
        fdroidserver.update.config = config
        apkfile = os.path.join(os.path.dirname(__file__), 'urzip-badsig.apk')
        sig = self.javagetsig(apkfile)
        self.assertIsNone(sig, "sig should be None: " + str(sig))
        pysig = fdroidserver.update.getsig(apkfile)
        self.assertIsNone(pysig, "python sig should be None: " + str(sig))

        apkfile = os.path.join(os.path.dirname(__file__), 'urzip-badcert.apk')
        sig = self.javagetsig(apkfile)
        self.assertIsNone(sig, "sig should be None: " + str(sig))
        pysig = fdroidserver.update.getsig(apkfile)
        self.assertIsNone(pysig, "python sig should be None: " + str(sig))

    def testScanApksAndObbs(self):
        os.chdir(os.path.join(localmodule, 'tests'))
        if os.path.basename(os.getcwd()) != 'tests':
            raise Exception('This test must be run in the "tests/" subdir')

        config = dict()
        fdroidserver.common.fill_config_defaults(config)
        config['ndk_paths'] = dict()
        config['accepted_formats'] = ['json', 'txt', 'yml']
        fdroidserver.common.config = config
        fdroidserver.update.config = config

        fdroidserver.update.options = type('', (), {})()
        fdroidserver.update.options.clean = True
        fdroidserver.update.options.delete_unknown = True

        apps = fdroidserver.metadata.read_metadata(xref=True)
        knownapks = fdroidserver.common.KnownApks()
        apks, cachechanged = fdroidserver.update.scan_apks({}, 'repo', knownapks, False)
        self.assertEqual(len(apks), 6)
        apk = apks[0]
        self.assertEqual(apk['minSdkVersion'], '4')
        self.assertEqual(apk['targetSdkVersion'], '18')
        self.assertFalse('maxSdkVersion' in apk)

        fdroidserver.update.insert_obbs('repo', apps, apks)
        for apk in apks:
            if apk['packageName'] == 'obb.mainpatch.current':
                self.assertEqual(apk.get('obbMainFile'), 'main.1619.obb.mainpatch.current.obb')
                self.assertEqual(apk.get('obbPatchFile'), 'patch.1619.obb.mainpatch.current.obb')
            elif apk['packageName'] == 'obb.main.oldversion':
                self.assertEqual(apk.get('obbMainFile'), 'main.1434483388.obb.main.oldversion.obb')
                self.assertIsNone(apk.get('obbPatchFile'))
            elif apk['packageName'] == 'obb.main.twoversions':
                self.assertIsNone(apk.get('obbPatchFile'))
                if apk['versionCode'] == 1101613:
                    self.assertEqual(apk.get('obbMainFile'), 'main.1101613.obb.main.twoversions.obb')
                elif apk['versionCode'] == 1101615:
                    self.assertEqual(apk.get('obbMainFile'), 'main.1101615.obb.main.twoversions.obb')
                elif apk['versionCode'] == 1101617:
                    self.assertEqual(apk.get('obbMainFile'), 'main.1101615.obb.main.twoversions.obb')
                else:
                    self.assertTrue(False)
            elif apk['packageName'] == 'info.guardianproject.urzip':
                self.assertIsNone(apk.get('obbMainFile'))
                self.assertIsNone(apk.get('obbPatchFile'))

    def testScanApkMetadata(self):

        def _build_yaml_representer(dumper, data):
            '''Creates a YAML representation of a Build instance'''
            return dumper.represent_dict(data)

        config = dict()
        fdroidserver.common.fill_config_defaults(config)
        fdroidserver.update.config = config
        os.chdir(os.path.dirname(__file__))
        if os.path.basename(os.getcwd()) != 'tests':
            raise Exception('This test must be run in the "tests/" subdir')

        config['ndk_paths'] = dict()
        config['accepted_formats'] = ['json', 'txt', 'yml']
        fdroidserver.common.config = config
        fdroidserver.update.config = config

        fdroidserver.update.options = type('', (), {})()
        fdroidserver.update.options.clean = True
        fdroidserver.update.options.delete_unknown = True

        for icon_dir in fdroidserver.update.get_all_icon_dirs('repo'):
            if not os.path.exists(icon_dir):
                os.makedirs(icon_dir)

        knownapks = fdroidserver.common.KnownApks()
        apkList = ['../urzip.apk', '../org.dyndns.fules.ck_20.apk']

        for apkName in apkList:
            _, apk, cachechanged = fdroidserver.update.scan_apk({}, apkName, 'repo', knownapks, False)
            # Don't care about the date added to the repo and relative apkName
            del apk['added']
            del apk['apkName']
            # avoid AAPT application name bug
            del apk['name']

            savepath = os.path.join('metadata', 'apk', apk['packageName'] + '.yaml')
            # Uncomment to save APK metadata
            # with open(savepath, 'w') as f:
            #     yaml.add_representer(fdroidserver.metadata.Build, _build_yaml_representer)
            #     yaml.dump(apk, f, default_flow_style=False)

            with open(savepath, 'r') as f:
                frompickle = yaml.load(f)
            self.maxDiff = None
            self.assertEqual(apk, frompickle)

    def test_scan_invalid_apk(self):
        os.chdir(os.path.join(localmodule, 'tests'))
        if os.path.basename(os.getcwd()) != 'tests':
            raise Exception('This test must be run in the "tests/" subdir')

        config = dict()
        fdroidserver.common.fill_config_defaults(config)
        fdroidserver.common.config = config
        fdroidserver.update.config = config
        fdroidserver.update.options.delete_unknown = False

        knownapks = fdroidserver.common.KnownApks()
        apk = 'fake.ota.update_1234.zip'  # this is not an APK, scanning should fail
        (skip, apk, cachechanged) = fdroidserver.update.scan_apk({}, apk, 'repo', knownapks, False)

        self.assertTrue(skip)
        self.assertIsNone(apk)
        self.assertFalse(cachechanged)


if __name__ == "__main__":
    parser = optparse.OptionParser()
    parser.add_option("-v", "--verbose", action="store_true", default=False,
                      help="Spew out even more information than normal")
    (fdroidserver.common.options, args) = parser.parse_args(['--verbose'])

    newSuite = unittest.TestSuite()
    newSuite.addTest(unittest.makeSuite(UpdateTest))
    unittest.main()
