#
# Copyright 2017 the original author or authors.
#
# 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.
#
from unittest import main, TestCase

from voltha.extensions.omci.database.mib_db_ext import *
from voltha.extensions.omci.database.mib_db_api import MODIFIED_KEY, CREATED_KEY,\
    DEVICE_ID_KEY, MDS_KEY, LAST_SYNC_KEY
from voltha.extensions.omci.omci_cc import UNKNOWN_CLASS_ATTRIBUTE_KEY
from mock.mock_adapter_agent import MockAdapterAgent, MockDevice
from nose.tools import raises, assert_raises
import time

_DEVICE_ID = 'br-549'


class TestOmciMibDbExt(TestCase):

    def setUp(self):
        self.adapter_agent = MockAdapterAgent()
        self.adapter_agent.add_device(MockDevice(_DEVICE_ID))  # For Entity class lookups
        self.db = MibDbExternal(self.adapter_agent)

    def tearDown(self):
        self.db.stop()

    def test_start_stop(self):
        # Simple start stop
        self.assertFalse(self.db.active)
        self.db.start()
        self.assertTrue(self.db.active)
        self.db.stop()
        self.assertFalse(self.db.active)

        # Start after start still okay
        self.db.start()
        self.db.start()
        self.assertTrue(self.db.active)

        self.db.stop()
        self.db.stop()
        self.assertFalse(self.db.active)

    @raises(DatabaseStateError)
    def test_bad_state_add(self):
        self.db.add(_DEVICE_ID)

    @raises(DatabaseStateError)
    def test_bad_state_remove(self):
        self.db.remove(_DEVICE_ID)

    @raises(DatabaseStateError)
    def test_bad_state_query_1(self):
        self.db.query(_DEVICE_ID, 0)

    @raises(DatabaseStateError)
    def test_bad_state_query_2(self):
        self.db.query(_DEVICE_ID, 0, 0)

    @raises(DatabaseStateError)
    def test_bad_state_query_3(self):
        self.db.query(_DEVICE_ID, 0, 0, 'test')

    @raises(DatabaseStateError)
    def test_bad_state_set(self):
        self.db.set(_DEVICE_ID, 0, 0, {'test': 123})

    @raises(DatabaseStateError)
    def test_bad_state_delete(self):
        self.db.delete(_DEVICE_ID, 0, 0)

    @raises(KeyError)
    def test_no_device_query(self):
        self.db.start()
        self.db.query(_DEVICE_ID)

    def test_no_device_last_sync(self):
        self.db.start()
        # Returns None, not a KeyError
        value = self.db.get_last_sync(_DEVICE_ID)
        self.assertIsNone(value)

    def test_no_device_mds(self):
        self.db.start()
        # Returns None, not a KeyError
        value = self.db.get_mib_data_sync(_DEVICE_ID)
        self.assertIsNone(value)

    @raises(KeyError)
    def test_no_device_save_last_sync(self):
        self.db.start()
        self.db.save_last_sync(_DEVICE_ID, datetime.utcnow())

    @raises(KeyError)
    def test_no_device_save_mds(self):
        self.db.start()
        self.db.save_mib_data_sync(_DEVICE_ID, 123)

    def test_param_types(self):
        self.db.start()
        assert_raises(TypeError, self.db.add, 123)
        assert_raises(TypeError, self.db.remove, 123)
        assert_raises(TypeError, self.db.query, 123)

        assert_raises(TypeError, self.db.get_mib_data_sync, 123)
        assert_raises(TypeError, self.db.save_mib_data_sync, 123, 0)
        assert_raises(TypeError, self.db.save_mib_data_sync, _DEVICE_ID, 'zero')

        assert_raises(TypeError, self.db.get_last_sync, 123)
        assert_raises(TypeError, self.db.save_last_sync, 123, datetime.utcnow())
        assert_raises(TypeError, self.db.save_last_sync, _DEVICE_ID, 'bad-date')

        assert_raises(TypeError, self.db.set, 123, 0, 0, {'test': 0})
        assert_raises(TypeError, self.db.set, None, 0, 0, {'test': 0})
        assert_raises(ValueError, self.db.set, _DEVICE_ID, None, 0, {'test': 0})
        assert_raises(ValueError, self.db.set, _DEVICE_ID, 0, None, {'test': 0})
        assert_raises(TypeError, self.db.set, _DEVICE_ID, 0, 0, None)
        assert_raises(TypeError, self.db.set, _DEVICE_ID, 0, 0, 'not-a-dict')

        assert_raises(ValueError, self.db.set, _DEVICE_ID, -1, 0, {'test': 0})
        assert_raises(ValueError, self.db.set, _DEVICE_ID, 0x10000, 0, {'test': 0})
        assert_raises(ValueError, self.db.set, _DEVICE_ID, 0, -1, {'test': 0})
        assert_raises(ValueError, self.db.set, _DEVICE_ID, 0, 0x10000, {'test': 0})

        assert_raises(TypeError, self.db.delete, 123, 0, 0)
        assert_raises(ValueError, self.db.delete, _DEVICE_ID, -1, 0)
        assert_raises(ValueError, self.db.delete, _DEVICE_ID, 0x10000, 0)
        assert_raises(ValueError, self.db.delete, _DEVICE_ID, 0, -1)
        assert_raises(ValueError, self.db.delete, _DEVICE_ID, 0, 0x10000)

    def test_add_remove_device(self):
        self.db.start()

        # Remove of non-existent device is not an error
        assert_raises(KeyError, self.db.query, _DEVICE_ID)
        self.db.remove(_DEVICE_ID)

        start_time = datetime.utcnow()
        self.db.add(_DEVICE_ID)
        dev_data = self.db.query(_DEVICE_ID)

        self.assertEqual(dev_data[DEVICE_ID_KEY], _DEVICE_ID)
        self.assertEquals(dev_data[MDS_KEY], 0)
        self.assertIsNone(dev_data[LAST_SYNC_KEY])
        self.assertEqual(dev_data[VERSION_KEY], MibDbExternal.CURRENT_VERSION)

        self.assertGreaterEqual(self.db.created, start_time)

        # Remove it
        self.db.remove(_DEVICE_ID)
        assert_raises(KeyError, self.db.query, _DEVICE_ID)

        # Remove of non-existant dev okay
        self.db.remove(_DEVICE_ID +'abcd')

        # Overwrite tests
        self.db.add(_DEVICE_ID)
        assert_raises(KeyError, self.db.add, _DEVICE_ID)
        self.db.add(_DEVICE_ID, overwrite=True)  # This is okay

    def test_mib_data_sync(self):
        self.db.start()
        self.db.add(_DEVICE_ID)
        self.assertEquals(self.db.get_mib_data_sync(_DEVICE_ID), 0)

        self.db.save_mib_data_sync(_DEVICE_ID, 100)
        self.assertEqual(self.db.get_mib_data_sync(_DEVICE_ID), 100)

        assert_raises(ValueError, self.db.save_mib_data_sync, _DEVICE_ID, -1)
        assert_raises(ValueError, self.db.save_mib_data_sync, _DEVICE_ID, 256)

    def test_last_sync(self):
        self.db.start()
        self.assertIsNone(self.db.get_last_sync(_DEVICE_ID))

        self.db.add(_DEVICE_ID)
        self.assertIsNone(self.db.get_last_sync(_DEVICE_ID))

        now = datetime.utcnow()