"""Jira class file."""

# import base64
import csv
import datetime

import json
import requests

from jira.client import JIRA


class Jira(object):
    """Jira class."""

    def __init__(
        self, username, password, server="https://company.atlassian.net", verbose=False
    ):
        """Initialize a JIRA class instance."""
        self.jira_server = server
        self.jira_username = username
        self.jira_password = password

        self.verbose = verbose

        self.base_url = self.jira_server
        self.headers = {
            "Accept": "application/json",
            "Content-Type": "application/json",
        }

        # authenticate
        if username and password:
            if self.verbose:
                print("Connecting to JIRA v3...")
            self.connect_v3()

    def connect_v3(self):
        """Connect to JIRA."""
        self.auth = (self.jira_username, self.jira_password)

    #
    # Group Members
    #
    def get_group_members(self, group, includeInactiveUsers=False):
        """Return a list of members in a group."""
        url = "{}/rest/api/3/group/member".format(self.base_url)
        params = {
            "groupname": group,
            "includeInactiveUsers": includeInactiveUsers,
            "maxResults": 1000,
        }
        response = requests.get(url, auth=self.auth, params=params).json()
        members = response.get("values", [])

        while not response["isLast"]:
            nextPage = response["nextPage"]
            response = requests.get(nextPage, auth=self.auth).json()
            members.extend(response.get("values", []))

        return members

    def get_group_members_dict(self, group, includeInactiveUsers=False):
        """Return a list of emails of members in a group."""
        members = self.get_group_members(group, includeInactiveUsers)
        emails = {}
        for m in members:
            if "emailAddress" not in m:
                print("ERROR: Email address not found for: {}".format(m["accountId"]))
                continue
            email = m["emailAddress"]
            emails[email] = m
        return emails

    #
    # Groups
    #
    def get_groups(self):
        """Return a list of JIRA Group names."""
        url = "{}/rest/api/3/groups/picker".format(self.base_url)
        params = {"maxResults": 1000}
        response = requests.get(url, auth=self.auth, params=params).json()
        groups = response["groups"]
        total = response["total"]
        if len(groups) < total:
            print("WARNING: {}".format(response["header"]))
        return groups

    def get_groups_dict(self):
        """Return a dict of JIRA Groups."""
        groups = {}
        for group in self.get_groups():
            name = group["name"]
            groups[name] = group
        return groups

    def get_groups_with_members(self):
        """Return na list of JIRA Groups."""
        groups_dict = self.get_groups_dict()
        for name in groups_dict:
            group = groups_dict[name]
            members = self.get_group_members(name)
            group["members"] = members
            yield group

    #
    # Update Groups
    #
    def add_user_to_group(self, accountId, groupname):
        """Add a user to a JIRA Group."""
        url = "{}/rest/api/3/group/user".format(self.base_url)
        body = {"accountId": accountId}
        params = {"groupname": groupname}
        return requests.post(url, auth=self.auth, json=body, params=params)

    def remove_user_from_group(self, accountId, groupname):
        """Remove a user from a JIRA Group."""
        url = "{}/rest/api/3/group/user".format(self.base_url)
        params = {"accountId": accountId, "groupname": groupname}
        return requests.delete(url, auth=self.auth, params=params)

    def get_members_to_add(self, current, members):
        """Return a list of members to add to a group."""
        add = []
        for email in members:
            if email not in current:
                users = self.get_user(email)
                if len(users) == 1:
                    add.append(users[0])
        return add

    def get_members_to_remove(self, current, members):
        """Return a list of members to remove from a group."""
        remove = []
        for email in current:
            if email not in members:
                remove.append(current[email])
        return remove

    def update_group_members(self, group, google_members):
        """Update the members of a group to match provided members list."""
        current = self.get_group_members_dict(group)
        add = self.get_members_to_add(current, google_members)
        remove = self.get_members_to_remove(current, google_members)

        if add:
            print("    Adding members:")
            for user in sorted(add, key=lambda x: x["emailAddress"]):
                accountId = user["accountId"]
                print("      + {}".format(user["emailAddress"]))
                self.add_user_to_group(accountId, group)

        if remove:
            print("    Removing members:")
            for user in sorted(remove, key=lambda x: x["emailAddress"]):
                accountId = user["accountId"]
                print("      - {}".format(user["emailAddress"]))
                self.remove_user_from_group(accountId, group)

    def _get_google_group_member_emails(self, members, users):
        """Return a list of emails for Google Group Members."""
        emails = []
        for m in members:
            uid = m["id"]
            email = m["email"]
            if uid not in users:
                print("ERROR: Google User not found: {} [{}]".format(email, uid))
                continue
            user = users[uid]
            email = user["primaryEmail"]
            emails.append(email)
        return emails

    def update_groups(self, auth, groups):
        """Update JIRA Groups."""
        g = auth.google()
        g.auth_service_account(g.scopes, g.subject)

        # get Google Users
        if auth.verbose:
            print("Getting Google Users...")
        fields = "nextPageToken,users(id,primaryEmail,name/fullName)"
        users = g.directory().get_users_dict(fields=fields)
        if auth.verbose:
            print("Found {} Google Users".format(len(users)))

        # process all group updates
        for group in sorted(groups, key=lambda x: x.lower()):
            email = groups[group]

            # get google group members emails
            google_members = self._get_google_group_member_emails(
                g.directory().get_derived_members(email), users
            )

            print("Updating Group: {} [{}]".format(group, email))
            self.update_group_members(group, google_members)

    #
    # Projects
    #
    def get_projects(self):
        """Return a list of JIRA Group projects."""
        maxResults = 1000
        params = {"maxResults": maxResults}
        url = "{}/rest/api/3/project/search".format(self.base_url)

        projects = []

        response = requests.get(url, auth=self.auth, params=params)
        results = response.json()
        projects.extend(results.get("values", []))

        while not results["isLast"]:
            params["startAt"] = len(projects)
            response = requests.get(url, auth=self.auth, params=params)
            results = response.json()
            projects.extend(results.get("values", []))

        return projects

    def get_projects_dict(self):
        """Return a dict of JIRA Group projects."""
        projects = {}
        for p in self.get_projects():
            projects[p.key] = p.raw
        return projects

    #
    # Epics
    #
    def get_epics_by_project_key(self, project):
        """Return a list of epics from a specific project"""
        url = "{}/rest/api/3/search?jql=project+%3D+{}+and+issuetype+%3D+Epic".format(
            self.base_url, project
        )

        r = requests.get(url, auth=self.auth)
        data = r.json()

        return data["issues"]

    #
    # Stories
    #
    def get_stories_by_epic_key(self, epic):
        """Return a list of stories from a specific epic"""
        url = "{}/rest/api/3/search?jql=%22Epic+Link%22+%3D{}".format(
            self.base_url, epic
        )

        r = requests.get(url, auth=self.auth)
        data = r.json()

        return data["issues"]

    def set_story_field(self, story, field, new_value):
        """Sets story field to a new value, returns true if posted correctly
           story value should be a key ie 'APCM-104'
           field should be the field that you wish to edit ie custom_field17820
        """
        url = "{}/rest/api/2/issue/{}".format(self.base_url, story)

        data = {"fields": {field: {"value": new_value}}}

        r = requests.put(url, json=data, auth=self.auth)

        if r.status_code is 204:
            return True
        return False

    #
    # Users
    #
    def get_user(self, accountId, applicationRoles=False, groups=False):
        """Return a JIRA user."""
        url = "{}/rest/api/3/user".format(self.base_url)

        expand = []
        if applicationRoles:
            expand.append("applicationRoles")
        if groups:
            expand.append("groups")

        params = {"accountId": accountId, "expand": ",".join(expand)}

        return requests.get(url, auth=self.auth, params=params).json()

    def get_users(
        self,
        # query='',
        # accountId=None,
        startAt=0,
        maxResults=1000,
        # includeActive=True,
        # includeInactive=False,
    ):
        """Return a list of all users."""
        url = "{}/rest/api/3/users/search".format(self.base_url)

        params = {
            # 'query': query,
            # 'accountId': accountId,
            "startAt": startAt,
            "maxResults": maxResults,
            # 'includeActive': includeActive,
            # 'includeInactive': includeInactive,
        }

        users = []

        response = requests.get(url, auth=self.auth, params=params)
        results = response.json()
        users.extend(results)

        while len(results) == maxResults:
            params["startAt"] = len(users)
            response = requests.get(url, auth=self.auth, params=params)
            results = response.json()
            users.extend(results)

        return users

    def get_users_csv(
        self, auth, bucket="broad-jira-users", filename="export-users.csv"
    ):
        """Return a list of account dictionaries from a CSV file."""
        g = auth.google()
        g.auth_service_account(g.scopes)
        media = g.storage().get_object_media(bucket, filename)
        lines = media.getvalue().decode().split("\n")

        users = []
        for user in list(csv.DictReader(lines)):
            user = dict(user)

            # accountId
            user["accountId"] = user["id"]
            del user["id"]

            # active, Confluence, Jira Software
            for key in ["active", "Confluence", "Jira Software"]:
                if user[key] == "Yes":
                    user[key] = True
                else:
                    user[key] = False

            # created
            if "Before" in user["created"]:
                user["created"] = user["created"].replace("Before ", "")
            created = datetime.datetime.strptime(user["created"], "%d %b %Y")
            user["created"] = str(created.date())

            # emailAddress
            user["emailAddress"] = user["email"]
            del user["email"]

            # full_name
            user["displayName"] = user["full_name"]
            del user["full_name"]

            # last active dates
            for key in [
                "Last active in Confluence",
                "Last active in Jira",
                "Last active in Opsgenie",
                "Last active in Statuspage",
                "Last active in Stride",
            ]:
                if "Never logged in" in user[key]:
                    user[key] = None
                elif "Not active in the last 180 days":
                    user[key] = "1 Jan 1970"
                elif "Before" in user[key]:
                    user[key] = user[key].replace("Before ", "")
                if user[key]:
                    time = datetime.datetime.strptime(user[key], "%d %b %Y")
                    user[key] = str(time.date())

            users.append(user)

        return users

    def get_users_csv_dict(
        self, auth, bucket="broad-jira-users", filename="export-users.csv"
    ):
        """Return a dict of account dictionaries from a CSV file."""
        users = {}
        for user in self.get_users_csv(auth, bucket, filename):
            uid = user["accountId"]
            users[uid] = user
        return users

    def get_users_dict(
        self,
        # query='',
        startAt=0,
        maxResults=1000,
        # includeActive=True,
        # includeInactive=False,
        key="emailAddress",
    ):
        """Return a list of all users."""
        all_users = self.get_users(
            # query,
            startAt=startAt,
            maxResults=maxResults,
            # includeActive=includeActive,
            # includeInactive=includeInactive
        )

        # convert to dict
        users = {}
        for u in all_users:
            k = u[key]
            users[k] = u
        return users

    def search_users(
        self,
        query="",
        # accountId=None,
        startAt=0,
        maxResults=1000,
        # includeActive=True,
        # includeInactive=False,
    ):
        """Return a list of all users."""
        url = "{}/rest/api/3/user/search".format(self.base_url)

        params = {
            "query": query,
            # 'accountId': accountId,
            "startAt": startAt,
            "maxResults": maxResults,
            # 'includeActive': includeActive,
            # 'includeInactive': includeInactive,
        }

        users = []

        response = requests.get(url, auth=self.auth, params=params)
        results = response.json()
        users.extend(results)

        while len(results) == maxResults:
            params["startAt"] = len(users)
            response = requests.get(url, auth=self.auth, params=params)
            results = response.json()
            users.extend(results)

        return users
