# Copyright 2016 Google LLC
#
# 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


class TestConfig(unittest.TestCase):
    PROJECT = 'PROJECT'
    CONFIG_NAME = 'config_name'
    CONFIG_PATH = 'projects/%s/configs/%s' % (PROJECT, CONFIG_NAME)

    @staticmethod
    def _get_target_class():
        from google.cloud.runtimeconfig.config import Config

        return Config

    def _make_one(self, *args, **kw):
        return self._get_target_class()(*args, **kw)

    def _verifyResourceProperties(self, config, resource):
        from google.cloud.runtimeconfig._helpers import (
            config_name_from_full_name)

        if 'name' in resource:
            self.assertEqual(config.full_name, resource['name'])
            self.assertEqual(
                config.name,
                config_name_from_full_name(resource['name']))
        if 'description' in resource:
            self.assertEqual(config.description, resource['description'])

    def test_ctor(self):
        client = _Client(project=self.PROJECT)
        config = self._make_one(name=self.CONFIG_NAME,
                                client=client)
        self.assertEqual(config.name, self.CONFIG_NAME)
        self.assertEqual(config.project, self.PROJECT)
        self.assertEqual(config.full_name, self.CONFIG_PATH)

    def test_ctor_w_no_name(self):
        client = _Client(project=self.PROJECT)
        config = self._make_one(name=None, client=client)
        with self.assertRaises(ValueError):
            getattr(config, 'full_name')

    def test_exists_miss_w_bound_client(self):
        conn = _Connection()
        client = _Client(project=self.PROJECT, connection=conn)
        config = self._make_one(client=client, name=self.CONFIG_NAME)

        self.assertFalse(config.exists())

        self.assertEqual(len(conn._requested), 1)
        req = conn._requested[0]
        self.assertEqual(req['method'], 'GET')
        self.assertEqual(req['path'], '/%s' % (self.CONFIG_PATH,))
        self.assertEqual(req['query_params'], {'fields': 'name'})

    def test_exists_hit_w_alternate_client(self):
        conn1 = _Connection()
        CLIENT1 = _Client(project=self.PROJECT, connection=conn1)
        conn2 = _Connection({})
        CLIENT2 = _Client(project=self.PROJECT, connection=conn2)
        config = self._make_one(client=CLIENT1, name=self.CONFIG_NAME)

        self.assertTrue(config.exists(client=CLIENT2))

        self.assertEqual(len(conn1._requested), 0)
        self.assertEqual(len(conn2._requested), 1)
        req = conn2._requested[0]
        self.assertEqual(req['method'], 'GET')
        self.assertEqual(req['path'], '/%s' % (self.CONFIG_PATH,))
        self.assertEqual(req['query_params'], {'fields': 'name'})

    def test_reload_w_empty_resource(self):
        RESOURCE = {}
        conn = _Connection(RESOURCE)
        client = _Client(project=self.PROJECT, connection=conn)
        config = self._make_one(name=self.CONFIG_NAME, client=client)

        config.reload()

        self.assertEqual(len(conn._requested), 1)
        req = conn._requested[0]
        # Name should not be overwritten if not in the response.
        self.assertEqual(self.CONFIG_NAME, config.name)
        self.assertEqual(req['method'], 'GET')
        self.assertEqual(req['path'], '/%s' % (self.CONFIG_PATH,))
        self._verifyResourceProperties(config, RESOURCE)

    def test_reload_w_bound_client(self):
        RESOURCE = {'name': self.CONFIG_PATH, 'description': 'hello'}
        conn = _Connection(RESOURCE)
        client = _Client(project=self.PROJECT, connection=conn)
        config = self._make_one(name=self.CONFIG_NAME, client=client)

        config.reload()

        self.assertEqual(len(conn._requested), 1)
        req = conn._requested[0]
        self.assertEqual(req['method'], 'GET')
        self.assertEqual(req['path'], '/%s' % (self.CONFIG_PATH,))
        self._verifyResourceProperties(config, RESOURCE)

    def test_reload_w_alternate_client(self):
        RESOURCE = {'name': self.CONFIG_PATH, 'description': 'hello'}
        conn1 = _Connection()
        CLIENT1 = _Client(project=self.PROJECT, connection=conn1)
        conn2 = _Connection(RESOURCE)
        CLIENT2 = _Client(project=self.PROJECT, connection=conn2)
        config = self._make_one(name=self.CONFIG_NAME, client=CLIENT1)

        config.reload(client=CLIENT2)

        self.assertEqual(len(conn1._requested), 0)
        self.assertEqual(len(conn2._requested), 1)
        req = conn2._requested[0]
        self.assertEqual(req['method'], 'GET')
        self.assertEqual(req['path'], '/%s' % (self.CONFIG_PATH,))
        self._verifyResourceProperties(config, RESOURCE)

    def test_variable(self):
        VARIABLE_NAME = 'my-variable/abcd'
        VARIABLE_PATH = '%s/variables/%s' % (self.CONFIG_PATH, VARIABLE_NAME)
        conn = _Connection()
        client = _Client(project=self.PROJECT, connection=conn)
        config = self._make_one(name=self.CONFIG_NAME, client=client)

        variable = config.variable(VARIABLE_NAME)

        self.assertEqual(variable.name, VARIABLE_NAME)
        self.assertEqual(variable.full_name, VARIABLE_PATH)
        self.assertEqual(len(conn._requested), 0)

    def test_get_variable_w_bound_client(self):
        from google.api_core import datetime_helpers

        VARIABLE_NAME = 'my-variable/abcd'
        VARIABLE_PATH = '%s/variables/%s' % (self.CONFIG_PATH, VARIABLE_NAME)
        RESOURCE = {
            'name': VARIABLE_PATH,
            'value': 'bXktdmFyaWFibGUtdmFsdWU=',  # base64 my-variable-value
            'updateTime': '2016-04-14T21:21:54.123456789Z',
            'state': 'VARIABLE_STATE_UNSPECIFIED',
        }
        conn = _Connection(RESOURCE)
        client = _Client(project=self.PROJECT, connection=conn)
        config = self._make_one(name=self.CONFIG_NAME, client=client)

        variable = config.get_variable(VARIABLE_NAME)

        self.assertEqual(variable.name, VARIABLE_NAME)
        self.assertEqual(variable.full_name, VARIABLE_PATH)
        DatetimeWithNanoseconds = datetime_helpers.DatetimeWithNanoseconds
        self.assertEqual(
            variable.update_time,
            DatetimeWithNanoseconds.from_rfc3339(RESOURCE['updateTime']))
        self.assertEqual(variable.state, RESOURCE['state'])

        self.assertEqual(len(conn._requested), 1)
        req = conn._requested[0]
        self.assertEqual(req['method'], 'GET')
        self.assertEqual(req['path'], '/%s' % (VARIABLE_PATH,))

    def test_get_variable_w_notfound(self):
        VARIABLE_NAME = 'my-variable/abcd'
        conn = _Connection()
        client = _Client(project=self.PROJECT, connection=conn)
        config = self._make_one(name=self.CONFIG_NAME, client=client)
        variable = config.get_variable(VARIABLE_NAME)
        self.assertIsNone(variable)

    def test_get_variable_w_alternate_client(self):
        from google.cloud._helpers import _rfc3339_to_datetime

        VARIABLE_NAME = 'my-variable/abcd'
        VARIABLE_PATH = '%s/variables/%s' % (self.CONFIG_PATH, VARIABLE_NAME)
        RESOURCE = {
            'name': VARIABLE_PATH,
            'value': 'bXktdmFyaWFibGUtdmFsdWU=',  # base64 my-variable-value
            'updateTime': '2016-04-14T21:21:54.5000Z',
            'state': 'VARIABLE_STATE_UNSPECIFIED',
        }
        conn1 = _Connection()
        CLIENT1 = _Client(project=self.PROJECT, connection=conn1)
        conn2 = _Connection(RESOURCE)
        CLIENT2 = _Client(project=self.PROJECT, connection=conn2)
        config = self._make_one(client=CLIENT1, name=self.CONFIG_NAME)

        variable = config.get_variable(VARIABLE_NAME, client=CLIENT2)

        self.assertEqual(variable.name, VARIABLE_NAME)
        self.assertEqual(variable.full_name, VARIABLE_PATH)
        self.assertEqual(
            variable.update_time,
            _rfc3339_to_datetime(RESOURCE['updateTime']))
        self.assertEqual(variable.state, RESOURCE['state'])

        self.assertEqual(len(conn1._requested), 0)
        self.assertEqual(len(conn2._requested), 1)
        req = conn2._requested[0]
        self.assertEqual(req['method'], 'GET')
        self.assertEqual(req['path'], '/%s' % (VARIABLE_PATH,))

    def test_list_variables_empty(self):
        import six

        conn = _Connection({})
        client = _Client(project=self.PROJECT, connection=conn)
        config = self._make_one(name=self.CONFIG_NAME, client=client)

        iterator = config.list_variables()
        page = six.next(iterator.pages)
        variables = list(page)
        token = iterator.next_page_token

        self.assertEqual(variables, [])
        self.assertIsNone(token)
        self.assertEqual(len(conn._requested), 1)
        req = conn._requested[0]
        self.assertEqual(req['method'], 'GET')
        PATH = 'projects/%s/configs/%s/variables' % (
            self.PROJECT, self.CONFIG_NAME)
        self.assertEqual(req['path'], '/%s' % (PATH,))

    def test_list_variables_defaults(self):
        import six
        from google.cloud._helpers import _rfc3339_to_datetime
        from google.cloud.runtimeconfig.variable import Variable

        VARIABLE_1 = 'variable-one'
        VARIABLE_2 = 'variable/two'
        PATH = 'projects/%s/configs/%s/variables' % (
            self.PROJECT, self.CONFIG_NAME)
        TOKEN = 'TOKEN'
        DATA = {
            'nextPageToken': TOKEN,
            'variables': [
                {'name': '%s/%s' % (PATH, VARIABLE_1),
                 'updateTime': '2016-04-14T21:21:54.5000Z'},
                {'name': '%s/%s' % (PATH, VARIABLE_2),
                 'updateTime': '2016-04-21T21:21:54.6000Z'},
            ]
        }

        conn = _Connection(DATA)
        client = _Client(project=self.PROJECT, connection=conn)
        config = self._make_one(name=self.CONFIG_NAME, client=client)

        iterator = config.list_variables()
        page = six.next(iterator.pages)
        variables = list(page)
        token = iterator.next_page_token

        self.assertEqual(len(variables), len(DATA['variables']))
        for found, expected in zip(variables, DATA['variables']):
            self.assertIsInstance(found, Variable)
            self.assertEqual(found.full_name, expected['name'])
            self.assertEqual(
                found.update_time,
                _rfc3339_to_datetime(expected['updateTime']))
        self.assertEqual(token, TOKEN)

        self.assertEqual(len(conn._requested), 1)
        req = conn._requested[0]
        self.assertEqual(req['method'], 'GET')
        self.assertEqual(req['path'], '/%s' % (PATH,))
        self.assertNotIn('filter', req['query_params'])

    def test_list_variables_explicit(self):
        import six
        from google.cloud._helpers import _rfc3339_to_datetime
        from google.cloud.runtimeconfig.variable import Variable

        VARIABLE_1 = 'variable-one'
        VARIABLE_2 = 'variable/two'
        PATH = 'projects/%s/configs/%s/variables' % (
            self.PROJECT, self.CONFIG_NAME)
        TOKEN = 'TOKEN'
        DATA = {
            'variables': [
                {'name': '%s/%s' % (PATH, VARIABLE_1),
                 'updateTime': '2016-04-14T21:21:54.5000Z'},
                {'name': '%s/%s' % (PATH, VARIABLE_2),
                 'updateTime': '2016-04-21T21:21:54.6000Z'},
            ]
        }

        conn = _Connection(DATA)
        client = _Client(project=self.PROJECT, connection=conn)
        config = self._make_one(name=self.CONFIG_NAME, client=client)

        iterator = config.list_variables(
            page_size=3,
            page_token=TOKEN,
            client=client)
        page = six.next(iterator.pages)
        variables = list(page)
        token = iterator.next_page_token

        self.assertEqual(len(variables), len(DATA['variables']))
        for found, expected in zip(variables, DATA['variables']):
            self.assertIsInstance(found, Variable)
            self.assertEqual(found.full_name, expected['name'])
            self.assertEqual(
                found.update_time,
                _rfc3339_to_datetime(expected['updateTime']))
        self.assertIsNone(token)

        self.assertEqual(len(conn._requested), 1)
        req = conn._requested[0]
        self.assertEqual(req['method'], 'GET')
        self.assertEqual(req['path'], '/%s' % (PATH,))
        self.assertEqual(
            req['query_params'],
            {
                'pageSize': 3,
                'pageToken': TOKEN,
            })


class _Client(object):

    _connection = None

    def __init__(self, project, connection=None):
        self.project = project
        self._connection = connection


class _Connection(object):

    def __init__(self, *responses):
        self._responses = responses
        self._requested = []

    def api_request(self, **kw):
        from google.cloud.exceptions import NotFound

        self._requested.append(kw)

        try:
            response, self._responses = self._responses[0], self._responses[1:]
        except IndexError:
            raise NotFound('miss')
        else:
            return response
