# All Rights Reserved.
#
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
#    not use this file except in compliance with the License. You may obtain
#    a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#    License for the specific language governing permissions and limitations
#    under the License.

"""Look for governed projects that have their tagging ACLs misconfigured.

"""

import argparse
import configparser
import json
import os.path
import urllib

import appdirs
from openstack_governance import governance
import requests
from requests.packages import urllib3

import openstack_releases
from openstack_releases import defaults
from openstack_releases import deliverable

# Disable warnings about insecure connections.
urllib3.disable_warnings()


IGNORED_TEAMS = [
    'Infrastructure',
    'OpenStack Charms',
]

ALLOWED = [
    'library-release',
    'Release Managers',
    'openstack-chef-release',
    'xstatic-release',
    'release-tools-core',
    'releases-core',
]


class GerritClient(object):

    BASE = 'https://review.openstack.org:443/a/'

    def __init__(self, user, password):
        self._user = user
        self._password = password
        self._auth = requests.auth.HTTPDigestAuth(
            self._user,
            self._password,
        )
        self._groups = {}

    def _mk_url(self, api, *args):
        encoded = [
            urllib.parse.quote_plus(a)
            for a in args
        ]
        return self.BASE + api.format(*encoded)

    def _get(self, url):
        response = requests.get(url, auth=self._auth)
        if response.status_code == 404:
            raise ValueError(404)

        # strip off first few chars because 'the JSON response body starts with
        # a magic prefix line that must be stripped before feeding the rest of
        # the response body to a JSON parser'
        # https://review.opendev.org/Documentation/rest-api.html
        # print(response.text)
        return json.loads(response.text[5:])

    def get_access(self, repo):
        url = self._mk_url('projects/{}/access', repo)
        return self._get(url)

    def get_group(self, group_id):
        if group_id in self._groups:
            return self._groups[group_id]
        url = self._mk_url('groups/{}', group_id)
        data = self._get(url)
        self._groups[group_id] = data
        return data


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument(
        '--verbose', '-v',
        action='store_true',
        default=False,
    )
    parser.add_argument(
        '--deliverables-dir',
        default=openstack_releases.deliverable_dir,
        help='location of deliverable files',
    )
    args = parser.parse_args()

    config_filename = os.path.join(
        appdirs.user_config_dir('openstack-release', 'openstack'),
        'gerrit.ini',
    )
    config = configparser.ConfigParser()
    config.read(config_filename, encoding='utf-8')

    if not config.has_option('DEFAULT', 'username'):
        parser.error('No username set in {}'.format(config_filename))
    if not config.has_option('DEFAULT', 'password'):
        parser.error('No password set in {}'.format(config_filename))

    gov_data = governance.Governance.from_remote_repo()

    # Some deliverables were independent at one time but might not be
    # any more, so compare the independent list with the current
    # release series.
    all_independent_deliverables = set(
        name
        for team, series, name, deliv in deliverable.Deliverables(
            root_dir=args.deliverables_dir,
            collapse_history=True,
        ).get_deliverables(None, None)
    )
    current_deliverables = set(
        name
        for team, series, name, deliv in deliverable.Deliverables(
            root_dir=args.deliverables_dir,
            collapse_history=True,
        ).get_deliverables(None, defaults.RELEASE)
    )
    independent_deliverables = all_independent_deliverables.difference(
        current_deliverables)

    gerrit = GerritClient(
        config['DEFAULT']['username'],
        config['DEFAULT']['password'],
    )

    for repo in gov_data.get_repositories():

        if repo.name.endswith('-specs'):
            continue
        if 'cookiecutter' in repo.name:
            continue

        if repo.deliverable.team.name in IGNORED_TEAMS:
            if args.verbose:
                print('{}: ignoring {} team'.format(
                    repo.name, repo.deliverable.team.name))
            continue

        if repo.deliverable.name in independent_deliverables:
            if args.verbose:
                print('{}: ignoring independent deliverable'.format(
                    repo.name))
            continue

        acls = gerrit.get_access(repo.name)
        local_tag_acls = acls.get('local', {}).get('refs/tags/*', {})
        if local_tag_acls:
            rules = local_tag_acls.get('permissions', {}).get(
                'pushSignedTag', {}).get('rules', {})
            if not rules and args.verbose:
                print('{}: OK'.format(repo.name))

            for group_id, permissions in rules.items():
                group_details = gerrit.get_group(group_id)
                group_name = group_details['name']
                if group_name in ALLOWED:
                    if args.verbose:
                        print('{}: {} pushSignedTag OK'.format(
                            repo.name, group_name))
                    continue
                if args.verbose:
                    print('{}: {} pushSignedTag WARNING'.format(
                        repo.name, group_name))
                else:
                    print('{}: {} pushSignedTag'.format(
                        repo.name, group_name))