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


class TestConnection(unittest.TestCase):

    PROJECT = 'project'
    FILTER = 'logName:syslog AND severity>=ERROR'

    def _getTargetClass(self):
        from google.cloud.logging.connection import Connection
        return Connection

    def _makeOne(self, *args, **kw):
        return self._getTargetClass()(*args, **kw)

    def test_default_url(self):
        creds = _Credentials()
        conn = self._makeOne(creds)
        klass = self._getTargetClass()
        self.assertEqual(conn.credentials._scopes, klass.SCOPE)


class Test_LoggingAPI(unittest.TestCase):

    PROJECT = 'project'
    LIST_ENTRIES_PATH = 'entries:list'
    WRITE_ENTRIES_PATH = 'entries:write'
    LOGGER_NAME = 'LOGGER_NAME'
    FILTER = 'logName:syslog AND severity>=ERROR'

    def _getTargetClass(self):
        from google.cloud.logging.connection import _LoggingAPI
        return _LoggingAPI

    def _makeOne(self, *args, **kw):
        return self._getTargetClass()(*args, **kw)

    def test_ctor(self):
        connection = object()
        api = self._makeOne(connection)
        self.assertTrue(api._connection is connection)

    @staticmethod
    def _make_timestamp():
        from datetime import datetime
        from google.cloud._helpers import UTC

        NOW = datetime.utcnow().replace(tzinfo=UTC)
        return _datetime_to_rfc3339_w_nanos(NOW)

    def test_list_entries_no_paging(self):
        TIMESTAMP = self._make_timestamp()
        IID = 'IID'
        TEXT = 'TEXT'
        SENT = {
            'projectIds': [self.PROJECT],
        }
        TOKEN = 'TOKEN'
        RETURNED = {
            'entries': [{
                'textPayload': TEXT,
                'insertId': IID,
                'resource': {
                    'type': 'global',
                },
                'timestamp': TIMESTAMP,
                'logName': 'projects/%s/logs/%s' % (
                    self.PROJECT, self.LOGGER_NAME),
            }],
            'nextPageToken': TOKEN,
        }
        conn = _Connection(RETURNED)
        api = self._makeOne(conn)

        entries, token = api.list_entries([self.PROJECT])

        self.assertEqual(entries, RETURNED['entries'])
        self.assertEqual(token, TOKEN)

        self.assertEqual(conn._called_with['method'], 'POST')
        path = '/%s' % self.LIST_ENTRIES_PATH
        self.assertEqual(conn._called_with['path'], path)
        self.assertEqual(conn._called_with['data'], SENT)

    def test_list_entries_w_paging(self):
        from google.cloud.logging import DESCENDING

        PROJECT1 = 'PROJECT1'
        PROJECT2 = 'PROJECT2'
        TIMESTAMP = self._make_timestamp()
        IID1 = 'IID1'
        IID2 = 'IID2'
        PAYLOAD = {'message': 'MESSAGE', 'weather': 'partly cloudy'}
        PROTO_PAYLOAD = PAYLOAD.copy()
        PROTO_PAYLOAD['@type'] = 'type.googleapis.com/testing.example'
        TOKEN = 'TOKEN'
        PAGE_SIZE = 42
        SENT = {
            'projectIds': [PROJECT1, PROJECT2],
            'filter': self.FILTER,
            'orderBy': DESCENDING,
            'pageSize': PAGE_SIZE,
            'pageToken': TOKEN,
        }
        RETURNED = {
            'entries': [{
                'jsonPayload': PAYLOAD,
                'insertId': IID1,
                'resource': {
                    'type': 'global',
                },
                'timestamp': TIMESTAMP,
                'logName': 'projects/%s/logs/%s' % (
                    self.PROJECT, self.LOGGER_NAME),
            }, {
                'protoPayload': PROTO_PAYLOAD,
                'insertId': IID2,
                'resource': {
                    'type': 'global',
                },
                'timestamp': TIMESTAMP,
                'logName': 'projects/%s/logs/%s' % (
                    self.PROJECT, self.LOGGER_NAME),
            }],
        }
        conn = _Connection(RETURNED)
        api = self._makeOne(conn)

        entries, token = api.list_entries(
            projects=[PROJECT1, PROJECT2], filter_=self.FILTER,
            order_by=DESCENDING, page_size=PAGE_SIZE, page_token=TOKEN)

        self.assertEqual(entries, RETURNED['entries'])
        self.assertEqual(token, None)

        self.assertEqual(conn._called_with['method'], 'POST')
        path = '/%s' % self.LIST_ENTRIES_PATH
        self.assertEqual(conn._called_with['path'], path)
        self.assertEqual(conn._called_with['data'], SENT)

    def test_write_entries_single(self):
        TEXT = 'TEXT'
        ENTRY = {
            'textPayload': TEXT,
            'resource': {
                'type': 'global',
            },
            'logName': 'projects/%s/logs/%s' % (
                self.PROJECT, self.LOGGER_NAME),
        }
        SENT = {
            'entries': [ENTRY],
        }
        conn = _Connection({})
        api = self._makeOne(conn)

        api.write_entries([ENTRY])

        self.assertEqual(conn._called_with['method'], 'POST')
        path = '/%s' % self.WRITE_ENTRIES_PATH
        self.assertEqual(conn._called_with['path'], path)
        self.assertEqual(conn._called_with['data'], SENT)

    def test_write_entries_multiple(self):
        TEXT = 'TEXT'
        LOG_NAME = 'projects/%s/logs/%s' % (self.PROJECT, self.LOGGER_NAME)
        RESOURCE = {
            'type': 'global',
        }
        LABELS = {
            'baz': 'qux',
            'spam': 'eggs',
        }
        ENTRY1 = {
            'textPayload': TEXT,
        }
        ENTRY2 = {
            'jsonPayload': {'foo': 'bar'},
        }
        SENT = {
            'logName': LOG_NAME,
            'resource': RESOURCE,
            'labels': LABELS,
            'entries': [ENTRY1, ENTRY2],
        }
        conn = _Connection({})
        api = self._makeOne(conn)

        api.write_entries([ENTRY1, ENTRY2], LOG_NAME, RESOURCE, LABELS)

        self.assertEqual(conn._called_with['method'], 'POST')
        path = '/%s' % self.WRITE_ENTRIES_PATH
        self.assertEqual(conn._called_with['path'], path)
        self.assertEqual(conn._called_with['data'], SENT)

    def test_logger_delete(self):
        path = '/projects/%s/logs/%s' % (self.PROJECT, self.LOGGER_NAME)
        conn = _Connection({})
        api = self._makeOne(conn)

        api.logger_delete(self.PROJECT, self.LOGGER_NAME)

        self.assertEqual(conn._called_with['method'], 'DELETE')
        self.assertEqual(conn._called_with['path'], path)


class Test_SinksAPI(unittest.TestCase):

    PROJECT = 'project'
    FILTER = 'logName:syslog AND severity>=ERROR'
    LIST_SINKS_PATH = 'projects/%s/sinks' % (PROJECT,)
    SINK_NAME = 'sink_name'
    SINK_PATH = 'projects/%s/sinks/%s' % (PROJECT, SINK_NAME)
    DESTINATION_URI = 'faux.googleapis.com/destination'

    def _getTargetClass(self):
        from google.cloud.logging.connection import _SinksAPI
        return _SinksAPI

    def _makeOne(self, *args, **kw):
        return self._getTargetClass()(*args, **kw)

    def test_ctor(self):
        connection = object()
        api = self._makeOne(connection)
        self.assertTrue(api._connection is connection)

    def test_list_sinks_no_paging(self):
        TOKEN = 'TOKEN'
        RETURNED = {
            'sinks': [{
                'name': self.SINK_PATH,
                'filter': self.FILTER,
                'destination': self.DESTINATION_URI,
            }],
            'nextPageToken': TOKEN,
        }
        conn = _Connection(RETURNED)
        api = self._makeOne(conn)

        sinks, token = api.list_sinks(self.PROJECT)

        self.assertEqual(sinks, RETURNED['sinks'])
        self.assertEqual(token, TOKEN)

        self.assertEqual(conn._called_with['method'], 'GET')
        path = '/%s' % (self.LIST_SINKS_PATH,)
        self.assertEqual(conn._called_with['path'], path)
        self.assertEqual(conn._called_with['query_params'], {})

    def test_list_sinks_w_paging(self):
        TOKEN = 'TOKEN'
        PAGE_SIZE = 42
        RETURNED = {
            'sinks': [{
                'name': self.SINK_PATH,
                'filter': self.FILTER,
                'destination': self.DESTINATION_URI,
            }],
        }
        conn = _Connection(RETURNED)
        api = self._makeOne(conn)

        sinks, token = api.list_sinks(
            self.PROJECT, page_size=PAGE_SIZE, page_token=TOKEN)

        self.assertEqual(sinks, RETURNED['sinks'])
        self.assertEqual(token, None)

        self.assertEqual(conn._called_with['method'], 'GET')
        path = '/%s' % (self.LIST_SINKS_PATH,)
        self.assertEqual(conn._called_with['path'], path)
        self.assertEqual(conn._called_with['query_params'],
                         {'pageSize': PAGE_SIZE, 'pageToken': TOKEN})

    def test_sink_create_conflict(self):
        from google.cloud.exceptions import Conflict
        SENT = {
            'name': self.SINK_NAME,
            'filter': self.FILTER,
            'destination': self.DESTINATION_URI,
        }
        conn = _Connection()
        conn._raise_conflict = True
        api = self._makeOne(conn)

        with self.assertRaises(Conflict):
            api.sink_create(
                self.PROJECT, self.SINK_NAME, self.FILTER,
                self.DESTINATION_URI)

        self.assertEqual(conn._called_with['method'], 'POST')
        path = '/projects/%s/sinks' % (self.PROJECT,)
        self.assertEqual(conn._called_with['path'], path)
        self.assertEqual(conn._called_with['data'], SENT)

    def test_sink_create_ok(self):
        SENT = {
            'name': self.SINK_NAME,
            'filter': self.FILTER,
            'destination': self.DESTINATION_URI,
        }
        conn = _Connection({})
        api = self._makeOne(conn)

        api.sink_create(
            self.PROJECT, self.SINK_NAME, self.FILTER, self.DESTINATION_URI)

        self.assertEqual(conn._called_with['method'], 'POST')
        path = '/projects/%s/sinks' % (self.PROJECT,)
        self.assertEqual(conn._called_with['path'], path)
        self.assertEqual(conn._called_with['data'], SENT)

    def test_sink_get_miss(self):
        from google.cloud.exceptions import NotFound
        conn = _Connection()
        api = self._makeOne(conn)

        with self.assertRaises(NotFound):
            api.sink_get(self.PROJECT, self.SINK_NAME)

        self.assertEqual(conn._called_with['method'], 'GET')
        path = '/projects/%s/sinks/%s' % (self.PROJECT, self.SINK_NAME)
        self.assertEqual(conn._called_with['path'], path)

    def test_sink_get_hit(self):
        RESPONSE = {
            'name': self.SINK_PATH,
            'filter': self.FILTER,
            'destination': self.DESTINATION_URI,
        }
        conn = _Connection(RESPONSE)
        api = self._makeOne(conn)

        response = api.sink_get(self.PROJECT, self.SINK_NAME)

        self.assertEqual(response, RESPONSE)
        self.assertEqual(conn._called_with['method'], 'GET')
        path = '/projects/%s/sinks/%s' % (self.PROJECT, self.SINK_NAME)
        self.assertEqual(conn._called_with['path'], path)

    def test_sink_update_miss(self):
        from google.cloud.exceptions import NotFound
        SENT = {
            'name': self.SINK_NAME,
            'filter': self.FILTER,
            'destination': self.DESTINATION_URI,
        }
        conn = _Connection()
        api = self._makeOne(conn)

        with self.assertRaises(NotFound):
            api.sink_update(
                self.PROJECT, self.SINK_NAME, self.FILTER,
                self.DESTINATION_URI)

        self.assertEqual(conn._called_with['method'], 'PUT')
        path = '/projects/%s/sinks/%s' % (self.PROJECT, self.SINK_NAME)
        self.assertEqual(conn._called_with['path'], path)
        self.assertEqual(conn._called_with['data'], SENT)

    def test_sink_update_hit(self):
        SENT = {
            'name': self.SINK_NAME,
            'filter': self.FILTER,
            'destination': self.DESTINATION_URI,
        }
        conn = _Connection({})
        api = self._makeOne(conn)

        api.sink_update(
            self.PROJECT, self.SINK_NAME, self.FILTER, self.DESTINATION_URI)

        self.assertEqual(conn._called_with['method'], 'PUT')
        path = '/projects/%s/sinks/%s' % (self.PROJECT, self.SINK_NAME)
        self.assertEqual(conn._called_with['path'], path)
        self.assertEqual(conn._called_with['data'], SENT)

    def test_sink_delete_miss(self):
        from google.cloud.exceptions import NotFound
        conn = _Connection()
        api = self._makeOne(conn)

        with self.assertRaises(NotFound):
            api.sink_delete(self.PROJECT, self.SINK_NAME)

        self.assertEqual(conn._called_with['method'], 'DELETE')
        path = '/projects/%s/sinks/%s' % (self.PROJECT, self.SINK_NAME)
        self.assertEqual(conn._called_with['path'], path)

    def test_sink_delete_hit(self):
        conn = _Connection({})
        api = self._makeOne(conn)

        api.sink_delete(self.PROJECT, self.SINK_NAME)

        self.assertEqual(conn._called_with['method'], 'DELETE')
        path = '/projects/%s/sinks/%s' % (self.PROJECT, self.SINK_NAME)
        self.assertEqual(conn._called_with['path'], path)


class Test_MetricsAPI(unittest.TestCase):

    PROJECT = 'project'
    FILTER = 'logName:syslog AND severity>=ERROR'
    LIST_METRICS_PATH = 'projects/%s/metrics' % (PROJECT,)
    METRIC_NAME = 'metric_name'
    METRIC_PATH = 'projects/%s/metrics/%s' % (PROJECT, METRIC_NAME)
    DESCRIPTION = 'DESCRIPTION'

    def _getTargetClass(self):
        from google.cloud.logging.connection import _MetricsAPI
        return _MetricsAPI

    def _makeOne(self, *args, **kw):
        return self._getTargetClass()(*args, **kw)

    def test_list_metrics_no_paging(self):
        TOKEN = 'TOKEN'
        RETURNED = {
            'metrics': [{
                'name': self.METRIC_PATH,
                'filter': self.FILTER,
            }],
            'nextPageToken': TOKEN,
        }
        conn = _Connection(RETURNED)
        api = self._makeOne(conn)

        metrics, token = api.list_metrics(self.PROJECT)

        self.assertEqual(metrics, RETURNED['metrics'])
        self.assertEqual(token, TOKEN)

        self.assertEqual(conn._called_with['method'], 'GET')
        path = '/%s' % (self.LIST_METRICS_PATH,)
        self.assertEqual(conn._called_with['path'], path)

    def test_list_metrics_w_paging(self):
        TOKEN = 'TOKEN'
        PAGE_SIZE = 42
        RETURNED = {
            'metrics': [{
                'name': self.METRIC_PATH,
                'filter': self.FILTER,
            }],
        }
        conn = _Connection(RETURNED)
        api = self._makeOne(conn)

        metrics, token = api.list_metrics(
            self.PROJECT, page_size=PAGE_SIZE, page_token=TOKEN)

        self.assertEqual(metrics, RETURNED['metrics'])
        self.assertEqual(token, None)

        self.assertEqual(conn._called_with['method'], 'GET')
        path = '/%s' % (self.LIST_METRICS_PATH,)
        self.assertEqual(conn._called_with['path'], path)
        self.assertEqual(conn._called_with['query_params'],
                         {'pageSize': PAGE_SIZE, 'pageToken': TOKEN})

    def test_metric_create_conflict(self):
        from google.cloud.exceptions import Conflict
        SENT = {
            'name': self.METRIC_NAME,
            'filter': self.FILTER,
            'description': self.DESCRIPTION,
        }
        conn = _Connection()
        conn._raise_conflict = True
        api = self._makeOne(conn)

        with self.assertRaises(Conflict):
            api.metric_create(
                self.PROJECT, self.METRIC_NAME, self.FILTER,
                self.DESCRIPTION)

        self.assertEqual(conn._called_with['method'], 'POST')
        path = '/projects/%s/metrics' % (self.PROJECT,)
        self.assertEqual(conn._called_with['path'], path)
        self.assertEqual(conn._called_with['data'], SENT)

    def test_metric_create_ok(self):
        SENT = {
            'name': self.METRIC_NAME,
            'filter': self.FILTER,
            'description': self.DESCRIPTION,
        }
        conn = _Connection({})
        api = self._makeOne(conn)

        api.metric_create(
            self.PROJECT, self.METRIC_NAME, self.FILTER, self.DESCRIPTION)

        self.assertEqual(conn._called_with['method'], 'POST')
        path = '/projects/%s/metrics' % (self.PROJECT,)
        self.assertEqual(conn._called_with['path'], path)
        self.assertEqual(conn._called_with['data'], SENT)

    def test_metric_get_miss(self):
        from google.cloud.exceptions import NotFound
        conn = _Connection()
        api = self._makeOne(conn)

        with self.assertRaises(NotFound):
            api.metric_get(self.PROJECT, self.METRIC_NAME)

        self.assertEqual(conn._called_with['method'], 'GET')
        path = '/projects/%s/metrics/%s' % (self.PROJECT, self.METRIC_NAME)
        self.assertEqual(conn._called_with['path'], path)

    def test_metric_get_hit(self):
        RESPONSE = {
            'name': self.METRIC_NAME,
            'filter': self.FILTER,
            'description': self.DESCRIPTION,
        }
        conn = _Connection(RESPONSE)
        api = self._makeOne(conn)

        response = api.metric_get(self.PROJECT, self.METRIC_NAME)

        self.assertEqual(response, RESPONSE)
        self.assertEqual(conn._called_with['method'], 'GET')
        path = '/projects/%s/metrics/%s' % (self.PROJECT, self.METRIC_NAME)
        self.assertEqual(conn._called_with['path'], path)

    def test_metric_update_miss(self):
        from google.cloud.exceptions import NotFound
        SENT = {
            'name': self.METRIC_NAME,
            'filter': self.FILTER,
            'description': self.DESCRIPTION,
        }
        conn = _Connection()
        api = self._makeOne(conn)

        with self.assertRaises(NotFound):
            api.metric_update(
                self.PROJECT, self.METRIC_NAME, self.FILTER,
                self.DESCRIPTION)

        self.assertEqual(conn._called_with['method'], 'PUT')
        path = '/projects/%s/metrics/%s' % (self.PROJECT, self.METRIC_NAME)
        self.assertEqual(conn._called_with['path'], path)
        self.assertEqual(conn._called_with['data'], SENT)

    def test_metric_update_hit(self):
        SENT = {
            'name': self.METRIC_NAME,
            'filter': self.FILTER,
            'description': self.DESCRIPTION,
        }
        conn = _Connection({})
        api = self._makeOne(conn)

        api.metric_update(
            self.PROJECT, self.METRIC_NAME, self.FILTER, self.DESCRIPTION)

        self.assertEqual(conn._called_with['method'], 'PUT')
        path = '/projects/%s/metrics/%s' % (self.PROJECT, self.METRIC_NAME)
        self.assertEqual(conn._called_with['path'], path)
        self.assertEqual(conn._called_with['data'], SENT)

    def test_metric_delete_miss(self):
        from google.cloud.exceptions import NotFound
        conn = _Connection()
        api = self._makeOne(conn)

        with self.assertRaises(NotFound):
            api.metric_delete(self.PROJECT, self.METRIC_NAME)

        self.assertEqual(conn._called_with['method'], 'DELETE')
        path = '/projects/%s/metrics/%s' % (self.PROJECT, self.METRIC_NAME)
        self.assertEqual(conn._called_with['path'], path)

    def test_metric_delete_hit(self):
        conn = _Connection({})
        api = self._makeOne(conn)

        api.metric_delete(self.PROJECT, self.METRIC_NAME)

        self.assertEqual(conn._called_with['method'], 'DELETE')
        path = '/projects/%s/metrics/%s' % (self.PROJECT, self.METRIC_NAME)
        self.assertEqual(conn._called_with['path'], path)


class _Credentials(object):

    _scopes = None

    @staticmethod
    def create_scoped_required():
        return True

    def create_scoped(self, scope):
        self._scopes = scope
        return self


class _Connection(object):

    _called_with = None
    _raise_conflict = False

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

    def api_request(self, **kw):
        from google.cloud.exceptions import Conflict
        from google.cloud.exceptions import NotFound
        self._called_with = kw
        if self._raise_conflict:
            raise Conflict('oops')
        try:
            response, self._responses = self._responses[0], self._responses[1:]
        except IndexError:
            raise NotFound('miss')
        return response


def _datetime_to_rfc3339_w_nanos(value):
    from google.cloud._helpers import _RFC3339_NO_FRACTION
    no_fraction = value.strftime(_RFC3339_NO_FRACTION)
    return '%s.%09dZ' % (no_fraction, value.microsecond * 1000)
