#!/usr/bin/env python
# -*- coding: utf-8 -*-
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
"""Helpers methods for webhooks."""

import hashlib
import hmac
import json
import logging
import re

from webcompat import app
from webcompat.db import SiteGlobal
from webcompat.db import SiteRegional
from webcompat.db import global_site_db
from webcompat.db import regional_site_db
from webcompat.form import domain_name
from webcompat.helpers import extract_url
from webcompat.helpers import proxy_request
from webcompat.helpers import to_bytes
from webcompat.helpers import get_domains
from webcompat.issues import moderation_template
from webcompat.form import wrap_metadata
from webcompat.nsfw import get_nsfw_label

BROWSERS = ['blackberry', 'brave', 'chrome', 'edge', 'firefox', 'iceweasel', 'ie', 'lynx', 'myie', 'opera', 'puffin', 'qq', 'safari', 'samsung', 'seamonkey', 'uc', 'vivaldi']  # noqa
GECKO_BROWSERS = ['browser-android-components',
                  'browser-fenix',
                  'browser-firefox',
                  'browser-firefox-mobile',
                  'browser-firefox-reality',
                  'browser-firefox-tablet',
                  'browser-focus-geckoview',
                  'browser-geckoview', ]
IOS_BROWSERS = ['browser-firefox-ios', ]
PUBLIC_REPO = app.config['ISSUES_REPO_URI']
PRIVATE_REPO = app.config['PRIVATE_REPO_URI']
REJECTED_CONFIG = {
    'rejected': {
        'template': 'rejected',
        'labels': ['status-notacceptable']
    },
    'autoclosed': {
        'template': 'autoclosed',
        'labels': ['bugbug-probability-high']
    }
}


def extract_metadata(body):
    """Parse all the hidden comments for issue metadata.

    <!-- @foo: bar -->.
    Returns a dict with all such comments as members,
    with foo, bar as key, value.
    """
    match_list = re.findall(r'<!--\s@(\w+):\s(.+)\s-->', body)
    # Reverse the list before creating a dict, because we want to keep
    # the first instance (in case there are duplicates added by the user after)
    metadata_dict = dict(reversed(match_list))
    return metadata_dict


def extract_browser_label(metadata_dict):
    """Return the browser label from metadata_dict."""
    browser = metadata_dict.get('browser', None)
    dash_browser = 'fixme'
    # Only proceed if browser looks like "FooBrowser 99.0"
    if browser and re.search(r'([^\d]+?)\s[\d\.]+', browser):
        browser = browser.lower()
        # Remove parenthesis from the name
        browser = browser.translate(str.maketrans('', '', '()'))
        # Before returning a label, we need to clean up a bit
        label_data = browser.split(' ')
        name = label_data[0]
        remainder = label_data[1:]
        # Let's focus on the known browsers.
        if name in BROWSERS:
            dash_browser = label_data[0]
            # Let's find a type for the browser.
            remainder = label_data[1:]
            browser_type = None
            if 'mobile' in remainder:
                browser_type = 'mobile'
            if 'tablet' in remainder:
                browser_type = 'tablet'
            if 'ios' in remainder:
                browser_type = 'ios'
            if browser_type:
                dash_browser = f'{dash_browser}-{browser_type}'
    return f'browser-{dash_browser}'


def extract_extra_labels(metadata_dict):
    """Return the 'extra_labels' labels from metadata_dict, as a list."""
    labels = metadata_dict.get('extra_labels', None)
    if labels:
        extra_labels = labels.split(', ')
        extra_labels = [label.lower() for label in extra_labels
                        if label in app.config['EXTRA_LABELS']]
    else:
        extra_labels = labels
    return extra_labels


def priority_label_by_url(url):
    priorities = ['critical', 'important', 'normal']
    # Find hostname in the regional DB first,
    # if it doesn't exist, look in global
    site = regional_site_db.query(SiteRegional).filter_by(url=url).first()
    if not site:
        site = global_site_db.query(SiteGlobal).filter_by(url=url).first()

    if site:
        return f'priority-{priorities[site.priority - 1]}'

    return None


def extract_priority_label(body):
    """Parse url from body and query the priority labels."""
    hostname = domain_name(extract_url(body))
    label = None

    if hostname:
        label = priority_label_by_url(hostname)
        # If label for hostname not found, try less-level domain (>2)
        # If hostname is lv4.lv3.example.com, find lv3.example.com/example.com
        if not label:
            domains = get_domains(hostname)
            for domain in domains:
                sub_label = priority_label_by_url(domain)
                if sub_label:
                    return sub_label
    return label


def extract_nsfw_label(body):
    """Parse url from body and query the nsfw DB."""
    hostname = domain_name(extract_url(body))
    return get_nsfw_label(hostname)


def signature_check(key, post_signature, payload):
    """Check the HTTP POST legitimacy."""
    if post_signature.startswith('sha1='):
        sha_name, signature = post_signature.split('=')
    else:
        return False
    if not signature:
        return False
    # HMAC requires its key to be bytes, but data is strings.
    hexmac = get_payload_signature(key, payload)
    return hmac.compare_digest(to_bytes(hexmac), to_bytes(signature))


def get_payload_signature(key, payload):
    """Compute the payload signature given a key.

    key needs to be a bytes object.
    """
    key = to_bytes(key)
    payload = to_bytes(payload)
    mac = hmac.new(key, msg=payload, digestmod=hashlib.sha1)
    return mac.hexdigest()


def is_github_hook(request):
    """Validate the github webhook HTTP POST request."""
    if request.headers.get('X-GitHub-Event') is None:
        return False
    post_signature = request.headers.get('X-Hub-Signature')
    if post_signature:
        key = app.config['HOOK_SECRET_KEY']
        return signature_check(key, post_signature, request.data)
    return False


def get_issue_labels(issue_body):
    """Extract the list of labels from an issue body to be sent to GitHub."""
    labelslist = []
    metadata_dict = extract_metadata(issue_body)
    extra_labels = extract_extra_labels(metadata_dict)
    browser_label = extract_browser_label(metadata_dict)
    if extra_labels:
        # if extra_labels contains a browser tag, we do not need to extract it
        if any(label.startswith('browser') for label in extra_labels):
            browser_label = None
        labelslist.extend(extra_labels)
    priority_label = extract_priority_label(issue_body)
    nsfw_label = extract_nsfw_label(issue_body)
    labelslist.extend([browser_label, priority_label, nsfw_label])
    if any(label for label in labelslist if label in GECKO_BROWSERS):
        labelslist.append('engine-gecko')
    if any(label for label in labelslist if label in IOS_BROWSERS):
        labelslist.append('os-ios')
    labelslist = [label for label in labelslist if label is not None]
    return labelslist


def make_request(method, path, payload_request):
    """Helper method to wrap webcompat.helpers.proxy_request.

    Throws requests.exceptions.HTTPError for a non-2XX or 3XX response.
    """
    headers = {'Authorization': f'token {app.config["OAUTH_TOKEN"]}'}
    r = proxy_request(method, path, headers=headers, data=json.dumps(
        payload_request))
    r.raise_for_status()
    return r


def make_response(body, status_code):
    """Helper method to return text/plain response with body & status_code"""
    return (body, status_code, {'Content-Type': 'text/plain'})


def oops():
    """Lazy way to return an oops reponse."""
    return make_response('oops', 400)


def repo_scope(source_repo):
    """Check the scope nature of the repository.

    The repository can be a
    * known public.
    * known private.
    * or completely unknown.
    """
    scope = 'unknown'
    if source_repo.endswith(PUBLIC_REPO.rsplit('/issues')[0]):
        scope = 'public'
    elif source_repo.endswith(PRIVATE_REPO.rsplit('/issues')[0]):
        scope = 'private'
    return scope


def msg_log(msg, issue_number):
    """Write a log with the reason and the issue number."""
    log = app.logger
    log.setLevel(logging.INFO)
    msg = f'issue {issue_number} {msg}'
    log.info(msg)


def prepare_rejected_issue(reason='rejected'):
    """Create the payload for the rejected moderated issue.

    When the issue has been moderated as rejected or labelled
    invalid by the bot we need to change a couple of things
    in the public space

    - change Title
    - change body
    - close the issue
    - remove the action-needsmoderation label
    - change the milestone to invalid
    """
    # Extract the relevant information
    invalid_id = app.config['STATUSES']['invalid']['id']
    config = REJECTED_CONFIG[reason]
    payload_request = moderation_template(config['template'])
    payload_request['labels'] = config['labels']
    payload_request['state'] = 'closed'
    payload_request['milestone'] = invalid_id
    return payload_request


def prepare_private_url(private_url):
    """Create metadata with private url"""
    return "\n" + wrap_metadata(('private_url', private_url))
