from __future__ import absolute_import
from django.conf import settings
from zerver.models import get_client, UserProfile
from zerver.lib.response import json_success
from zerver.lib.validator import check_dict
from zerver.decorator import authenticated_api_view, REQ, has_request_variables, to_non_negative_int, flexible_boolean
from zerver.views.messages import send_message_backend
from zerver.lib.webhooks.git import get_push_commits_event_message,\
    SUBJECT_WITH_BRANCH_TEMPLATE, get_force_push_commits_event_message, \
    get_remove_branch_event_message, get_pull_request_event_message,\
    get_issue_event_message, SUBJECT_WITH_PR_OR_ISSUE_INFO_TEMPLATE,\
    get_commits_comment_action_message
import logging
import re
import ujson

from typing import Any, Dict, List, Mapping, Optional, Sequence, Tuple, Text
from zerver.lib.str_utils import force_str
from django.http import HttpRequest, HttpResponse

ZULIP_TEST_REPO_NAME = 'zulip-test'
ZULIP_TEST_REPO_ID = 6893087

def is_test_repository(repository):
    # type: (Mapping[Text, Any]) -> bool
    return repository['name'] == ZULIP_TEST_REPO_NAME and repository['id'] == ZULIP_TEST_REPO_ID

class UnknownEventType(Exception):
    pass

def github_pull_request_content(payload):
    # type: (Mapping[Text, Any]) -> Text
    pull_request = payload['pull_request']
    action = get_pull_request_or_issue_action(payload)

    if action in ('opened', 'edited'):
        return get_pull_request_event_message(
            payload['sender']['login'],
            action,
            pull_request['html_url'],
            pull_request['number'],
            pull_request['head']['ref'],
            pull_request['base']['ref'],
            pull_request['body'],
            get_pull_request_or_issue_assignee(pull_request)
        )
    return get_pull_request_event_message(
        payload['sender']['login'],
        action,
        pull_request['html_url'],
        pull_request['number']
    )

def github_issues_content(payload):
    # type: (Mapping[Text, Any]) -> Text
    issue = payload['issue']
    action = get_pull_request_or_issue_action(payload)

    if action in ('opened', 'edited'):
        return get_issue_event_message(
            payload['sender']['login'],
            action,
            issue['html_url'],
            issue['number'],
            issue['body'],
            get_pull_request_or_issue_assignee(issue)
        )
    return get_issue_event_message(
        payload['sender']['login'],
        action,
        issue['html_url'],
        issue['number'],
    )

def github_object_commented_content(payload, type):
    # type: (Mapping[Text, Any], Text) -> Text
    comment = payload['comment']
    issue = payload['issue']
    action = u'[commented]({}) on'.format(comment['html_url'])

    return get_pull_request_event_message(
        comment['user']['login'],
        action,
        issue['html_url'],
        issue['number'],
        message=comment['body'],
        type=type
    )

def get_pull_request_or_issue_action(payload):
    # type: (Mapping[Text, Any]) -> Text
    return 'synchronized' if payload['action'] == 'synchronize' else payload['action']

def get_pull_request_or_issue_assignee(object_payload):
    # type: (Mapping[Text, Any]) -> Optional[Text]
    assignee_dict = object_payload.get('assignee')
    if assignee_dict:
        return assignee_dict.get('login')
    return None

def get_pull_request_or_issue_subject(repository, payload_object, type):
    # type: (Mapping[Text, Any], Mapping[Text, Any], Text) -> Text
    return SUBJECT_WITH_PR_OR_ISSUE_INFO_TEMPLATE.format(
        repo=repository['name'],
        type=type,
        id=payload_object['number'],
        title=payload_object['title']
    )

def github_generic_subject(noun, topic_focus, blob):
    # type: (Text, Text, Mapping[Text, Any]) -> Text
    # issue and pull_request objects have the same fields we're interested in
    return u'%s: %s %d: %s' % (topic_focus, noun, blob['number'], blob['title'])

def api_github_v1(user_profile, event, payload, branches, stream, **kwargs):
    # type: (UserProfile, Text, Mapping[Text, Any], Text, Text, **Any) -> Tuple[Text, Text, Text]
    """
    processes github payload with version 1 field specification
    `payload` comes in unmodified from github
    `stream` is set to 'commits' if otherwise unset
    """
    commit_stream = stream
    issue_stream = 'issues'
    return api_github_v2(user_profile, event, payload, branches, stream, commit_stream, issue_stream, **kwargs)


def api_github_v2(user_profile, event, payload, branches, default_stream,
                  commit_stream, issue_stream, topic_focus = None):
    # type: (UserProfile, Text, Mapping[Text, Any], Text, Text, Text, Text, Optional[Text]) -> Tuple[Text, Text, Text]
    """
    processes github payload with version 2 field specification
    `payload` comes in unmodified from github
    `default_stream` is set to what `stream` is in v1 above
    `commit_stream` and `issue_stream` fall back to `default_stream` if they are empty
    This and allowing alternative endpoints is what distinguishes v1 from v2 of the github configuration
    """
    target_stream = commit_stream if commit_stream else default_stream
    issue_stream = issue_stream if issue_stream else default_stream
    repository = payload['repository']
    topic_focus = topic_focus if topic_focus else repository['name']

    # Event Handlers
    if event == 'pull_request':
        subject = get_pull_request_or_issue_subject(repository, payload['pull_request'], 'PR')
        content = github_pull_request_content(payload)
    elif event == 'issues':
        # in v1, we assume that this stream exists since it is
        # deprecated and the few realms that use it already have the
        # stream
        target_stream = issue_stream
        subject = get_pull_request_or_issue_subject(repository, payload['issue'], 'Issue')
        content = github_issues_content(payload)
    elif event == 'issue_comment':
        # Comments on both issues and pull requests come in as issue_comment events
        issue = payload['issue']
        if 'pull_request' not in issue or issue['pull_request']['diff_url'] is None:
            # It's an issues comment
            target_stream = issue_stream
            type = 'Issue'
            subject = get_pull_request_or_issue_subject(repository, payload['issue'], type)
        else:
            # It's a pull request comment
            type = 'PR'
            subject = get_pull_request_or_issue_subject(repository, payload['issue'], type)

        content = github_object_commented_content(payload, type)

    elif event == 'push':
        subject, content = build_message_from_gitlog(user_profile, topic_focus,
                                                     payload['ref'], payload['commits'],
                                                     payload['before'], payload['after'],
                                                     payload['compare'],
                                                     payload['pusher']['name'],
                                                     forced=payload['forced'],
                                                     created=payload['created'])
    elif event == 'commit_comment':
        subject = topic_focus

        comment = payload.get('comment')
        action = u'[commented]({})'.format(comment['html_url'])
        content = get_commits_comment_action_message(
            comment['user']['login'],
            action,
            comment['html_url'].split('#', 1)[0],
            comment['commit_id'],
            comment['body'],
        )

    else:
        raise UnknownEventType(force_str(u'Event %s is unknown and cannot be handled' % (event,)))

    return target_stream, subject, content

@authenticated_api_view(is_webhook=True)
@has_request_variables
def api_github_landing(request, user_profile, event=REQ(),
                       payload=REQ(validator=check_dict([])),
                       branches=REQ(default=''),
                       stream=REQ(default=''),
                       version=REQ(converter=to_non_negative_int, default=1),
                       commit_stream=REQ(default=''),
                       issue_stream=REQ(default=''),
                       exclude_pull_requests=REQ(converter=flexible_boolean, default=False),
                       exclude_issues=REQ(converter=flexible_boolean, default=False),
                       exclude_commits=REQ(converter=flexible_boolean, default=False),
                       emphasize_branch_in_topic=REQ(converter=flexible_boolean, default=False),
                       ):
    # type: (HttpRequest, UserProfile, Text, Mapping[Text, Any], Text, Text, int, Text, Text, bool, bool, bool, bool) -> HttpResponse

    repository = payload['repository']

    # Special hook for capturing event data. If we see our special test repo, log the payload from github.
    try:
        if is_test_repository(repository) and settings.PRODUCTION:
            with open('/var/log/zulip/github-payloads', 'a') as f: