# Copyright (C) 2012-2023 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
# (only), as published by the Free Software Foundation.
#
# 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 Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# This program is dual-licensed. If you wish to learn more about the
# RhodeCode Enterprise Edition, including its added features, Support services,
# and proprietary license terms, please see https://rhodecode.com/licenses/


import deform
import logging
import requests
import colander
import textwrap
from mako.template import Template
from rhodecode import events
from rhodecode.model.validation_schema.widgets import CheckboxChoiceWidgetDesc
from rhodecode.translation import _
from rhodecode.lib import helpers as h
from rhodecode.lib.celerylib import run_task, async_task, RequestContextTask
from rhodecode.lib.colander_utils import strip_whitespace
from rhodecode.integrations.types.base import (
    IntegrationTypeBase, CommitParsingDataHandler, render_with_traceback,
    requests_retry_call)

log = logging.getLogger(__name__)

REPO_PUSH_TEMPLATE = Template('''
<b>${data['actor']['username']}</b> pushed to repo <a href="${data['repo']['url']}">${data['repo']['repo_name']}</a>:
<br>
<ul>
%for branch, branch_commits in branches_commits.items():
    <li>
        % if branch:
        <a href="${branch_commits['branch']['url']}">branch: ${branch_commits['branch']['name']}</a>
        % else:
        to trunk
        % endif
        <ul>
        % for commit in branch_commits['commits']:
            <li><a href="${commit['url']}">${commit['short_id']}</a> - ${commit['message_html']}</li>
        % endfor
        </ul>
    </li>
%endfor
''')


class HipchatSettingsSchema(colander.Schema):
    color_choices = [
        ('yellow', _('Yellow')),
        ('red', _('Red')),
        ('green', _('Green')),
        ('purple', _('Purple')),
        ('gray', _('Gray')),
    ]

    server_url = colander.SchemaNode(
        colander.String(),
        title=_('Hipchat server URL'),
        description=_('Hipchat integration url.'),
        default='',
        preparer=strip_whitespace,
        validator=colander.url,
        widget=deform.widget.TextInputWidget(
            placeholder='https://?.hipchat.com/v2/room/?/notification?auth_token=?',
        ),
    )
    notify = colander.SchemaNode(
        colander.Bool(),
        title=_('Notify'),
        description=_('Make a notification to the users in room.'),
        missing=False,
        default=False,
    )
    color = colander.SchemaNode(
        colander.String(),
        title=_('Color'),
        description=_('Background color of message.'),
        missing='',
        validator=colander.OneOf([x[0] for x in color_choices]),
        widget=deform.widget.Select2Widget(
            values=color_choices,
        ),
    )


class HipchatIntegrationType(IntegrationTypeBase, CommitParsingDataHandler):
    key = 'hipchat'
    display_name = _('Hipchat')
    description = _('Send events such as repo pushes and pull requests to '
                    'your hipchat channel.')

    @classmethod
    def icon(cls):
        return '''<?xml version="1.0" encoding="utf-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 1000 1000" enable-background="new 0 0 1000 1000" xml:space="preserve"><g><g transform="translate(0.000000,511.000000) scale(0.100000,-0.100000)"><path fill="#205281" d="M4197.1,4662.4c-1661.5-260.4-3018-1171.6-3682.6-2473.3C219.9,1613.6,100,1120.3,100,462.6c0-1014,376.8-1918.4,1127-2699.4C2326.7-3377.6,3878.5-3898.3,5701-3730.5l486.5,44.5l208.9-123.3c637.2-373.4,1551.8-640.6,2240.4-650.9c304.9-6.9,335.7,0,417.9,75.4c185,174.7,147.3,411.1-89.1,548.1c-315.2,181.6-620,544.7-733.1,870.1l-51.4,157.6l472.7,472.7c349.4,349.4,520.7,551.5,657.7,774.2c784.5,1281.2,784.5,2788.5,0,4052.6c-236.4,376.8-794.8,966-1178.4,1236.7c-572.1,407.7-1264.1,709.1-1993.7,870.1c-267.2,58.2-479.6,75.4-1038,82.2C4714.4,4686.4,4310.2,4679.6,4197.1,4662.4z M5947.6,3740.9c1856.7-380.3,3127.6-1709.4,3127.6-3275c0-1000.3-534.4-1949.2-1466.2-2600.1c-188.4-133.6-287.8-226.1-301.5-284.4c-41.1-157.6,263.8-938.6,397.4-1020.8c20.5-10.3,34.3-44.5,34.3-75.4c0-167.8-811.9,195.3-1363.4,609.8l-181.6,137l-332.3-58.2c-445.3-78.8-1281.2-78.8-1702.6,0C2796-2569.2,1734.1-1832.6,1220.2-801.5C983.8-318.5,905,51.5,929,613.3c27.4,640.6,243.2,1192.1,685.1,1740.3c620,770.8,1661.5,1305.2,2822.8,1452.5C4806.9,3854,5553.7,3819.7,5947.6,3740.9z"/><path fill="#205281" d="M2381.5-345.9c-75.4-106.2-68.5-167.8,34.3-322c332.3-500.2,1010.6-928.4,1760.8-1120.2c417.9-106.2,1226.4-106.2,1644.3,0c712.5,181.6,1270.9,517.3,1685.4,1014C7681-561.7,7715.3-424.7,7616-325.4c-89.1,89.1-167.9,65.1-431.7-133.6c-835.8-630.3-2028-856.4-3086.5-585.8C3683.3-938.6,3142-685,2830.3-448.7C2576.8-253.4,2463.7-229.4,2381.5-345.9z"/></g></g><!-- Svg Vector Icons : http://www.onlinewebfonts.com/icon --></svg>'''

    valid_events = [
        events.PullRequestCloseEvent,
        events.PullRequestMergeEvent,
        events.PullRequestUpdateEvent,
        events.PullRequestCommentEvent,
        events.PullRequestReviewEvent,
        events.PullRequestCreateEvent,
        events.RepoPushEvent,
        events.RepoCreateEvent,
    ]

    def send_event(self, event):
        if event.__class__ not in self.valid_events:
            log.debug('event %r not present in valid event list (%s)', event, self.valid_events)
            return

        if not self.event_enabled(event):
            return

        data = event.as_dict()

        text = '<b>{}<b> caused a <b>{}</b> event'.format(
            data['actor']['username'], event.name)

        if isinstance(event, events.PullRequestCommentEvent):
            text = self.format_pull_request_comment_event(event, data)
        elif isinstance(event, events.PullRequestCommentEditEvent):
            text = self.format_pull_request_comment_event(event, data)
        elif isinstance(event, events.PullRequestReviewEvent):
            text = self.format_pull_request_review_event(event, data)
        elif isinstance(event, events.PullRequestEvent):
            text = self.format_pull_request_event(event, data)
        elif isinstance(event, events.RepoPushEvent):
            text = self.format_repo_push_event(data)
        elif isinstance(event, events.RepoCreateEvent):
            text = self.format_repo_create_event(data)
        else:
            log.error('unhandled event type: %r', event)

        run_task(post_text_to_hipchat, self.settings, text)

    def settings_schema(self):
        schema = HipchatSettingsSchema()
        schema.add(colander.SchemaNode(
            colander.Set(),
            widget=CheckboxChoiceWidgetDesc(
                values=sorted(
                    [(e.name, e.display_name, e.description) for e in self.valid_events]
                ),
            ),
            description="List of events activated for this integration",
            name='events'
        ))

        return schema

    def format_pull_request_comment_event(self, event, data):
        comment_text = data['comment']['text']
        if len(comment_text) > 200:
            comment_text = '{comment_text}<a href="{comment_url}">...<a/>'.format(
                comment_text=h.html_escape(comment_text[:200]),
                comment_url=data['comment']['url'],
            )

        comment_status = ''
        if data['comment']['status']:
            comment_status = '[{}]: '.format(data['comment']['status'])

        return (textwrap.dedent(
            '''
            {user} commented on pull request <a href="{pr_url}">{number}</a> - {pr_title}:
            >>> {comment_status}{comment_text}
            ''').format(
                comment_status=comment_status,
                user=data['actor']['username'],
                number=data['pullrequest']['pull_request_id'],
                pr_url=data['pullrequest']['url'],
                pr_status=data['pullrequest']['status'],
                pr_title=h.html_escape(data['pullrequest']['title']),
                comment_text=h.html_escape(comment_text)
            )
        )

    def format_pull_request_review_event(self, event, data):
        return (textwrap.dedent(
            '''
            Status changed to {pr_status} for pull request <a href="{pr_url}">#{number}</a> - {pr_title}
            ''').format(
                user=data['actor']['username'],
                number=data['pullrequest']['pull_request_id'],
                pr_url=data['pullrequest']['url'],
                pr_status=data['pullrequest']['status'],
                pr_title=h.html_escape(data['pullrequest']['title']),
            )
        )

    def format_pull_request_event(self, event, data):
        action = {
            events.PullRequestCloseEvent: 'closed',
            events.PullRequestMergeEvent: 'merged',
            events.PullRequestUpdateEvent: 'updated',
            events.PullRequestCreateEvent: 'created',
        }.get(event.__class__, str(event.__class__))

        return ('Pull request <a href="{url}">#{number}</a> - {title} '
                '{action} by <b>{user}</b>').format(
            user=data['actor']['username'],
            number=data['pullrequest']['pull_request_id'],
            url=data['pullrequest']['url'],
            title=h.html_escape(data['pullrequest']['title']),
            action=action
        )

    def format_repo_push_event(self, data):
        branches_commits = self.aggregate_branch_data(
            data['push']['branches'], data['push']['commits'])

        result = render_with_traceback(
            REPO_PUSH_TEMPLATE,
            data=data,
            branches_commits=branches_commits,
        )
        return result

    def format_repo_create_event(self, data):
        return '<a href="{}">{}</a> ({}) repository created by <b>{}</b>'.format(
            data['repo']['url'],
            h.html_escape(data['repo']['repo_name']),
            data['repo']['repo_type'],
            data['actor']['username'],
        )


@async_task(ignore_result=True, base=RequestContextTask)
def post_text_to_hipchat(settings, text):
    log.debug('sending %s to hipchat %s', text, settings['server_url'])
    json_message = {
        "message": text,
        "color": settings.get('color', 'yellow'),
        "notify": settings.get('notify', False),
    }
    req_session = requests_retry_call()
    resp = req_session.post(settings['server_url'], json=json_message, timeout=60)
    resp.raise_for_status()  # raise exception on a failed request
