# Copyright 2014 Google 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

import mock


def _make_credentials():
    import google.auth.credentials
    return mock.Mock(spec=google.auth.credentials.Credentials)


def _make_entity_pb(project, kind, integer_id, name=None, str_val=None):
    from google.cloud.grpc.datastore.v1 import entity_pb2
    from google.cloud.datastore.helpers import _new_value_pb

    entity_pb = entity_pb2.Entity()
    entity_pb.key.partition_id.project_id = project
    path_element = entity_pb.key.path.add()
    path_element.kind = kind
    path_element.id = integer_id
    if name is not None and str_val is not None:
        value_pb = _new_value_pb(entity_pb, name)
        value_pb.string_value = str_val

    return entity_pb


class Test__get_gcd_project(unittest.TestCase):

    def _call_fut(self):
        from google.cloud.datastore.client import _get_gcd_project
        return _get_gcd_project()

    def test_no_value(self):
        environ = {}
        with mock.patch('os.getenv', new=environ.get):
            project = self._call_fut()
            self.assertIsNone(project)

    def test_value_set(self):
        from google.cloud.datastore.client import GCD_DATASET

        MOCK_PROJECT = object()
        environ = {GCD_DATASET: MOCK_PROJECT}
        with mock.patch('os.getenv', new=environ.get):
            project = self._call_fut()
            self.assertEqual(project, MOCK_PROJECT)


class Test__determine_default_project(unittest.TestCase):

    def _call_fut(self, project=None):
        from google.cloud.datastore.client import (
            _determine_default_project)
        return _determine_default_project(project=project)

    def _determine_default_helper(self, gcd=None, fallback=None,
                                  project_called=None):
        _callers = []

        def gcd_mock():
            _callers.append('gcd_mock')
            return gcd

        def fallback_mock(project=None):
            _callers.append(('fallback_mock', project))
            return fallback

        patch = mock.patch.multiple(
            'google.cloud.datastore.client',
            _get_gcd_project=gcd_mock,
            _base_default_project=fallback_mock)
        with patch:
            returned_project = self._call_fut(project_called)

        return returned_project, _callers

    def test_no_value(self):
        project, callers = self._determine_default_helper()
        self.assertIsNone(project)
        self.assertEqual(callers, ['gcd_mock', ('fallback_mock', None)])

    def test_explicit(self):
        PROJECT = object()
        project, callers = self._determine_default_helper(
            project_called=PROJECT)
        self.assertEqual(project, PROJECT)
        self.assertEqual(callers, [])

    def test_gcd(self):
        PROJECT = object()
        project, callers = self._determine_default_helper(gcd=PROJECT)
        self.assertEqual(project, PROJECT)
        self.assertEqual(callers, ['gcd_mock'])

    def test_fallback(self):
        PROJECT = object()
        project, callers = self._determine_default_helper(fallback=PROJECT)
        self.assertEqual(project, PROJECT)
        self.assertEqual(callers, ['gcd_mock', ('fallback_mock', None)])


class TestClient(unittest.TestCase):

    PROJECT = 'PROJECT'

    def setUp(self):
        from google.cloud.datastore import client as MUT

        self.original_cnxn_class = MUT.Connection
        MUT.Connection = _MockConnection

    def tearDown(self):
        from google.cloud.datastore import client as MUT

        MUT.Connection = self.original_cnxn_class

    @staticmethod
    def _get_target_class():
        from google.cloud.datastore.client import Client
        return Client

    def _make_one(self, project=PROJECT, namespace=None,
                  credentials=None, http=None):
        return self._get_target_class()(project=project,
                                        namespace=namespace,
                                        credentials=credentials,
                                        http=http)

    def test_ctor_w_project_no_environ(self):
        # Some environments (e.g. AppVeyor CI) run in GCE, so
        # this test would fail artificially.
        patch = mock.patch(
            'google.cloud.datastore.client._base_default_project',
            new=lambda project: None)
        with patch:
  
          self.assertRaises(EnvironmentError, self._make_one, None)

    def test_ctor_w_implicit_inputs(self):
        OTHER = 'other'
        creds = _make_credentials()
        default_called = []


        def fallback_mock(project):
            default_called.append(project)
            return project or OTHER

        klass = self._get_target_class()
        patch1 = mock.patch(
            'google.cloud.datastore.client._determine_default_project',
            new=fallback_mock)
        patch2 = mock.patch(
            'google.cloud.client.get_credentials',
            new=lambda: creds)

        with patch1:
            with patch2:
                client = klass()
        self.assertEqual(client.project, OTHER)
        self.assertIsNone(client.namespace)
        self.assertIsInstance(client._connection, _MockConnection)
        self.assertIs(client._connection.credentials, creds)
        self.assertIsNone(client._connection.http)
        self.assertIsNone(client.current_batch)
        self.assertIsNone(client.current_transaction)
        self.assertEqual(default_called, [None])

    def test_ctor_w_explicit_inputs(self):
        OTHER = 'other'
        NAMESPACE = 'namespace'
        creds = _make_credentials()
        http = object()
        client = self._make_one(project=OTHER,
                                namespace=NAMESPACE,
                                credentials=creds,
                                http=http)
        self.assertEqual(client.project, OTHER)
        self.assertEqual(client.namespace, NAMESPACE)
        self.assertIsInstance(client._connection, _MockConnection)
        self.assertIs(client._connection.credentials, creds)
        self.assertIs(client._connection.http, http)
        self.assertIsNone(client.current_batch)
        self.assertEqual(list(client._batch_stack), [])

    def test__push_batch_and__pop_batch(self):
        creds = _make_credentials()
        client = self._make_one(credentials=creds)
        batch = client.batch()
        xact = client.transaction()
        client._push_batch(batch)
        self.assertEqual(list(client._batch_stack), [batch])
        self.assertIs(client.current_batch, batch)
        self.assertIsNone(client.current_transaction)
        client._push_batch(xact)
        self.assertIs(client.current_batch, xact)
        self.assertIs(client.current_transaction, xact)
        # list(_LocalStack) returns in reverse order.
        self.assertEqual(list(client._batch_stack), [xact, batch])
        self.assertIs(client._pop_batch(), xact)
        self.assertEqual(list(client._batch_stack), [batch])
        self.assertIs(client._pop_batch(), batch)
        self.assertEqual(list(client._batch_stack), [])

    def test_get_miss(self):
        _called_with = []

        def _get_multi(*args, **kw):
            _called_with.append((args, kw))
            return []

        creds = _make_credentials()
        client = self._make_one(credentials=creds)
        client.get_multi = _get_multi

        key = object()

        self.assertIsNone(client.get(key))

        self.assertEqual(_called_with[0][0], ())
        self.assertEqual(_called_with[0][1]['keys'], [key])
        self.assertIsNone(_called_with[0][1]['missing'])
        self.assertIsNone(_called_w