from jira.client import JIRA
from jira.exceptions import JIRAError
from requests import ConnectionError, HTTPError


class JiraModerator(object):
    """
    Helper class to connect and manipulate the data in Jira
    """

    client = None
    clientOptions = {'server': 'https://issues.ypg.com'}    #: conneciton options for Jira
    username = "ypgAutoTester"                              #: Automation User username (QA Lead access to Jira)
    password = "@ut0t3$t3r"                                 #: Automation User password
    logger = None                                           #: logger to send loggin information to.
    project = None

    def __init__(self, logger):
        """
        Constructor for class

        @param logger: instance of a logging object configured in testing project
        @type logger: Logger
        """
        self.logger = logger

    def connect(self):
        """
        Establishes a connection to our YPG Jira instance and assignes it to self.client

        @return: True if we are connected to Jira and False if we are not
        @rtype: bool
        """

        try:
            self.client = JIRA(self.clientOptions, basic_auth=(self.username, self.password))
            self.client.session()
            success = True

        except ConnectionError, e:
            self.logger.error("Error Connection to Jira :")
            self.logger.error(e)
            self.client = None
            success = False
        except HTTPError, e:
            self.logger.error("Error Connection to Jira :")
            self.logger.error(e)
            self.client = None
            success = False

        return success

    def search_for_issue(self, query):
        """
        Returns a list of issues that were returned by Jira given the query you specified

        @param query: A string representation of a JQL query.  anything you can enter in Jira advanced search can be
        entered here.
        @type query:: str

        @return: a list of jira issues
        @rtype: ResultList
        """
        result_list = None
        try:
            result_list = self.client.search_issues(query)

        except JIRAError, e:
            self.logger.error("Could not search what you are looking for because " + str(e))

        if len(result_list) < 1:
            issues = None
        elif len(result_list) > 1:
            self.logger.warn(
                '''Jira found more than one issue with the search %s . You may want to manually verify
                        the automated process updated the correct issue." % query)''')
            issues = result_list[0]
        else:
            issues = result_list[0]
        return issues

    def confirm_version(self, proj_key, build_version):
        """
        Confirms that the project version supplied actually exists in Jira for the specified project

        @param proj_key: the Jira project acronym
        @type proj_key: str
        @param build_version: the version of the application you are testing
        @type build_version: str

        @return: True if the version was found in Jira.  False if the version was not found
        @rtype: bool
        """

        try:
            # get all the versions for the specified project from Jira
            project = self.client.project(proj_key)
            proj_versions = project.versions

            # search for the version we are testing.  Is it in Jira?
            for version in proj_versions:

                if str(version.name) == str(build_version):
                    self.logger.debug("Matched the specidied buildVersion runtime parameter to version in Jira")
                    if not version.released and not version.archived:
                        self.logger.debug(
                            "We are going to start to test build version " + version.name + " for project " + proj_key)
                        return True
                    else:
                        self.logger.warn(
                            '''The buildVersion you are searching for has been released or archived in Jira
                               and is not a valid testable build version''')
                        return False
        except JIRAError:
            self.logger.error(
                "Could not retrieve the projects versions.  Check that the project exists or that Jira is not down")
            return False

    def get_required_fields(self, project_key, issue_type=None):
        """
        Fetches a list of fields that require values to open a new issue in a specified project

        @param project_key: The Jira project acronym for the project you are querying
        @type project_key: str
        @param issue_type: Optional issue type Names.  Single name or coma delimited string
        @type issue_type: str

        @return: a dictionary containing the required fields and None values for each.  Returns empty dict if
        search failed.
        @rtype: dict
        """

        req_fields = {}

        try:
            # Get a list of fields that are required to supply values for so that an issue can be created
            meta = self.client.createmeta(projectKeys=project_key,
                                          issuetypeNames=issue_type,
                                          expand='projects.issuetypes.fields')

            fields = meta['projects'][0]['issuetypes'][0]['fields']

            for field in fields:
                if fields[field]['required']:
                    req_fields[field] = None

        except JIRAError, e:
            self.logger.error("Could not get required fields for Jira project " + project_key + " because " + str(e))
        except IndexError, e:
            self.logger.error("Could not get required fields for Jira project " + project_key + " because " + str(e))

        return req_fields

    def create_issue(self, data):
        """
        Creates an issue in Jira with the dictionary of data supplied.  Be sure that data contains all required
        fields before using this.

        @param data: dictionary of required fields and valid values
        @type data: dict

        @return: returns True if issues was created and False if there was a failure
        @rtype: bool
        """

        try:
            self.client.create_issue(fields=data)
            success = True

        except JIRAError, e:
            success = False
            self.logger.error("Issue was not created :" + str(e))

        return success

    def reopen_bug(self, issue, proj_key, version):
        """
        Transitions a specified jira Bug from any resolved state back to In Review and assigns it to the
        project lead with comments

        @param issue: an issue object that came from Jira.  Use searchForIssue first before reopening issues
        @type issue: issue
        @param proj_key: the Jira project acronym from Jira
        @type proj_key: str
        @param version: the build number currently under test where the bug was rediscovered
        @type version: str

        @return: returns False if we could not reopen issue and True if we could
        @rtype: bool
        """
        cur_state = issue.fields.status.name

        transitions = self.client.transitions(issue)

        project = self.client.project(proj_key)
        proj_lead = project.lead.name

        version = version

        comment = "This issue has reoccured in the latest version %s" % version

        try:
            if cur_state == "Closed":
                self.client.transition_issue(issue, self.get_transition_id(transitions, "Re-Open"),
                                             assignee={'name': proj_lead})
                self.client.add_comment(issue, comment)
            elif cur_state == "Ready for QA":
                self.client.transition_issue(issue, self.get_transition_id(transitions, "Back to In Development"),
                                             assignee={'name': proj_lead})
                self.client.add_comment(issue, comment)
            elif cur_state == "In Testing":
                self.client.transition_issue(issue, self.get_transition_id(transitions, "Fail"),
                                             assignee={'name': proj_lead})
                self.client.add_comment(issue, comment)
            elif cur_state == "Ready to Deploy":
                self.client.transition_issue(issue, self.get_transition_id(transitions, "Back to In Testing"))
                transitions = self.client.transitions(issue)
                self.client.transition_issue(issue, self.get_transition_id(transitions, "Fail"),
                                             assignee={'name': proj_lead})
                self.client.add_comment(issue, comment)
        except IndexError:
            self.logger.error("Could not find a transition to reopen the issue '" + issue.key + "'")
            return False
        except JIRAError, e:
            self.logger.error("Jira returned error when modifying issue '" + issue.key + "' because " + str(e))
            return False

        return True

    #noinspection PyMethodMayBeStatic
    def get_transition_id(self, trans_dict, trans_name):
        """
        Fetch the id for a transition's name

        @param trans_dict: a dictionary of transitions fetched from Jira for a given issue
        @type trans_dict: dict
        @param trans_dict: name of the Jira transition you would like the id for
        @type trans_name: str

        @return: a numeric id associtated to the transition name
        @rtype: str
        """

        id_dict = [element['id'] for element in trans_dict if element['name'] == trans_name]
        return id_dict[0]

    #noinspection PyMethodMayBeStatic
    def prepare_issue_data(self, req_fields, test_id, project, summary, description, component, severity, version):
        """
        Constructs a properly formatted dictionary of data to supply to Jira for opening an issue.  Creates a bug
        in the specified project After construction, it will validate the dictionary by checking if all required
        fields are filled

        @param req_fields: dictionary of jira project required fields.  Construct the dict with getRequiredFields
        @type req_fields: dict
        @param test_id: the name of the test cases found in the test case's decoreator in python or the name in Spiratest
        @type test_id: str
        @param project: the Jira project acronym for the project you wish to open a bug inspect
        @type project: str
        @param summary: the summary of the bug you wish to open
        @type summary: str
        @param description: the description of the bug you wish to open
        @type description: str
        @param component: the component of the bug you wish to open
        @type component: str
        @param severity: the severity of the bug you wish to open
        @type severity: str
        @param version: the affected version of the bug you wish to open
        @type version: str

        @return: if the dictionary properly complies to all the required fields.
        Returns emtpy dict if it does not.
        @rtype: dict
        """
        desc = '''This issue was created by YPG automated Test Case : %(test_id)s. \n \n The error is caused when
            sending the following parameters to the API method in question : \n %(description)s''' % \
            {'description': description, 'test_id': test_id}

        req_fields['project'] = {'key': project}
        req_fields['summary'] = summary
        req_fields['description'] = desc
        req_fields['issuetype'] = {'name': 'Bug'}
        req_fields['customfield_10411'] = {'value': severity}
        req_fields['components'] = [{'name': component}]
        req_fields['versions'] = [{'name': version}]

        # if any of the required fields are not filled erase the dict
        if None in req_fields.values():
            req_fields = {}

        return req_fields

    def get_project_key(self, proj_name=''):
        """
        @param proj_name: (str) Name of Project in Jira
        @return: (str) Project acronym / key from Jira
        """

        #get all projects from Jira
        projs = self.client.projects()
        key = None

        #find the project in Jira who's name matches the input
        #and return the project key
        for proj in projs:
            if proj.name == proj_name:
                key = proj.key
                break

        return key
