# Copyright (c) 2016 VMware, Inc.
# All Rights Reserved.
#
#    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.

import json
from unittest import mock

import ddt

from osprofiler.drivers import loginsight
from osprofiler import exc
from osprofiler.tests import test


@ddt.ddt
class LogInsightDriverTestCase(test.TestCase):
    BASE_ID = "8d28af1e-acc0-498c-9890-6908e33eff5f"

    def setUp(self):
        super().setUp()
        self._client = mock.Mock(spec=loginsight.LogInsightClient)
        self._project = "cinder"
        self._service = "osapi_volume"
        self._host = "ubuntu"
        with mock.patch.object(
            loginsight, "LogInsightClient", return_value=self._client
        ):
            self._driver = loginsight.LogInsightDriver(
                "loginsight://username:password@host",
                project=self._project,
                service=self._service,
                host=self._host,
            )

    @mock.patch.object(loginsight, "LogInsightClient")
    def test_init(self, client_class):
        client = mock.Mock()
        client_class.return_value = client

        loginsight.LogInsightDriver("loginsight://username:password@host")
        client_class.assert_called_once_with("host", "username", "password")
        client.login.assert_called_once_with()

    @ddt.data(
        "loginsight://username@host",
        "loginsight://username:p@ssword@host",
        "loginsight://us:rname:password@host",
    )
    def test_init_with_invalid_connection_string(self, conn_str):
        self.assertRaises(ValueError, loginsight.LogInsightDriver, conn_str)

    @mock.patch.object(loginsight, "LogInsightClient")
    def test_init_with_special_chars_in_conn_str(self, client_class):
        client = mock.Mock()
        client_class.return_value = client

        loginsight.LogInsightDriver("loginsight://username:p%40ssword@host")
        client_class.assert_called_once_with("host", "username", "p@ssword")
        client.login.assert_called_once_with()

    def test_get_name(self):
        self.assertEqual("loginsight", self._driver.get_name())

    def _create_trace(
        self,
        name,
        timestamp,
        parent_id="8d28af1e-acc0-498c-9890-6908e33eff5f",
        base_id=BASE_ID,
        trace_id="e465db5c-9672-45a1-b90b-da918f30aef6",
    ):
        return {
            "parent_id": parent_id,
            "name": name,
            "base_id": base_id,
            "trace_id": trace_id,
            "timestamp": timestamp,
            "info": {"host": self._host},
        }

    def _create_start_trace(self):
        return self._create_trace("wsgi-start", "2016-10-04t11:50:21.902303")

    def _create_stop_trace(self):
        return self._create_trace("wsgi-stop", "2016-10-04t11:50:30.123456")

    @mock.patch("json.dumps")
    def test_notify(self, dumps):
        json_str = mock.sentinel.json_str
        dumps.return_value = json_str

        trace = self._create_stop_trace()
        self._driver.notify(trace)

        trace["project"] = self._project
        trace["service"] = self._service
        exp_event = {
            "text": "OSProfiler trace",
            "fields": [
                {"name": "base_id", "content": trace["base_id"]},
                {"name": "trace_id", "content": trace["trace_id"]},
                {"name": "project", "content": trace["project"]},
                {"name": "service", "content": trace["service"]},
                {"name": "name", "content": trace["name"]},
                {"name": "trace", "content": json_str},
            ],
        }
        self._client.send_event.assert_called_once_with(exp_event)

    @mock.patch.object(loginsight.LogInsightDriver, "_append_results")
    @mock.patch.object(loginsight.LogInsightDriver, "_parse_results")
    def test_get_report(self, parse_results, append_results):
        start_trace = self._create_start_trace()
        start_trace["project"] = self._project
        start_trace["service"] = self._service

        stop_trace = self._create_stop_trace()
        stop_trace["project"] = self._project
        stop_trace["service"] = self._service

        resp = {
            "events": [
                {
                    "text": "OSProfiler trace",
                    "fields": [
                        {"name": "trace", "content": json.dumps(start_trace)}
                    ],
                },
                {
                    "text": "OSProfiler trace",
                    "fields": [
                        {"name": "trace", "content": json.dumps(stop_trace)}
                    ],
                },
            ]
        }
        self._client.query_events = mock.Mock(return_value=resp)

        self._driver.get_report(self.BASE_ID)
        self._client.query_events.assert_called_once_with(
            {"base_id": self.BASE_ID}
        )
        append_results.assert_has_calls(
            [
                mock.call(
                    start_trace["trace_id"],
                    start_trace["parent_id"],
                    start_trace["name"],
                    start_trace["project"],
                    start_trace["service"],
                    start_trace["info"]["host"],
                    start_trace["timestamp"],
                    start_trace,
                ),
                mock.call(
                    stop_trace["trace_id"],
                    stop_trace["parent_id"],
                    stop_trace["name"],
                    stop_trace["project"],
                    stop_trace["service"],
                    stop_trace["info"]["host"],
                    stop_trace["timestamp"],
                    stop_trace,
                ),
            ]
        )
        parse_results.assert_called_once_with()


class LogInsightClientTestCase(test.TestCase):
    def setUp(self):
        super().setUp()
        self._host = "localhost"
        self._username = "username"
        self._password = "password"  # noqa: S105
        self._client = loginsight.LogInsightClient(
            self._host, self._username, self._password
        )
        self._client._session_id = "4ff800d1-3175-4b49-9209-39714ea56416"

    def test_check_response_login_timeout(self):
        resp = mock.Mock(status_code=440)
        self.assertRaises(
            exc.LogInsightLoginTimeout, self._client._check_response, resp
        )

    def test_check_response_api_error(self):
        resp = mock.Mock(status_code=401, ok=False)
        resp.text = json.dumps(
            {
                "errorMessage": "Invalid username or password.",
                "errorCode": "FIELD_ERROR",
            }
        )
        e = self.assertRaises(
            exc.LogInsightAPIError, self._client._check_response, resp
        )
        self.assertEqual("Invalid username or password.", str(e))

    @mock.patch("requests.Request")
    @mock.patch("json.dumps")
    @mock.patch.object(loginsight.LogInsightClient, "_check_response")
    def test_send_request(self, check_resp, json_dumps, request_class):
        req = mock.Mock()
        request_class.return_value = req
        prep_req = mock.sentinel.prep_req
        req.prepare = mock.Mock(return_value=prep_req)

        data = mock.sentinel.data
        json_dumps.return_value = data

        self._client._session = mock.Mock()
        resp = mock.Mock()
        self._client._session.send = mock.Mock(return_value=resp)
        resp_json = mock.sentinel.resp_json
        resp.json = mock.Mock(return_value=resp_json)

        header = {"X-LI-Session-Id": "foo"}
        body = mock.sentinel.body
        params = mock.sentinel.params
        ret = self._client._send_request(
            "get", "https", "api/v1/events", header, body, params
        )

        self.assertEqual(resp_json, ret)
        exp_headers = {
            "X-LI-Session-Id": "foo",
            "content-type": "application/json",
        }
        request_class.assert_called_once_with(
            "get",
            "https://localhost:9543/api/v1/events",
            headers=exp_headers,
            data=data,
            params=mock.sentinel.params,
        )
        self._client._session.send.assert_called_once_with(
            prep_req, verify=False
        )
        check_resp.assert_called_once_with(resp)

    @mock.patch.object(loginsight.LogInsightClient, "_send_request")
    def test_is_current_session_active_with_active_session(self, send_request):
        self.assertTrue(self._client._is_current_session_active())
        exp_header = {"X-LI-Session-Id": self._client._session_id}
        send_request.assert_called_once_with(
            "get", "https", "api/v1/sessions/current", headers=exp_header
        )

    @mock.patch.object(loginsight.LogInsightClient, "_send_request")
    def test_is_current_session_active_with_expired_session(
        self, send_request
    ):
        send_request.side_effect = exc.LogInsightLoginTimeout

        self.assertFalse(self._client._is_current_session_active())
        send_request.assert_called_once_with(
            "get",
            "https",
            "api/v1/sessions/current",
            headers={"X-LI-Session-Id": self._client._session_id},
        )

    @mock.patch.object(
        loginsight.LogInsightClient,
        "_is_current_session_active",
        return_value=True,
    )
    @mock.patch.object(loginsight.LogInsightClient, "_send_request")
    def test_login_with_current_session_active(
        self, send_request, is_current_session_active
    ):
        self._client.login()
        is_current_session_active.assert_called_once_with()
        send_request.assert_not_called()

    @mock.patch.object(
        loginsight.LogInsightClient,
        "_is_current_session_active",
        return_value=False,
    )
    @mock.patch.object(loginsight.LogInsightClient, "_send_request")
    def test_login(self, send_request, is_current_session_active):
        new_session_id = "569a80aa-be5c-49e5-82c1-bb62392d2667"
        resp = {"sessionId": new_session_id}
        send_request.return_value = resp

        self._client.login()
        is_current_session_active.assert_called_once_with()
        exp_body = {"username": self._username, "password": self._password}
        send_request.assert_called_once_with(
            "post", "https", "api/v1/sessions", body=exp_body
        )
        self.assertEqual(new_session_id, self._client._session_id)

    @mock.patch.object(loginsight.LogInsightClient, "_send_request")
    def test_send_event(self, send_request):
        event = mock.sentinel.event
        self._client.send_event(event)

        exp_body = {"events": [event]}
        exp_path = (
            f"api/v1/events/ingest/{self._client.LI_OSPROFILER_AGENT_ID}"
        )
        send_request.assert_called_once_with(
            "post", "http", exp_path, body=exp_body
        )

    @mock.patch.object(loginsight.LogInsightClient, "_send_request")
    def test_query_events(self, send_request):
        resp = mock.sentinel.response
        send_request.return_value = resp

        self.assertEqual(resp, self._client.query_events({"foo": "bar"}))
        exp_header = {"X-LI-Session-Id": self._client._session_id}
        exp_params = {"limit": 20000, "timeout": self._client._query_timeout}
        send_request.assert_called_once_with(
            "get",
            "https",
            "api/v1/events/foo/CONTAINS+bar/timestamp/GT+0",
            headers=exp_header,
            params=exp_params,
        )

    @mock.patch.object(loginsight.LogInsightClient, "_send_request")
    @mock.patch.object(loginsight.LogInsightClient, "login")
    def test_query_events_with_session_expiry(self, login, send_request):
        resp = mock.sentinel.response
        send_request.side_effect = [exc.LogInsightLoginTimeout, resp]

        self.assertEqual(resp, self._client.query_events({"foo": "bar"}))
        login.assert_called_once_with()
        exp_header = {"X-LI-Session-Id": self._client._session_id}
        exp_params = {"limit": 20000, "timeout": self._client._query_timeout}
        exp_send_request_call = mock.call(
            "get",
            "https",
            "api/v1/events/foo/CONTAINS+bar/timestamp/GT+0",
            headers=exp_header,
            params=exp_params,
        )
        send_request.assert_has_calls([exp_send_request_call] * 2)
