#!/usr/bin/env python
# ***** BEGIN LICENSE BLOCK *****
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at http://mozilla.org/MPL/2.0/.
# ***** END LICENSE BLOCK *****


import copy
import os
import pprint
import sys
import urllib

# load modules from parent dir
sys.path.insert(1, os.path.dirname(os.path.dirname(sys.path[0])))

from mozharness.base.python import PreScriptAction
from mozharness.mozilla.buildbot import TBPL_SUCCESS, TBPL_WARNING, EXIT_STATUS_DICT
from mozharness.mozilla.testing.firefox_ui_tests import (
    FirefoxUIUpdateTests,
    firefox_ui_update_config_options
)


# Command line arguments for release update tests
firefox_ui_update_release_config_options = [
    [['--build-number'], {
        'dest': 'build_number',
        'help': 'Build number of release, eg: 2',
    }],
    [['--limit-locales'], {
        'dest': 'limit_locales',
        'default': -1,
        'type': int,
        'help': 'Limit the number of locales to run.',
    }],
    [['--release-update-config'], {
        'dest': 'release_update_config',
        'help': 'Name of the release update verification config file to use.',
    }],
    [['--this-chunk'], {
        'dest': 'this_chunk',
        'default': 1,
        'help': 'What chunk of locales to process.',
    }],
    [['--tools-repo'], {
        'dest': 'tools_repo',
        'default': 'http://hg.mozilla.org/build/tools',
        'help': 'Which tools repo to check out',
    }],
    [['--tools-tag'], {
        'dest': 'tools_tag',
        'help': 'Which revision/tag to use for the tools repository.',
    }],
    [['--total-chunks'], {
        'dest': 'total_chunks',
        'default': 1,
        'help': 'Total chunks to dive the locales into.',
    }],
] + copy.deepcopy(firefox_ui_update_config_options)


class ReleaseFirefoxUIUpdateTests(FirefoxUIUpdateTests):

    def __init__(self):
        all_actions = [
            'clobber',
            'checkout',
            'create-virtualenv',
            'query_minidump_stackwalk',
            'read-release-update-config',
            'run-tests',
        ]

        super(ReleaseFirefoxUIUpdateTests, self).__init__(
            all_actions=all_actions,
            default_actions=all_actions,
            config_options=firefox_ui_update_release_config_options,
            append_env_variables_from_configs=True,
        )

        self.tools_repo = self.config.get('tools_repo')
        self.tools_tag = self.config.get('tools_tag')

        assert self.tools_repo and self.tools_tag, \
            'Without the "--tools-tag" we can\'t clone the releng\'s tools repository.'

        self.limit_locales = int(self.config.get('limit_locales'))

        # This will be a list containing one item per release based on configs
        # from tools/release/updates/*cfg
        self.releases = None

    def checkout(self):
        """
        We checkout the tools repository and update to the right branch
        for it.
        """
        dirs = self.query_abs_dirs()

        super(ReleaseFirefoxUIUpdateTests, self).checkout()

        self.vcs_checkout(
            repo=self.tools_repo,
            dest=dirs['abs_tools_dir'],
            branch=self.tools_tag,
            vcs='hg'
        )

    def query_abs_dirs(self):
        if self.abs_dirs:
            return self.abs_dirs

        abs_dirs = super(ReleaseFirefoxUIUpdateTests, self).query_abs_dirs()
        dirs = {
            'abs_tools_dir': os.path.join(abs_dirs['abs_work_dir'], 'tools'),
        }

        for key in dirs:
            if key not in abs_dirs:
                abs_dirs[key] = dirs[key]
        self.abs_dirs = abs_dirs

        return self.abs_dirs

    def read_release_update_config(self):
        '''
        Builds a testing matrix based on an update verification configuration
        file under the tools repository (release/updates/*.cfg).

        Each release info line of the update verification files look similar to the following.

        NOTE: This shows each pair of information as a new line but in reality
        there is one white space separting them. We only show the values we care for.

            release="38.0"
            platform="Linux_x86_64-gcc3"
            build_id="20150429135941"
            locales="ach af ... zh-TW"
            channel="beta-localtest"
            from="/firefox/releases/38.0b9/linux-x86_64/%locale%/firefox-38.0b9.tar.bz2"
            ftp_server_from="http://archive.mozilla.org/pub"

        We will store this information in self.releases as a list of releases.

        NOTE: We will talk of full and quick releases. Full release info normally contains a subset
        of all locales (except for the most recent releases). A quick release has all locales,
        however, it misses the fields 'from' and 'ftp_server_from'.
        Both pairs of information complement each other but differ in such manner.
        '''
        dirs = self.query_abs_dirs()
        assert os.path.exists(dirs['abs_tools_dir']), \
            'Without the tools/ checkout we can\'t use releng\'s config parser.'

        if self.config.get('release_update_config'):
            # The config file is part of the tools repository. Make sure that if specified
            # we force a revision of that repository to be set.
            if self.tools_tag is None:
                self.fatal('Make sure to specify the --tools-tag')

            self.release_update_config = self.config['release_update_config']

        # Import the config parser
        sys.path.insert(1, os.path.join(dirs['abs_tools_dir'], 'lib', 'python'))
        from release.updates.verify import UpdateVerifyConfig

        uvc = UpdateVerifyConfig()
        config_file = os.path.join(dirs['abs_tools_dir'], 'release', 'updates',
                                   self.config['release_update_config'])
        uvc.read(config_file)
        if not hasattr(self, 'update_channel'):
            self.update_channel = uvc.channel

        # Filter out any releases that are less than Gecko 38
        uvc.releases = [r for r in uvc.releases
                        if int(r['release'].split('.')[0]) >= 38]

        temp_releases = []
        for rel_info in uvc.releases:
            # This is the full release info
            if 'from' in rel_info and rel_info['from'] is not None:
                # Let's find the associated quick release which contains the remaining locales
                # for all releases except for the most recent release which contain all locales
                quick_release = uvc.getRelease(build_id=rel_info['build_id'], from_path=None)
                if quick_release != {}:
                    rel_info['locales'] = sorted(rel_info['locales'] + quick_release['locales'])
                temp_releases.append(rel_info)

        uvc.releases = temp_releases
        chunked_config = uvc.getChunk(
            chunks=int(self.config['total_chunks']),
            thisChunk=int(self.config['this_chunk'])
        )

        self.releases = chunked_config.releases

    @PreScriptAction('run-tests')
    def _pre_run_tests(self, action):
        assert ('release_update_config' in self.config or
                self.installer_url or self.installer_path), \
            'Either specify --update-verify-config, --installer-url or --installer-path.'

    def run_tests(self):
        dirs = self.query_abs_dirs()

        # We don't want multiple outputs of the same environment information. To prevent
        # that, we can't make it an argument of run_command and have to print it on our own.
        self.info('Using env: {}'.format(pprint.pformat(self.query_env())))

        results = {}

        locales_counter = 0
        for rel_info in sorted(self.releases, key=lambda release: release['build_id']):
            build_id = rel_info['build_id']
            results[build_id] = {}

            self.info('About to run {buildid} {path} - {num_locales} locales'.format(
                buildid=build_id,
                path=rel_info['from'],
                num_locales=len(rel_info['locales'])
            ))

            # Each locale gets a fresh port to avoid address in use errors in case of
            # tests that time out unexpectedly.
            marionette_port = 2827
            for locale in rel_info['locales']:
                locales_counter += 1
                self.info('Running {buildid} {locale}'.format(buildid=build_id,
                                                              locale=locale))

                if self.limit_locales > -1 and locales_counter > self.limit_locales:
                    self.info('We have reached the limit of locales we were intending to run')
                    break

                if self.config['dry_run']:
                    continue

                # Determine from where to download the file
                installer_url = '{server}/{fragment}'.format(
                    server=rel_info['ftp_server_from'],
                    fragment=urllib.quote(rel_info['from'].replace('%locale%', locale))
                )
                installer_path = self.download_file(
                    url=installer_url,
                    parent_dir=dirs['abs_work_dir']
                )

                binary_path = self.install_app(app=self.config.get('application'),
                                               installer_path=installer_path)

                marionette_port += 1

                retcode = self.run_test(
                    binary_path=binary_path,
                    env=self.query_env(avoid_host_env=True),
                    marionette_port=marionette_port,
                )

                self.uninstall_app()

                # Remove installer which is not needed anymore
                self.info('Removing {}'.format(installer_path))
                os.remove(installer_path)

                if retcode:
                    self.warning('FAIL: {} has failed.'.format(sys.argv[0]))

                    base_cmd = 'python {command} --firefox-ui-branch {branch} ' \
                        '--release-update-config {config} --tools-tag {tag}'.format(
                            command=sys.argv[0],
                            branch=self.firefox_ui_branch,
                            config=self.release_update_config,
                            tag=self.tools_tag
                        )

                    for config in self.config['config_files']:
                        base_cmd += ' --cfg {}'.format(config)

                    if self.symbols_url:
                        base_cmd += ' --symbols-path {}'.format(self.symbols_url)

                    base_cmd += ' --installer-url {}'.format(installer_url)

                    self.info('You can run the *specific* locale on the same machine with:')
                    self.info(base_cmd)

                    self.info('You can run the *specific* locale on *your* machine with:')
                    self.info('{} --cfg developer_config.py'.format(base_cmd))

                results[build_id][locale] = retcode

                self.info('Completed {buildid} {locale} with return code: {retcode}'.format(
                    buildid=build_id,
                    locale=locale,
                    retcode=retcode))

            if self.limit_locales > -1 and locales_counter > self.limit_locales:
                break

        # Determine which locales have failed and set scripts exit code
        exit_status = TBPL_SUCCESS
        for build_id in sorted(results.keys()):
            failed_locales = []
            for locale in sorted(results[build_id].keys()):
                if results[build_id][locale] != 0:
                    failed_locales.append(locale)

            if failed_locales:
                if exit_status == TBPL_SUCCESS:
                    self.info('\nSUMMARY - Failed locales for {}:'.format(self.cli_script))
                    self.info('====================================================')
                    exit_status = TBPL_WARNING

                self.info(build_id)
                self.info('  {}'.format(', '.join(failed_locales)))

        self.return_code = EXIT_STATUS_DICT[exit_status]


if __name__ == '__main__':
    myScript = ReleaseFirefoxUIUpdateTests()
    myScript.run_and_exit()
