from __future__ import absolute_import

import urlparse

from django import forms
from django.contrib import messages
from django.utils.translation import ugettext_lazy as _
from django.core.validators import URLValidator
from django.http import HttpResponseRedirect

from sentry import http
from sentry.web.helpers import render_to_response
from sentry.identity.pipeline import IdentityProviderPipeline
from sentry.integrations import (
    IntegrationInstallation, IntegrationFeatures, IntegrationMetadata, IntegrationProvider, FeatureDescription,
)
from sentry.pipeline import (
    PipelineView, NestedPipelineView
)
from sentry.utils.http import absolute_uri

from opsgenie import OpsGenie
from opsgenie import Configuration as OpsgenieConfiguration
from opsgenie import GetAccountRequest

DESCRIPTION = """
Connect your Sentry organization to your Opsgenie app, and start
getting alerts for errors right in front of you where all the
action happens in your office!
"""

FEATURES = [
    FeatureDescription(
        """
        Unfurls Sentry URLs directly within opsgenie, providing you context and
        actionability on issues right at your fingertips.
        """,
        IntegrationFeatures.CHAT_UNFURL,
    ),
    # to be done - as this requires custom actions from opsgenie
    # FeatureDescription(
    #     """
    #     Resolve, ignore, and assign issues with minimal context switching.
    #     """,
    #     IntegrationFeatures.ACTION_NOTIFICATION,
    # ),
    FeatureDescription(
        """
        Configure rule based Opsgenie alerts to automatically be posted to a
        specific team/user. Want any error that's happening more than 100 times a
        minute to be posted with P1 priority? Setup a rule for it!
        """,
        IntegrationFeatures.ALERT_RULE,
    ),
]

setup_alert = {
    'type': 'info',
    'text': 'The Opsgenie integration adds a new Alert Rule action to all projects. To enable automatic alerts sent to Opsgenie you must create a rule using the opsgenie account action in your project settings.',
}

metadata = IntegrationMetadata(
    description=_(DESCRIPTION.strip()),
    features=FEATURES,
    author='Kumarappan Arumugam',
    noun=_('Installation'),
    issue_url='https://github.com/kumarappan-arumugam/sentry-opsgenie/issues/new?title=Opsgenie%20Integration:%20&labels=Component%3A%20Integrations',
    source_url='https://github.com/kumarappan-arumugam/sentry-opsgenie/tree/master/sentry_opsgenie',
    aspects={
        'alerts': [setup_alert],
    }
)


class InstallationForm(forms.Form):
    name = forms.CharField(
        label="Provide a name for the integration",
        # choices=[],
        help_text=_('It will be easier to idenify the integration on the alert rules page if you provide a name.'),
        widget=forms.TextInput(
            attrs={'placeholder': _('Defaults to opsgenie account name')}
        ),
        required=False
    )
    api_url = forms.URLField(
        label="Opsgenie Api Url",
        # choices=[],
        help_text=_('The "Api Url" for your Opsgenie api integration, differs based on region. Can be found here https://docs.opsgenie.com/docs/european-service-region.'),
        widget=forms.TextInput(
            attrs={'placeholder': _('https://api.opsgenie.com')}
        ),
    )
    api_key = forms.CharField(
        label="Opsgenie Api Key",
        help_text=_('We require a "global" api integration key to be '
                    'configured. If it\'s not a global key then alerts can be sent only '
                    'to specific team. This can be generated by following '
                    'the instructions at https://docs.opsgenie.com/docs/api-key-management.'
                    ),
        widget=forms.TextInput(
            attrs={'placeholder': _('XXXXXXXXXXXXXXXXXXXXXXXXXXX')}
        )
    )
    account_name = forms.HiddenInput()

    def __init__(self, *args, **kwargs):
        super(InstallationForm, self).__init__(*args, **kwargs)

    def clean(self):
        """
        Hook for doing any extra form-wide cleaning after Field.clean() has been
        called on every field. Any ValidationError raised by this method will
        not be associated with a particular field; it will have a special-case
        association with the field named '__all__'.
        """
        super(InstallationForm, self).clean()

        api_url = self.cleaned_data.get('api_url')

        # short circuit if url is not valid
        if not api_url:
            return None

        if not 'v1' in api_url and not 'v2' in api_url:
            api_url = urlparse.urljoin(api_url, '/v2')

        self.cleaned_data['api_url'] = api_url

        config = OpsgenieConfiguration(
                    apikey=self.cleaned_data.get('api_key'),
                    endpoint=self.cleaned_data.get('api_url')
                )

        client = OpsGenie(config)
        # this gets the account name and also serves as a test for the api_key
        try:
            account = client.accounts.get_account(GetAccountRequest())
        except Exception as e:
            raise forms.ValidationError(_("API key/url is not functional, test failed with error - %(error)s"),
                                        code='test-error',
                                        params={'error': e.message}
                                    )
        else:
            self.cleaned_data['account_name'] = account.name

        return self.cleaned_data

class InstallationConfigView(PipelineView):
    def dispatch(self, request, pipeline):
        if request.method == 'POST':
            form = InstallationForm(request.POST)
            if form.is_valid():
                form_data = form.cleaned_data
                pipeline.bind_state('opsgenie', form_data)
                return pipeline.next_step()
        else:
            form = InstallationForm()

        return render_to_response(
            template='opsgenie-config.html',
            context={
                'form': form,
            },
            request=request,
        )

class OpsgenieIntegration(IntegrationInstallation):
    """
    An IntegrationInstallation represents an installed integration and manages the
    core functionality of the integration.
    """
    def get_client(self):
        # Return the api client for a given provider
        config = OpsgenieConfiguration(
                    apikey=self.model.metadata['api_key'],
                    endpoint=self.model.metadata['api_url']
                )
        return OpsGenie(config)

class OpsgenieIntegrationProvider(IntegrationProvider):
    """
    An integration provider describes a third party that can be registered within Sentry.
    The core behavior is simply how to add the integration (the setup
    pipeline), which will likely use a nested pipeline for identity
    authentication, and what kind of configuration is stored.
    This is similar to Sentry's legacy 'plugin' information, except that an
    integration is lives as an instance in the database, and the ``IntegrationProvider``
    class is just a descriptor for how that object functions, and what behavior
    it provides (such as extensions provided).
    """

    key = 'opsgenie'
    name = 'Opsgenie'
    metadata = metadata
    features = frozenset([
        # IntegrationFeatures.ACTION_NOTIFICATION, # to be done - as this requires custom actions from opsgenie
        IntegrationFeatures.CHAT_UNFURL,
        IntegrationFeatures.ALERT_RULE,
    ])

    integration_cls = OpsgenieIntegration

    setup_dialog_config = {
        'width': 600,
        'height': 900,
    }

    def get_pipeline_views(self):
        return [InstallationConfigView(), OpsgenieFinishedView()]

    def build_integration(self, state):
        name = state['opsgenie'].get('name')
        integration = {
            'name': name or state['opsgenie']['account_name'],
            'external_id': state['opsgenie']['account_name'],
            'metadata': {
                'api_key': state['opsgenie']['api_key'],
                'api_url': state['opsgenie']['api_url'],
            }
        }
        return integration

class OpsgenieFinishedView(PipelineView):
    def dispatch(self, request, pipeline):
        pipeline.finish_pipeline()

        messages.add_message(request, messages.SUCCESS, 'Opsgenie integration installed.')

        return HttpResponseRedirect(
            absolute_uri(u'/settings/{}/integrations/opsgenie/{}/'.format(
                pipeline.organization.slug,
                pipeline.integration.id,
            ))
        )
