# -*- coding: utf-8 -*-
#
from six.moves import builtins
import simplejson
import testify as T
from mock import mock_open
from mock import patch

from threat_intel.util.api_cache import ApiCache


def assert_cache_written(mock_write, patched_open):
    T.assert_equal(mock_write.call_count, 1)

    for call in patched_open.mock_calls:
        name, args, kwargs = call
        if '().write' != name:
            continue

        return simplejson.loads(args[0])
    return None


def assert_cache_not_written(mock_write):
    T.assert_falsey(mock_write.called)
    return None


class ApiCacheFileIOTest(T.TestCase):

    """Allows for setting and retrieving results of API calls."""

    @T.setup
    def setup_filename(self):
        self._file_name = '/tmp/any_name_will_do'

    def _open_cache(self, initial_contents=None, update_cache=True):
        """Creates an ApiCache object, mocking the contents of the cache on disk.

        Args:
                initial_contents: A dict containing the initial contents of the cache
                update_cache: Specifies whether ApiCache should write out the
                              cache file when closing it
        Returns:
                ApiCache
        """
        if not initial_contents:
            initial_contents = {}

        file_contents = simplejson.dumps(initial_contents)
        mock_read = mock_open(read_data=file_contents)
        with patch.object(builtins, 'open', mock_read, create=True):
            api_cache = ApiCache(self._file_name, update_cache=update_cache)
            return api_cache

    def _close_cache(self, api_cache, cache_written=True):
        """Closes an ApiCache and reads the final contents that were written to disk.

        Args:
                api_cache: An ApiCache instance
                cache_written: Specifies whether it should test that the cache
                               was written out to the cache file or whether to
                               test that it was not written out
        Returns:
                A dict representing the contents of the cache that was written
                out to the cache file or `None` in case cache was not expected
                to be written out
        """
        mock_write = mock_open()
        with patch.object(builtins, 'open', mock_write, create=True) as patched_open:
            api_cache.close()

            if cache_written:
                return assert_cache_written(mock_write, patched_open)

            return assert_cache_not_written(mock_write)

    def test_create_cache(self):
        initial_contents = {
            'banana': {
                'apple': ['pear', 'panda'],
                'sumo': False,
                'rebel_base_count': 42
            },
            'skiddo': 'Fo Sure',
            'pi': 3.1415
        }

        api_cache = self._open_cache(initial_contents)
        final_contents = self._close_cache(api_cache)
        T.assert_equal(initial_contents, final_contents)

    def test_persist_objects(self):
        contents_to_load = {
            'api1': {
                'key1': 'value1',
                'key2': 11,
                        'key3': {'some': 'dict'},
                        'key4': ['a', 'list']
            },
            'api2': {
                'key1': 'value42',
                'key4': 'lavash bread'
            }
        }

        # Open an empty cache
        api_cache = self._open_cache()

        # Load the cache
        for api_name in contents_to_load.keys():
            for key in contents_to_load[api_name]:
                api_cache.cache_value(api_name, key, contents_to_load[api_name][key])

        # Verify the cache
        for api_name in contents_to_load.keys():
            for key in contents_to_load[api_name]:
                expected_val = contents_to_load[api_name][key]
                actual_val = api_cache.lookup_value(api_name, key)
                T.assert_equal(expected_val, actual_val)

        # Close the cache
        final_contents = self._close_cache(api_cache)
        T.assert_equal(contents_to_load, final_contents)

    def test_do_not_update_cache(self):
        initial_contents = {
            'api1': {
                'bingo': 'woohoo'
            },
            'api2': {
                'bongo': 'boo'
            }
        }
        api_cache = self._open_cache(initial_contents, False)
        final_contents = self._close_cache(api_cache, cache_written=False)
        T.assert_equal(None, final_contents)