# Copyright 2017 Intel Research and Development Ireland Limited
# *************************************************************

# This file is part of OSM Monitoring module
# All Rights Reserved to Intel Corporation

# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at

#         http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

# For those usages not covered by the Apache License, Version 2.0 please
# contact: helena.mcgough@intel.com or adrian.hoban@intel.com
##
"""Tests for all common OpenStack methods."""


from __future__ import unicode_literals
import json
import logging
import socket
import unittest
from threading import Thread

import mock
import requests
from six.moves.BaseHTTPServer import BaseHTTPRequestHandler
from six.moves.BaseHTTPServer import HTTPServer

from osm_mon.core.message_bus.producer import KafkaProducer
from osm_mon.core.settings import Config
from osm_mon.plugins.OpenStack.Aodh.alarming import Alarming
from osm_mon.plugins.OpenStack.common import Common
from osm_mon.plugins.OpenStack.response import OpenStack_Response

log = logging.getLogger(__name__)

# Create an instance of the common openstack class, producer and consumer
openstack_auth = Common()

# Mock a valid get_response for alarm details
valid_get_resp = '{"gnocchi_resources_threshold_rule":\
                  {"resource_id": "my_resource_id"}}'


class MockResponse(object):
    """Mock a response class for generating responses."""

    def __init__(self, text):
        """Initialise a mock response with a text attribute."""
        self.text = text


class MockNotifierHandler(BaseHTTPRequestHandler):
    """Mock the NotifierHandler class for testing purposes."""

    def _set_headers(self):
        """Set the headers for a request."""
        self.send_response(200)
        self.send_header('Content-type', 'text/html')
        self.end_headers()

    def do_GET(self):
        """Mock functionality for GET request."""
        #        self.send_response(requests.codes.ok)
        self._set_headers()
        pass

    def do_POST(self):
        """Mock functionality for a POST request."""
        self._set_headers()
        content_length = int(self.headers['Content-Length'])
        post_data = self.rfile.read(content_length)
        try:
            post_data = post_data.decode()
        except AttributeError:
            pass
        self.notify_alarm(json.loads(post_data))

    def notify_alarm(self, values):
        """Mock the notify_alarm functionality to generate a valid response."""
        config = Config.instance()
        config.read_environ()
        self._alarming = Alarming()
        self._common = Common()
        self._response = OpenStack_Response()
        self._producer = KafkaProducer('alarm_response')
        alarm_id = values['alarm_id']

        auth_token = Common.get_auth_token('test_id')
        endpoint = Common.get_endpoint('alarming', 'test_id')

        # If authenticated generate and send response message
        if auth_token is not None and endpoint is not None:
            url = "{}/v2/alarms/%s".format(endpoint) % alarm_id

            # Get the resource_id of the triggered alarm and the date
            result = Common.perform_request(
                url, auth_token, req_type="get")
            alarm_details = json.loads(result.text)
            gnocchi_rule = alarm_details['gnocchi_resources_threshold_rule']
            resource_id = gnocchi_rule['resource_id']
            # Mock a date for testing purposes
            a_date = "dd-mm-yyyy 00:00"

            # Process an alarm notification if resource_id is valid
            if resource_id is not None:
                # Try generate and send response
                try:
                    resp_message = self._response.generate_response(
                        'notify_alarm', a_id=alarm_id,
                        r_id=resource_id,
                        sev=values['severity'], date=a_date,
                        state=values['current'], vim_type="OpenStack")
                    self._producer.publish_alarm_response(
                        'notify_alarm', resp_message)
                except Exception:
                    pass


def get_free_port():
    """Function to get a free port to run the test webserver on."""
    s = socket.socket(socket.AF_INET, type=socket.SOCK_STREAM)
    s.bind(('localhost', 0))
    address, port = s.getsockname()
    s.close()
    return port


# Create the webserver, port and run it on its own thread
mock_server_port = get_free_port()
mock_server = HTTPServer(('localhost', mock_server_port), MockNotifierHandler)
mock_server_thread = Thread(target=mock_server.serve_forever)
mock_server_thread.setDaemon(True)
mock_server_thread.start()


def test_do_get():
    """Integration test for get request on notifier webserver."""
    url = 'http://localhost:{port}/users'.format(port=mock_server_port)

    # Send a request to the mock API server and store the response.
    response = requests.get(url)

    # Confirm that the request-response cycle completed successfully.
    assert response.ok


class AlarmNotificationTest(unittest.TestCase):
    @mock.patch.object(KafkaProducer, "publish_alarm_response")
    @mock.patch.object(OpenStack_Response, "generate_response")
    @mock.patch.object(Common, "perform_request")
    @mock.patch.object(Common, "get_endpoint")
    @mock.patch.object(Common, "get_auth_token")
    def test_post_notify_alarm(self, auth, endpoint, perf_req, resp, notify):
        """Integration test for notify_alarm."""
        url = 'http://localhost:{port}/users'.format(port=mock_server_port)
        payload = {"severity": "critical",
                   "alarm_name": "my_alarm",
                   "current": "current_state",
                   "alarm_id": "my_alarm_id",
                   "reason": "Threshold has been broken",
                   "reason_data": {"count": 1,
                                   "most_recent": "null",
                                   "type": "threshold",
                                   "disposition": "unknown"},
                   "previous": "previous_state"}

        # Mock authenticate and request response for testing
        auth.return_value = "my_auth_token"
        endpoint.return_value = "my_endpoint"
        perf_req.return_value = MockResponse(valid_get_resp)

        # Generate a post request for testing
        response = requests.post(url, json.dumps(payload))
        self.assertEqual(response.status_code, 200)
        # A response message is generated with the following details
        resp.assert_called_with(
            "notify_alarm", a_id="my_alarm_id", r_id="my_resource_id",
            sev="critical", date='dd-mm-yyyy 00:00', state="current_state",
            vim_type="OpenStack")

        # Response message is sent back to the SO via MON's producer
        notify.assert_called_with("notify_alarm", mock.ANY)
