# 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/.

import shutil
from subprocess import CalledProcessError

from django.core.management.base import CommandError
from django.conf import settings

from fluent.syntax.parser import FluentParser, ParseError

from lib.l10n_utils.fluent import fluent_l10n, get_metadata, write_metadata
from ._ftl_repo_base import FTLRepoCommand


GIT_COMMIT_EMAIL = 'meao-bots+mozmarrobot@mozilla.com'
GIT_COMMIT_NAME = 'MozMEAO Bot'


class NoisyFluentParser(FluentParser):
    """A parser that will raise exceptions.

    The one from fluent.syntax doesn't raise exceptions, but will
    return instances of fluent.syntax.ast.Junk instead.
    """
    def get_entry_or_junk(self, ps):
        """Allow the ParseError to bubble up"""
        entry = self.get_entry(ps)
        ps.expect_line_end()
        return entry


class Command(FTLRepoCommand):
    help = 'Processes .ftl files from l10n team for use in bedrock'
    parser = None

    def add_arguments(self, parser):
        super().add_arguments(parser)
        parser.add_argument('--push', action='store_true', dest='push', default=False,
                            help='Push the changes to the MEAO Fluent files repo.')

    def handle(self, *args, **options):
        super().handle(*args, **options)
        self.parser = NoisyFluentParser()
        self.update_fluent_files()
        self.update_l10n_team_files()
        no_errors = self.copy_ftl_files()
        self.set_activation()
        self.copy_configs()
        if options['push']:
            changes = self.commit_changes()
            if changes:
                self.push_changes()

        if not no_errors:
            raise CommandError('Some errors were discovered in some .ftl files and they were not updated.'
                               'See above for details.')

    def config_fluent_repo(self):
        """Set user config so that committing will work"""
        self.meao_repo.git('config', 'user.email', GIT_COMMIT_EMAIL)
        self.meao_repo.git('config', 'user.name', GIT_COMMIT_NAME)

    def commit_changes(self):
        self.config_fluent_repo()
        self.meao_repo.git('add', '.')
        try:
            self.meao_repo.git('commit', '-m', 'Update files from l10n repo')
        except CalledProcessError:
            self.stdout.write('No changes to commit')
            return False

        self.stdout.write('Committed changes to local repo')
        return True

    def push_changes(self):
        try:
            self.meao_repo.git('push', self.git_push_url, 'HEAD:master')
        except CalledProcessError:
            raise CommandError(f'There was a problem pushing to {self.meao_repo.remote_url}')

        commit = self.meao_repo.git('rev-parse', '--short', 'HEAD')
        self.stdout.write(f'Pushed {commit} to {self.meao_repo.remote_url}')

    @property
    def git_push_url(self):
        if not settings.FLUENT_REPO_AUTH:
            raise CommandError('Git push authentication not configured')

        return self.meao_repo.remote_url_auth(settings.FLUENT_REPO_AUTH)

    def _copy_file(self, filepath):
        relative_filepath = filepath.relative_to(self.l10n_repo.path)
        to_filepath = self.meao_repo.path.joinpath(relative_filepath)
        to_filepath.parent.mkdir(parents=True, exist_ok=True)
        shutil.copy2(str(filepath), str(to_filepath))
        self.stdout.write('.', ending='')
        self.stdout.flush()

    def copy_configs(self):
        count = 0
        for filepath in self.l10n_repo.path.rglob('*.toml'):
            self._copy_file(filepath)
            count += 1

        self.stdout.write(f'\nCopied {count} .toml files')

    def copy_ftl_files(self):
        count = 0
        errors = []
        for filepath in self.l10n_repo.path.rglob('*.ftl'):
            if not self.lint_ftl_file(filepath):
                errors.append(filepath.relative_to(self.l10n_repo.path))
                continue

            self._copy_file(filepath)
            count += 1

        self.stdout.write(f'\nCopied {count} .ftl files')
        if errors:
            self.stdout.write('The following files had parse errors and were not copied:')
            for fpath in errors:
                self.stdout.write(f'- {fpath}')
            return False

        return True

    def lint_ftl_file(self, filepath):
        with filepath.open() as ftl:
            try:
                self.parser.parse(ftl.read())
            except ParseError:
                return False

            return True

    def set_activation(self):
        updated_ftl = set()
        modified, _ = self.meao_repo.modified_files()
        for fname in modified:
            if not fname.endswith('.ftl'):
                continue

            locale, ftl_name = fname.split('/', 1)
            updated_ftl.add(ftl_name)

        for ftl_name in updated_ftl:
            self.calculate_activation(ftl_name)

    def calculate_activation(self, ftl_file):
        translations = self.meao_repo.path.glob(f'*/{ftl_file}')
        metadata = get_metadata(ftl_file)
        active_locales = metadata.get('active_locales', [])
        inactive_locales = metadata.get('inactive_locales', [])
        percent_required = metadata.get('percent_required', settings.FLUENT_DEFAULT_PERCENT_REQUIRED)
        all_locales = {str(x.relative_to(self.meao_repo.path)).split('/', 1)[0] for x in translations}
        locales_to_check = all_locales.difference(['en'], active_locales, inactive_locales)
        new_activations = []
        for locale in locales_to_check:
            l10n = fluent_l10n([locale, 'en'], [ftl_file])
            if not l10n.has_required_messages:
                continue

            percent_trans = l10n.percent_translated
            if percent_trans < percent_required:
                continue

            new_activations.append(locale)

        if new_activations:
            active_locales.extend(new_activations)
            metadata['active_locales'] = sorted(active_locales)
            write_metadata(ftl_file, metadata)
            self.stdout.write(f'Activated {len(new_activations)} new locales for {ftl_file}')
