import requests
from pypandoc import convert_text
import logging
from tempfile import NamedTemporaryFile
from future.moves.subprocess import check_output
from future.moves.configparser import ConfigParser
import os.path
import re
from time import sleep


class GitHubClient(object):
    BASE_URL = 'https://api.github.com'
    BASE_HEADERS = {
            "Content-Type": "application/json",
            "Accept": "application/json",
            "User-Agent": "iris-edu/pyweed/repo-importer",
        }

    def __init__(self, owner="example", project="example", token="example"):
        self.headers = self.BASE_HEADERS.copy()
        self.headers['Authorization'] = 'Basic %s' % token

        self.issues_url = '%s/repos/%s/%s/issues' % (self.BASE_URL, owner, project)

    def list(self):
        """
        List all issues
        """
        issues = []
        page = 1
        while True:
            print("Getting page %d of GitHub issues" % page)
            r = requests.get(
                self.issues_url,
                params={'page': page, 'state': 'all'},
                headers=self.headers,
            )
            r.raise_for_status()
            json_data = r.json()
            if len(json_data):
                issues.extend(json_data)
                page += 1
            else:
                break
        return issues

    def close(self, issue_number):
        """
        Close an issue
        """
        edit_issue_url = "%s/%s" % (self.issues_url, issue_number)
        r = requests.patch(
            edit_issue_url,
            json={
                "state": "closed",
            },
            headers=self.headers,
        )
        r.raise_for_status()

    def create(self, issue_json):
        """
        Create an issue
        """
        # Limit the rate of creation
        # see https://developer.github.com/guides/best-practices-for-integrators/#dealing-with-abuse-rate-limits
        sleep(1)
        r = requests.post(
            self.issues_url,
            json=issue_json,
            headers=self.headers,
        )
        r.raise_for_status()
        return r.json()['number']


class RedmineClient(object):
    BASE_URL = 'http://dmscode.iris.washington.edu'

    def __init__(self, project="example", redmine_key="example"):
        self.redmine_key = redmine_key
        self.issues_url = "%s/projects/%s/issues.json" % (self.BASE_URL, project)

    def list(self):
        """
        List all issues
        """
        issues = []
        offset = 0
        limit = 50
        total = -1
        while (total < 0) or (total > offset):
            print("Loading %d issues from Redmine" % limit)
            r = requests.get(
                self.issues_url,
                json = {
                    'status_id': '*',
                    'key': self.redmine_key,
                    'offset': offset,
                    'limit': limit,
                    'sort': 'id',
                }
            )
            r.raise_for_status()
            json_data = r.json()
            issues.extend(json_data['issues'])
            offset += limit
            total = json_data['total_count']
        return issues


class RedmineToGitHub(object):

    def __init__(self, redmine_client=None, github_client=None):
        self.redmine_client = redmine_client or RedmineClient()
        self.github_client = github_client or GitHubClient()
        self.issue_mapping = {}
        self.closed_github_issues = set()

    def is_closed(self, redmine_issue):
        return 'closed_on' in redmine_issue

    def convert_issue(self, redmine_issue):
        """
        Turn a Redmine issue into a GitHub issue
        """
        redmine_issue_id = str(redmine_issue['id'])
        github_issue_id = self.issue_mapping.get(redmine_issue_id)
        if github_issue_id:
            print("Redmine #%s is already in GitHub as #%s" % (redmine_issue_id, github_issue_id))
        else:
            print("Converting Redmine #%s (%s)" % (redmine_issue_id, redmine_issue['subject']))
            github_issue_id = str(self.github_client.create(self.convert_issue_data(redmine_issue)))
            print("Created GitHub #%s" % github_issue_id)
            self.issue_mapping[redmine_issue_id] = github_issue_id

        # Close the issue if appropriate
        if self.is_closed(redmine_issue) and github_issue_id not in self.closed_github_issues:
            print("Marking #%s as closed" % github_issue_id)
            self.github_client.close(github_issue_id)
        return github_issue_id

    def convert_issue_data(self, redmine_issue):
        """
        Generate the data for a new GitHub issue
        """
        description_md = convert_text(
            redmine_issue['description'], 'markdown_github', 'textile'
        )
        porting_note = '###### ported from Redmine #%s (created %s)' % (
            redmine_issue['id'],
            redmine_issue['created_on'].split('T')[0]
        )
        if self.is_closed(redmine_issue):
            porting_note = '%s (CLOSED %s)' % (
                porting_note,
                redmine_issue['closed_on'].split('T')[0]
            )
        body = "%s\n\n%s" % (porting_note, description_md)
        title = "%(subject)s (RM#%(id)s)" % redmine_issue
        return {
            "title": title,
            "body": body,
            "assignees": ["adam-iris"],
        }

    def get_existing_mapping(self):
        self.issue_mapping = {}
        self.closed_github_issues.clear()
        print("Looking for existing GitHub issues")
        for github_issue in self.github_client.list():
            m = re.search(r'RM#(\d+)', github_issue['title'])
            if m:
                github_issue_id = str(github_issue['number'])
                self.issue_mapping[m.group(1)] = github_issue_id
                if github_issue['state'] == 'closed':
                    self.closed_github_issues.add(github_issue_id)

    def convert_issues(self):
        self.get_existing_mapping()
        print("Reading Redmine issues")
        for redmine_issue in self.redmine_client.list():
            self.convert_issue(redmine_issue)

    def setup_git_update(self):
        """
        Generate a filter to run on the git repo, eg.

        git filter-branch --msg-filter {{ filter }} HEAD
        """
        with NamedTemporaryFile('w+t', delete=False) as f:
            f.write(FILTER_TEMPLATE % str(self.issue_mapping))
        cmd = "git filter-branch -f --msg-filter 'python %s' -- --all" % f.name
        print("To update the Git repository, run\n%s" % cmd)

FILTER_TEMPLATE = """
#! python

import re
import sys

MAPPINGS = %s

def subfn(m):
    '''
    Function to call with re.sub(), takes a re.match object with a Redmine issue number
    and returns the corresponding GitHub issue number
    '''
    return MAPPINGS.get(m.group(0), m.group(0))

for line in sys.stdin:
    print(re.sub(r'(?<=#)\\d+\\b', subfn, line))
""".strip()


if __name__ == "__main__":
    logging.basicConfig(level=logging.INFO)

    config = ConfigParser()
    config.read_dict({
        'GITHUB': {
            'org': 'iris-edu',
            'repo': 'pyweed',
            'token': 'example',
        },
        'REDMINE': {
            'project': 'pyweed',
            'key': 'example',
        },
    })
    config_file = os.path.expanduser("~/.redmine_to_git.cfg")
    if os.path.exists(config_file):
        config.read([config_file])
    else:
        with open(config_file, 'w+t') as f:
            config.write(f)
        print("Please update %s to include GitHub auth token and Redmine auth key" % config_file)
        exit(1)
    redmine_client = RedmineClient(config.get('REDMINE', 'project'), config.get('REDMINE', 'key'))
    github_client = GitHubClient(config.get('GITHUB', 'org'), config.get('GITHUB', 'repo'), config.get('GITHUB', 'token'))
    rtg = RedmineToGitHub(redmine_client, github_client)

    rtg.convert_issues()
    rtg.setup_git_update()