"""
module to test telemetry main class for pipeline telemetry module

"""
from datetime import datetime
import pytest

from telemetry.main import Telemetry, FAIL_COUNT
from telemetry.storage import \
    TelemetryInMemoryStorage, AbstractTelemetryStorage
from telemetry.settings import exceptions, settings

from tests.data.test_data import DEFAULT_TELEMETRY_PARAMS

#pylint: disable=protected-access

def test_telemetry_exists():
    """ check that Telemetry class exists """
    assert Telemetry


def test_telemetry_instance_creation():
    """ check that Telemetry instance can be created using default_params """
    assert Telemetry(**DEFAULT_TELEMETRY_PARAMS).process_name == \
        'load_weather_data'


def test_telemetry_instance_has_telemetry_property():
    """ check that Telemetry instance has a telemetry property """
    assert Telemetry(**DEFAULT_TELEMETRY_PARAMS).telemetry


def test_telemetry_instance_has_process_types_property():
    """
    check that Telemetry instance has a process_types property that return a list with process_types
    """
    assert isinstance(
        Telemetry(**DEFAULT_TELEMETRY_PARAMS).process_types, list)
    assert Telemetry(**DEFAULT_TELEMETRY_PARAMS).process_types == \
        list(settings.BASE_SUB_PROCESS_TYPES.keys())


def test_telemetry_instance_has_sub_process_types_property():
    """
    check that Telemetry instance has a sub_process_types property that return
    a dict with all sub_process_types
    """
    assert isinstance(
        Telemetry(**DEFAULT_TELEMETRY_PARAMS).sub_process_types, dict)
    assert Telemetry(**DEFAULT_TELEMETRY_PARAMS).sub_process_types == \
        settings.BASE_SUB_PROCESS_TYPES


def test_telemetry_instance_raises_exception_with_invalid_process_type():
    """
    check that Telemetry instance creation raises exception when invalid
    process_type is used
    """
    telemetry_params = DEFAULT_TELEMETRY_PARAMS | {'process_type': 'invalid'}
    with pytest.raises(exceptions.InvalidProcessType):
        Telemetry(**telemetry_params)


def test_telemetry_instance_sets_start_time():
    """
    check that Telemetry instance has a start_date_time
    """
    telemetry = Telemetry(**DEFAULT_TELEMETRY_PARAMS).telemetry
    assert isinstance(telemetry.get('start_date_time'), datetime)


def test_add_sub_process_count_to_telemetry():
    """
    check that a sub process base count can be added to th eTelemetry instance
    """
    telemetry_inst = Telemetry(**DEFAULT_TELEMETRY_PARAMS)
    telemetry_inst.increase_sub_process_base_count('RETRIEVE_RAW_DATA')
    assert 'RETRIEVE_RAW_DATA' in telemetry_inst.telemetry


def test_increase_sub_process_count_not_allowed_with_closed_telemetry():
    """
    check that a sub process base count can be added to th eTelemetry instance
    """
    telemetry_inst = Telemetry(**DEFAULT_TELEMETRY_PARAMS)
    telemetry_inst.increase_sub_process_base_count('RETRIEVE_RAW_DATA')
    telemetry_inst.save_and_close()
    with pytest.raises(exceptions.TelemetryObjectAlreadyClosed):
        telemetry_inst.increase_sub_process_base_count('RETRIEVE_RAW_DATA')


def test_can_not_close_closed_telemetry_instance():
    """
    check that a closed telemetry instance can not be closed again
    """
    telemetry_inst = Telemetry(**DEFAULT_TELEMETRY_PARAMS)
    telemetry_inst.save_and_close()
    with pytest.raises(exceptions.TelemetryObjectAlreadyClosed):
        telemetry_inst.save_and_close()


def test_increase_sub_process_fail_not_allowed_with_closed_telemetry():
    """
    check that a sub process base count can be added to th eTelemetry instance
    """
    telemetry_inst = Telemetry(**DEFAULT_TELEMETRY_PARAMS)
    telemetry_inst.increase_sub_process_base_count('RETRIEVE_RAW_DATA')
    telemetry_inst.save_and_close()
    with pytest.raises(exceptions.TelemetryObjectAlreadyClosed):
        telemetry_inst.increase_sub_process_fail_count('RETRIEVE_RAW_DATA')


def test_sub_process_custom_count_not_allowed_with_closed_telemetry():
    """
    check that a sub process custom count method raises an excpetion
    when telementry object has been closed
    """
    telemetry_inst = Telemetry(**DEFAULT_TELEMETRY_PARAMS)
    telemetry_inst.increase_sub_process_base_count('RETRIEVE_RAW_DATA')
    telemetry_inst.save_and_close()
    with pytest.raises(exceptions.TelemetryObjectAlreadyClosed):
        telemetry_inst.increase_sub_process_custom_count(
            sub_process='RETRIEVE_RAW_DATA',
            custom_counter='test_counter')


def test_custom_count_not_allowed_with_closed_telemetry():
    """
    check that a custom count method raises an excpetion
    when telementry object has been closed
    """
    telemetry_inst = Telemetry(**DEFAULT_TELEMETRY_PARAMS)
    telemetry_inst.save_and_close()
    with pytest.raises(exceptions.TelemetryObjectAlreadyClosed):
        telemetry_inst.increase_custom_count(
            custom_counter='test_counter')


def test_add_sub_process_fail_count_to_telemetry():
    """
    check that a sub process fail count can be increased
    """
    telemetry_inst = Telemetry(**DEFAULT_TELEMETRY_PARAMS)
    telemetry_inst.increase_sub_process_base_count('RETRIEVE_RAW_DATA')
    telemetry_inst.increase_sub_process_fail_count('RETRIEVE_RAW_DATA')
    assert telemetry_inst.telemetry['RETRIEVE_RAW_DATA'][FAIL_COUNT] == 1


def test_add_sub_process_fail_count_raises_excpetion():
    """
    check that a sub process fail count raises an excpetion when sub_process
    has not yet been initialized by increase_sub_process_base_count(sub_process)
    """
    telemetry_inst = Telemetry(**DEFAULT_TELEMETRY_PARAMS)
    with pytest.raises(exceptions.BaseCountForSubProcessNotAdded):
        telemetry_inst.increase_sub_process_fail_count('RETRIEVE_RAW_DATA')


def test_add_custom_counter_to_telemetry():
    """
    check that a custom counter can be added to the telemetry instance
    """
    telemetry_inst = Telemetry(**DEFAULT_TELEMETRY_PARAMS)
    telemetry_inst.increase_custom_count('network_error')
    assert telemetry_inst.telemetry['network_error'] == 1


def test_increase_custom_counter_to_telemetry():
    """
    check that a custom counter once created can be increased with any value
    """
    telemetry_inst = Telemetry(**DEFAULT_TELEMETRY_PARAMS)
    telemetry_inst.increase_custom_count('network_error')
    telemetry_inst.increase_custom_count('network_error', 2)
    assert telemetry_inst.telemetry['network_error'] == 3


def test_increase_sub_process_custom_count_raises_excpetion():
    """
    check that increase_sub_process_custom_count method raises an excpetion
    when sub_process has not yet been initialized
    """
    telemetry_inst = Telemetry(**DEFAULT_TELEMETRY_PARAMS)
    with pytest.raises(exceptions.BaseCountForSubProcessNotAdded):
        telemetry_inst.increase_sub_process_custom_count(
            sub_process='RETRIEVE_RAW_DATA',
            custom_counter='test_counter')


def test_increase_sub_process_custom_count_with_two():
    """
    check that increase_sub_process_custom_count method creates counter
    with initial value 2
    """
    telemetry_inst = Telemetry(**DEFAULT_TELEMETRY_PARAMS)
    telemetry_inst.increase_sub_process_base_count('RETRIEVE_RAW_DATA')
    telemetry_inst.increase_sub_process_custom_count(
        sub_process='RETRIEVE_RAW_DATA',
        custom_counter='test_counter',
        increment=2)
    assert telemetry_inst.telemetry[
        'RETRIEVE_RAW_DATA']['test_counter'] == 2


def test_close_telemetry_instance_sets_run_time(mocker):
    """
    check that closing the telemetry sets the run_time
    """
    mocker.patch('telemetry.storage.TelemetryInMemoryStorage.store_telemetry',
                 return_value=None)
    telemetry_inst = Telemetry(**DEFAULT_TELEMETRY_PARAMS)
    telemetry_inst.increase_sub_process_base_count('RETRIEVE_RAW_DATA')
    telemetry_result = telemetry_inst.save_and_close()
    assert telemetry_result.get('run_time_in_seconds') > 0


def test_close_telemetry_instance_calls_store_telemetry(mocker):
    """
    check that closing the telemetry sets the run_time
    """
    mocker.patch('telemetry.storage.TelemetryInMemoryStorage.store_telemetry',
                 return_value=None)
    _store_telemetry_spy = mocker.spy(
        TelemetryInMemoryStorage, 'store_telemetry')
    Telemetry(**DEFAULT_TELEMETRY_PARAMS).save_and_close()
    assert _store_telemetry_spy.called


def test_get_storage_class_returns_in_memory_storage_by_default():
    """
    check that _get_storage_class method returns TelemetryInMemoryStorage
    instance if no storage class is provided
    """
    telemetry_inst = Telemetry(**DEFAULT_TELEMETRY_PARAMS)
    assert isinstance(
        telemetry_inst._get_storage_class(None), TelemetryInMemoryStorage)


def test_get_storage_class_raises_exception_if_invalid_class_is_provided():
    """
    check that _get_storage_class raises exception if storage class is provided
    that is not a subclass of AbstractTelemetryStorage
    """
    telemetry_inst = Telemetry(**DEFAULT_TELEMETRY_PARAMS)
    with pytest.raises(exceptions.StorageClassOfIncorrectType):
        telemetry_inst._get_storage_class(str)


def test_get_storage_class_returns_instance_of_storage_class():
    """
    check that _get_storage_class method returns an instance of
    Storage class provided
    """
    #pylint: disable=too-few-public-methods
    class TestStorage(AbstractTelemetryStorage):
        """ test class """
        def store_telemetry(self, telemetry):
            """ test method """
            return None

    telemetry_inst = Telemetry(**DEFAULT_TELEMETRY_PARAMS)
    assert isinstance(
        telemetry_inst._get_storage_class(TestStorage), TestStorage)


def test_storage_class_close_method_closes_db():
    """
    check that _get_storage_class method returns an instance of
    Storage class provided
    """
    telemetry_inst = Telemetry(**DEFAULT_TELEMETRY_PARAMS)
    storage_instance = telemetry_inst._get_storage_class(None)
    storage_instance.close_db()
    assert storage_instance.db_in_memory is None
    assert storage_instance.db_cursor is None
