# -*- coding: utf-8 -*-
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

from pathlib import Path
from os import getenv
import redis

from release_bot.celerizer import celery_app
from release_bot.exceptions import ReleaseException
from release_bot.configuration import configuration
from release_bot.releasebot import ReleaseBot

DEFAULT_CONF_FILE = "/home/release-bot/.config/conf.yaml"


@celery_app.task(name="task.celery_task.parse_web_hook_payload")
def parse_web_hook_payload(webhook_payload):
    """
    Parse json webhook payload callback
    :param webhook_payload: json from github webhook
    """
    db = get_redis_instance()
    if 'issue' in webhook_payload.keys():
        if webhook_payload['action'] == 'opened':
            handle_issue(webhook_payload, db)
    elif 'pull_request' in webhook_payload.keys():
        if webhook_payload['action'] == 'closed':
            if webhook_payload['pull_request']['merged'] is True:
                handle_pr(webhook_payload, db)
    elif 'installation' in webhook_payload.keys():
        # detect new repo installation
        if webhook_payload['action'] == 'added':
            installation_id = webhook_payload['installation']['id']
            repositories_added = webhook_payload['repositories_added']
            save_new_installations(installation_id, repositories_added, db)

        # detect when repo uninstall app
        if webhook_payload['action'] == 'removed':
            repositories_removed = webhook_payload['repositories_removed']
            delete_installations(repositories_removed, db)


def get_redis_instance():
    db = redis.Redis(
        host=getenv("REDIS_SERVICE_HOST", "localhost"),
        port=getenv("REDIS_SERVICE_PORT", "6379"),
        db=1,  # 0 is used by Celery
        decode_responses=True,
    )
    return db


def set_configuration(webhook_payload, db, issue=True):
    """
    Prepare configuration from parsed web hook payload
    and return ReleaseBot instance with logger

    :param webhook_payload: payload from web hook
    :param issue: if true parse Github issue payload otherwise parse
                  Github pull request payload
    :return: ReleaseBot instance, configuration logger
    """
    configuration.configuration = Path(getenv("CONF_PATH",
                                              DEFAULT_CONF_FILE)).resolve()

    # add configuration from Github webhook
    configuration.repository_name = webhook_payload['repository']['name']
    configuration.repository_owner = webhook_payload['repository']['owner']['login']
    if issue:
        configuration.github_username = webhook_payload['issue']['user']['login']
    else:
        configuration.github_username = webhook_payload['pull_request']['user']['login']

    repo_installation_id = db.get(webhook_payload['repository']['full_name'])
    configuration.github_app_installation_id = repo_installation_id

    configuration.load_configuration()  # load the rest of configuration if there is any

    # create url for github app to enable access over http
    configuration.clone_url = f'https://x-access-token:' \
        f'{configuration.github_token}@github.com/' \
        f'{configuration.repository_owner}/{configuration.repository_name}.git'

    return ReleaseBot(configuration), configuration.logger


def handle_issue(webhook_payload, db):
    """
    Handler for newly opened issues
    :param webhook_payload: json data from webhook
    :param db: Redis instance
    :return:
    """
    release_bot, logger = set_configuration(webhook_payload, db=db, issue=True)

    logger.info("Resolving opened issue")
    release_bot.git.pull()
    try:
        release_bot.load_release_conf()
        if (release_bot.new_release.trigger_on_issue and
                release_bot.find_open_release_issues()):
            if release_bot.new_release.labels is not None:
                release_bot.project.add_issue_labels(
                    release_bot.new_pr.issue_number,
                    release_bot.new_release.labels)
            release_bot.make_release_pull_request()
    except ReleaseException as exc:
        logger.error(exc)


def handle_pr(webhook_payload, db):
    """
    Handler for merged PR
    :param webhook_payload: json data from webhook
    :param db: Redis instance
    :return:
    """
    release_bot, logger = set_configuration(webhook_payload, db=db, issue=False)

    logger.info("Resolving opened PR")
    release_bot.git.pull()
    try:
        release_bot.load_release_conf()
        if release_bot.find_newest_release_pull_request():
            release_bot.make_new_github_release()
            # Try to do PyPi release regardless whether we just did github release
            # for case that in previous iteration (of the 'while True' loop)
            # we succeeded with github release, but failed with PyPi release
            release_bot.make_new_pypi_release()
    except ReleaseException as exc:
        logger.error(exc)

    msg = ''.join(release_bot.github.comment)
    release_bot.project.pr_comment(release_bot.new_release.pr_number, msg)
    release_bot.github.comment = []  # clean up


def save_new_installations(installation_id, repositories_added, db):
    """
    Save repo which installed release-bot github app with it installation id
    :param installation_id: installation identifier from initial installation web hook
    :param repositories_added: repositories which user choose for release-bot installation
    :param db: Redis instance
    :return: True if data was saved successfully into Redis
    """
    with db.pipeline() as pipe:
        for repo in repositories_added:
            pipe.set(repo["full_name"], installation_id)
        pipe.execute()
    return db.save()


def delete_installations(repositories_removed, db):
    """
    Delete repo from Redis when user uninstall release-bot app from such repo
    :param repositories_removed: repositories which user choose to uninstall from the release-bot
    :param db: Redis instance
    :return: True if data was deleted successfully into Redis
    """
    with db.pipeline() as pipe:
        for repo in repositories_removed:
            pipe.delete(repo["full_name"])
        pipe.execute()
    return db.save()