# Copyright 2017 Mycroft AI Inc.
#
# 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 unittest
from copy import copy

from unittest.mock import MagicMock, patch

import mycroft.api
import mycroft.configuration

from test.util import base_config
CONFIG = base_config()
CONFIG.merge(
    {
        'data_dir': '/opt/mycroft',
        'server': {
            'url': 'https://api-test.mycroft.ai',
            'version': 'v1',
            'update': True,
            'metrics': False
        }
    }
)


mycroft.api.requests.post = MagicMock()


def create_identity(uuid, expired=False):
    mock_identity = MagicMock()
    mock_identity.is_expired.return_value = expired
    mock_identity.uuid = uuid
    return mock_identity


def create_response(status, json=None, url='', data=''):
    json = json or {}
    response = MagicMock()
    response.status_code = status
    response.json.return_value = json
    response.url = url
    return response


class TestApi(unittest.TestCase):
    def setUp(self):
        patcher = patch('mycroft.configuration.Configuration.get',
                        return_value=CONFIG)
        self.mock_config_get = patcher.start()
        self.addCleanup(patcher.stop)
        super().setUp()

    @patch('mycroft.api.IdentityManager.get')
    def test_init(self, mock_identity_get):
        mock_identity_get.return_value = create_identity('1234')
        a = mycroft.api.Api('test-path')
        self.assertEqual(a.url, 'https://api-test.mycroft.ai')
        self.assertEqual(a.version, 'v1')
        self.assertEqual(a.identity.uuid, '1234')

    @patch('mycroft.api.IdentityManager')
    @patch('mycroft.api.requests.request')
    def test_send(self, mock_request, mock_identity_manager):
        # Setup an OK response
        mock_response_ok = create_response(200, {})
        mock_response_301 = create_response(301, {})
        mock_response_401 = create_response(401, {}, 'auth/token')
        mock_response_refresh = create_response(401, {}, '')

        mock_request.return_value = mock_response_ok
        a = mycroft.api.Api('test-path')
        req = {'path': 'something', 'headers': {}}

        # Check successful
        self.assertEqual(a.send(req), mock_response_ok.json())

        # check that a 300+ status code generates Exception
        mock_request.return_value = mock_response_301
        with self.assertRaises(mycroft.api.HTTPError):
            a.send(req)

        # Check 401
        mock_request.return_value = mock_response_401
        req = {'path': '', 'headers': {}}
        with self.assertRaises(mycroft.api.HTTPError):
            a.send(req)

        # Check refresh token
        a.old_params = copy(req)
        mock_request.side_effect = [mock_response_refresh, mock_response_ok,
                                    mock_response_ok]
        req = {'path': 'something', 'headers': {}}
        a.send(req)
        self.assertTrue(mycroft.api.IdentityManager.save.called)


class TestDeviceApi(unittest.TestCase):
    def setUp(self):
        patcher = patch('mycroft.configuration.Configuration.get',
                        return_value=CONFIG)
        self.mock_config_get = patcher.start()
        self.addCleanup(patcher.stop)
        super().setUp()

    @patch('mycroft.api.IdentityManager.get')
    @patch('mycroft.api.requests.request')
    def test_init(self, mock_request, mock_identity_get):
        mock_request.return_value = create_response(200)
        mock_identity_get.return_value = create_identity('1234')

        device = mycroft.api.DeviceApi()
        self.assertEqual(device.identity.uuid, '1234')
        self.assertEqual(device.path, 'device')

    @patch('mycroft.api.IdentityManager.get')
    @patch('mycroft.api.requests.request')
    def test_device_activate(self, mock_request, mock_identity_get):
        mock_request.return_value = create_response(200)
        mock_identity_get.return_value = create_identity('1234')
        # Test activate
        device = mycroft.api.DeviceApi()
        device.activate('state', 'token')
        json = mock_request.call_args[1]['json']
        self.assertEqual(json['state'], 'state')
        self.assertEqual(json['token'], 'token')

    @patch('mycroft.api.IdentityManager.get')
    @patch('mycroft.api.requests.request')
    def test_device_get(self, mock_request, mock_identity_get):
        mock_request.return_value = create_response(200)
        mock_identity_get.return_value = create_identity('1234')
        # Test get
        device = mycroft.api.DeviceApi()
        device.get()
        url = mock_request.call_args[0][1]
        self.assertEqual(url, 'https://api-test.mycroft.ai/v1/device/1234')

    @patch('mycroft.api.IdentityManager.update')
    @patch('mycroft.api.IdentityManager.get')
    @patch('mycroft.api.requests.request')
    def test_device_get_code(self, mock_request, mock_identity_get,
                             mock_identit_update):
        mock_request.return_value = create_response(200, '123ABC')
        mock_identity_get.return_value = create_identity('1234')
        device = mycroft.api.DeviceApi()
        ret = device.get_code('state')
        self.assertEqual(ret, '123ABC')
        url = mock_request.call_args[0][1]
        self.assertEqual(
            url, 'https://api-test.mycroft.ai/v1/device/code?state=state')

    @patch('mycroft.api.IdentityManager.get')
    @patch('mycroft.api.requests.request')
    def test_device_get_settings(self, mock_request, mock_identity_get):
        mock_request.return_value = create_response(200, {})
        mock_identity_get.return_value = create_identity('1234')
        device = mycroft.api.DeviceApi()
        device.get_settings()
        url = mock_request.call_args[0][1]
        self.assertEqual(
            url, 'https://api-test.mycroft.ai/v1/device/1234/setting')

    @patch('mycroft.api.IdentityManager.get')
    @patch('mycroft.api.requests.request')
    def test_device_report_metric(self, mock_request, mock_identity_get):
        mock_request.return_value = create_response(200, {})
        mock_identity_get.return_value = create_identity('1234')
        device = mycroft.api.DeviceApi()
        device.report_metric('mymetric', {'data': 'mydata'})
        url = mock_request.call_args[0][1]
        params = mock_request.call_args[1]

        content_type = params['headers']['Content-Type']
        correct_json = {'data': 'mydata'}
        self.assertEqual(content_type, 'application/json')
        self.assertEqual(params['json'], correct_json)
        self.assertEqual(
            url, 'https://api-test.mycroft.ai/v1/device/1234/metric/mymetric')

    @patch('mycroft.api.IdentityManager.get')
    @patch('mycroft.api.requests.request')
    def test_device_send_email(self, mock_request, mock_identity_get):
        mock_request.return_value = create_response(200, {})
        mock_identity_get.return_value = create_identity('1234')
        device = mycroft.api.DeviceApi()
        device.send_email('title', 'body', 'sender')
        url = mock_request.call_args[0][1]
        params = mock_request.call_args[1]

        content_type = params['headers']['Content-Type']
        correct_json = {'body': 'body', 'sender': 'sender', 'title': 'title'}
        self.assertEqual(content_type, 'application/json')
        self.assertEqual(params['json'], correct_json)
        self.assertEqual(
            url, 'https://api-test.mycroft.ai/v1/device/1234/message')

    @patch('mycroft.api.IdentityManager.get')
    @patch('mycroft.api.requests.request')
    def test_device_get_oauth_token(self, mock_request, mock_identity_get):
        mock_request.return_value = create_response(200, {})
        mock_identity_get.return_value = create_identity('1234')
        device = mycroft.api.DeviceApi()
        device.get_oauth_token(1)
        url = mock_request.call_args[0][1]

        self.assertEqual(
            url, 'https://api-test.mycroft.ai/v1/device/1234/token/1')

    @patch('mycroft.api.IdentityManager.get')
    @patch('mycroft.api.requests.request')
    def test_device_get_location(self, mock_request, mock_identity_get):
        mock_request.return_value = create_response(200, {})
        mock_identity_get.return_value = create_identity('1234')
        device = mycroft.api.DeviceApi()
        device.get_location()
        url = mock_request.call_args[0][1]
        self.assertEqual(
            url, 'https://api-test.mycroft.ai/v1/device/1234/location')

    @patch('mycroft.api.IdentityManager.get')
    @patch('mycroft.api.requests.request')
    def test_device_get_subscription(self, mock_request, mock_identity_get):
        mock_request.return_value = create_response(200, {})
        mock_identity_get.return_value = create_identity('1234')
        device = mycroft.api.DeviceApi()
        device.get_subscription()
        url = mock_request.call_args[0][1]
        self.assertEqual(
            url, 'https://api-test.mycroft.ai/v1/device/1234/subscription')

        mock_request.return_value = create_response(200, {'@type': 'free'})
        self.assertFalse(device.is_subscriber)

        mock_request.return_value = create_response(200, {'@type': 'monthly'})
        self.assertTrue(device.is_subscriber)

        mock_request.return_value = create_response(200, {'@type': 'yearly'})
        self.assertTrue(device.is_subscriber)

    @patch('mycroft.api.IdentityManager.get')
    @patch('mycroft.api.requests.request')
    def test_device_upload_skills_data(self, mock_request, mock_identity_get):
        mock_request.return_value = create_response(200)
        mock_identity_get.return_value = create_identity('1234')
        device = mycroft.api.DeviceApi()
        device.upload_skills_data({})
        url = mock_request.call_args[0][1]
        data = mock_request.call_args[1]['json']

        # Check that the correct url is called
        self.assertEqual(
            url, 'https://api-test.mycroft.ai/v1/device/1234/skillJson')

        # Check that the correct data is sent as json
        self.assertTrue('blacklist' in data)
        self.assertTrue('skills' in data)

        with self.assertRaises(ValueError):
            device.upload_skills_data('This isn\'t right at all')

    @patch('mycroft.api.IdentityManager.get')
    @patch('mycroft.api.requests.request')
    def test_stt(self, mock_request, mock_identity_get):
        mock_request.return_value = create_response(200, {})
        mock_identity_get.return_value = create_identity('1234')
        stt = mycroft.api.STTApi('stt')
        self.assertEqual(stt.path, 'stt')

    @patch('mycroft.api.IdentityManager.get')
    @patch('mycroft.api.requests.request')
    def test_stt_stt(self, mock_request, mock_identity_get):
        mock_request.return_value = create_response(200, {})
        mock_identity_get.return_value = create_identity('1234')
        stt = mycroft.api.STTApi('stt')
        stt.stt('La la la', 'en-US', 1)
        url = mock_request.call_args[0][1]
        self.assertEqual(url, 'https://api-test.mycroft.ai/v1/stt')
        data = mock_request.call_args[1].get('data')
        self.assertEqual(data, 'La la la')
        params = mock_request.call_args[1].get('params')
        self.assertEqual(params['lang'], 'en-US')

    @patch('mycroft.api.IdentityManager.load')
    def test_has_been_paired(self, mock_identity_load):
        # reset pairing cache
        mock_identity = MagicMock()
        mock_identity_load.return_value = mock_identity
        # Test None
        mock_identity.uuid = None
        self.assertFalse(mycroft.api.has_been_paired())
        # Test empty string
        mock_identity.uuid = ""
        self.assertFalse(mycroft.api.has_been_paired())
        # Test actual id number
        mock_identity.uuid = "1234"
        self.assertTrue(mycroft.api.has_been_paired())


@patch('mycroft.api.IdentityManager.get')
@patch('mycroft.api.requests.request')
class TestSettingsMeta(unittest.TestCase):
    def setUp(self):
        patcher = patch('mycroft.configuration.Configuration.get',
                        return_value=CONFIG)
        self.mock_config_get = patcher.start()
        self.addCleanup(patcher.stop)
        super().setUp()

    def test_upload_meta(self, mock_request, mock_identity_get):
        mock_request.return_value = create_response(200, {})
        mock_identity_get.return_value = create_identity('1234')
        device = mycroft.api.DeviceApi()

        settings_meta = {
            'name': 'TestMeta',
            "skill_gid": 'test_skill|19.02',
            'skillMetadata': {
                'sections': [
                    {
                        'name': 'Settings',
                        'fields': [
                            {
                                'name': 'Set me',
                                'type': 'number',
                                'value': 4
                            }
                        ]
                    }
                ]
            }
        }
        device.upload_skill_metadata(settings_meta)
        url = mock_request.call_args[0][1]
        method = mock_request.call_args[0][0]
        params = mock_request.call_args[1]

        content_type = params['headers']['Content-Type']
        self.assertEqual(content_type, 'application/json')
        self.assertEqual(method, 'PUT')
        self.assertEqual(params['json'], settings_meta)
        self.assertEqual(
            url, 'https://api-test.mycroft.ai/v1/device/1234/settingsMeta')

    def test_get_skill_settings(self, mock_request, mock_identity_get):
        mock_request.return_value = create_response(200, {})
        mock_identity_get.return_value = create_identity('1234')
        device = mycroft.api.DeviceApi()
        device.get_skill_settings()
        method = mock_request.call_args[0][0]
        url = mock_request.call_args[0][1]
        params = mock_request.call_args[1]

        self.assertEqual(method, 'GET')
        self.assertEqual(
            url, 'https://api-test.mycroft.ai/v1/device/1234/skill/settings')


@patch('mycroft.api._paired_cache', False)
@patch('mycroft.api.IdentityManager.get')
@patch('mycroft.api.requests.request')
class TestIsPaired(unittest.TestCase):
    def setUp(self):
        patcher = patch('mycroft.configuration.Configuration.get',
                        return_value=CONFIG)
        self.mock_config_get = patcher.start()
        self.addCleanup(patcher.stop)
        super().setUp()

    def test_is_paired_true(self, mock_request, mock_identity_get):
        mock_request.return_value = create_response(200)
        mock_identity = MagicMock()
        mock_identity.is_expired.return_value = False
        mock_identity.uuid = '1234'
        mock_identity_get.return_value = mock_identity
        num_calls = mock_identity_get.num_calls
        # reset paired cache

        self.assertTrue(mycroft.api.is_paired())

        self.assertEqual(num_calls, mock_identity_get.num_calls)
        url = mock_request.call_args[0][1]
        self.assertEqual(url, 'https://api-test.mycroft.ai/v1/device/1234')

    def test_is_paired_false_local(self, mock_request, mock_identity_get):
        mock_request.return_value = create_response(200)
        mock_identity = MagicMock()
        mock_identity.is_expired.return_value = False
        mock_identity.uuid = ''
        mock_identity_get.return_value = mock_identity

        self.assertFalse(mycroft.api.is_paired())
        mock_identity.uuid = None
        self.assertFalse(mycroft.api.is_paired())

    def test_is_paired_false_remote(self, mock_request, mock_identity_get):
        mock_request.return_value = create_response(401)
        mock_identity = MagicMock()
        mock_identity.is_expired.return_value = False
        mock_identity.uuid = '1234'
        mock_identity_get.return_value = mock_identity

        self.assertFalse(mycroft.api.is_paired())

    def test_is_paired_error_remote(self, mock_request, mock_identity_get):
        mock_request.return_value = create_response(500)
        mock_identity = MagicMock()
        mock_identity.is_expired.return_value = False
        mock_identity.uuid = '1234'
        mock_identity_get.return_value = mock_identity

        self.assertFalse(mycroft.api.is_paired())

        with self.assertRaises(mycroft.api.BackendDown):
            mycroft.api.is_paired(ignore_errors=False)
