import time
import json
import pytest
import responses
from UnleashClient import UnleashClient
from UnleashClient.strategies import Strategy
from tests.utilities.testing_constants import URL, ENVIRONMENT, APP_NAME, INSTANCE_ID, REFRESH_INTERVAL, \
    METRICS_INTERVAL, DISABLE_METRICS, DISABLE_REGISTRATION, CUSTOM_HEADERS, CUSTOM_OPTIONS
from tests.utilities.mocks.mock_features import MOCK_FEATURE_RESPONSE
from tests.utilities.mocks.mock_all_features import MOCK_ALL_FEATURES
from UnleashClient.constants import REGISTER_URL, FEATURES_URL, METRICS_URL


class EnvironmentStrategy(Strategy):
    def load_provisioning(self) -> list:
        return [x.strip() for x in self.parameters["environments"].split(',')]

    def apply(self, context: dict = None) -> bool:
        """
        Turn on if environemnt is a match.

        :return:
        """
        default_value = False

        if "environment" in context.keys():
            default_value = context["environment"] in self.parsed_provisioning

        return default_value


@pytest.fixture()
def unleash_client(tmpdir):
    unleash_client = UnleashClient(
        URL,
        APP_NAME,
        refresh_interval=REFRESH_INTERVAL,
        metrics_interval=METRICS_INTERVAL,
        cache_directory=tmpdir.dirname
    )
    yield unleash_client
    unleash_client.destroy()


@pytest.fixture()
def unleash_client_nodestroy(tmpdir):
    unleash_client = UnleashClient(
        URL,
        APP_NAME,
        refresh_interval=REFRESH_INTERVAL,
        metrics_interval=METRICS_INTERVAL,
        cache_directory=tmpdir.dirname
    )
    yield unleash_client


@pytest.fixture()
def unleash_client_toggle_only(tmpdir):
    unleash_client = UnleashClient(
        URL,
        APP_NAME,
        refresh_interval=REFRESH_INTERVAL,
        metrics_interval=METRICS_INTERVAL,
        disable_registration=True,
        disable_metrics=True,
        cache_directory=str(tmpdir)
    )
    yield unleash_client
    unleash_client.destroy()


def test_UC_initialize_default():
    client = UnleashClient(URL, APP_NAME)
    assert client.unleash_url == URL
    assert client.unleash_app_name == APP_NAME
    assert client.unleash_metrics_interval == 60


def test_UC_initialize_full():
    client = UnleashClient(URL,
                           APP_NAME,
                           ENVIRONMENT,
                           INSTANCE_ID,
                           REFRESH_INTERVAL,
                           METRICS_INTERVAL,
                           DISABLE_METRICS,
                           DISABLE_REGISTRATION,
                           CUSTOM_HEADERS,
                           CUSTOM_OPTIONS)
    assert client.unleash_instance_id == INSTANCE_ID
    assert client.unleash_refresh_interval == REFRESH_INTERVAL
    assert client.unleash_metrics_interval == METRICS_INTERVAL
    assert client.unleash_disable_metrics == DISABLE_METRICS
    assert client.unleash_disable_registration == DISABLE_REGISTRATION
    assert client.unleash_custom_headers == CUSTOM_HEADERS
    assert client.unleash_custom_options == CUSTOM_OPTIONS


def test_UC_type_violation():
    client = UnleashClient(URL, APP_NAME, refresh_interval="60")
    assert client.unleash_url == URL
    assert client.unleash_app_name == APP_NAME
    assert client.unleash_refresh_interval == "60"


@responses.activate
def test_uc_lifecycle(unleash_client):
    # Set up API
    responses.add(responses.POST, URL + REGISTER_URL, json={}, status=202)
    responses.add(responses.GET, URL + FEATURES_URL, json=MOCK_FEATURE_RESPONSE, status=200)
    responses.add(responses.POST, URL + METRICS_URL, json={}, status=202)

    # Create Unleash client and check initial load
    unleash_client.initialize_client()
    time.sleep(1)
    assert unleash_client.is_initialized
    assert len(unleash_client.features) >= 4

    # Simulate server provisioning change
    responses.add(responses.GET, URL + FEATURES_URL, json=MOCK_ALL_FEATURES, status=200)
    time.sleep(30)
    assert len(unleash_client.features) >= 9


@responses.activate
def test_uc_is_enabled(unleash_client):
    # Set up API
    responses.add(responses.POST, URL + REGISTER_URL, json={}, status=202)
    responses.add(responses.GET, URL + FEATURES_URL, json=MOCK_FEATURE_RESPONSE, status=200)
    responses.add(responses.POST, URL + METRICS_URL, json={}, status=202)

    # Create Unleash client and check initial load
    unleash_client.initialize_client()
    time.sleep(1)
    assert unleash_client.is_enabled("testFlag")


@responses.activate
def test_uc_fallbackfunction(unleash_client, mocker):
    def good_fallback(feature_name: str, context: dict) -> bool:
        return True

    def bad_fallback(feature_name: str, context: dict) -> bool:
        return False

    def context_fallback(feature_name: str, context: dict) -> bool:
        return context['wat']

    # Set up API
    responses.add(responses.POST, URL + REGISTER_URL, json={}, status=202)
    responses.add(responses.GET, URL + FEATURES_URL, json=MOCK_FEATURE_RESPONSE, status=200)
    responses.add(responses.POST, URL + METRICS_URL, json={}, status=202)
    fallback_spy = mocker.Mock(wraps=good_fallback)

    # Create Unleash client and check initial load
    unleash_client.initialize_client()
    time.sleep(1)
    # Non-existent feature flag, fallback_function
    assert unleash_client.is_enabled("notFoundTestFlag", fallback_function=fallback_spy)
    assert fallback_spy.call_count == 1
    fallback_spy.reset_mock()

    # Non-existent feature flag, default value, fallback_function
    assert unleash_client.is_enabled("notFoundTestFlag", default_value=True, fallback_function=bad_fallback)
    assert fallback_spy.call_count == 0

    # Existent feature flag, fallback_function
    assert unleash_client.is_enabled("testFlag", fallback_function=good_fallback)
    assert fallback_spy.call_count == 0


@responses.activate
def test_uc_dirty_cache(unleash_client_nodestroy):
    unleash_client = unleash_client_nodestroy
    # Set up API
    responses.add(responses.POST, URL + REGISTER_URL, json={}, status=202)
    responses.add(responses.GET, URL + FEATURES_URL, json=MOCK_FEATURE_RESPONSE, status=200)
    responses.add(responses.POST, URL + METRICS_URL, json={}, status=202)

    # Create Unleash client and check initial load
    unleash_client.initialize_client()
    time.sleep(5)
    assert unleash_client.is_enabled("testFlag")
    unleash_client.scheduler.shutdown()

    # Check that everything works if previous cache exists.
    unleash_client.initialize_client()
    time.sleep(5)
    assert unleash_client.is_enabled("testFlag")


@responses.activate
def test_uc_is_enabled_with_context():
    # Set up API
    responses.add(responses.POST, URL + REGISTER_URL, json={}, status=202)
    responses.add(responses.GET, URL + FEATURES_URL, json=MOCK_FEATURE_RESPONSE, status=200)
    responses.add(responses.POST, URL + METRICS_URL, json={}, status=202)

    custom_strategies_dict = {
        "custom-context": EnvironmentStrategy
    }

    unleash_client = UnleashClient(URL, APP_NAME, environment='prod', custom_strategies=custom_strategies_dict)
    # Create Unleash client and check initial load
    unleash_client.initialize_client()

    time.sleep(1)
    assert unleash_client.is_enabled("testContextFlag")
    unleash_client.destroy()


@responses.activate
def test_uc_is_enabled_error_states(unleash_client):
    # Set up API
    responses.add(responses.POST, URL + REGISTER_URL, json={}, status=202)
    responses.add(responses.GET, URL + FEATURES_URL, json=MOCK_FEATURE_RESPONSE, status=200)
    responses.add(responses.POST, URL + METRICS_URL, json={}, status=202)

    # Create Unleash client and check initial load
    unleash_client.initialize_client()
    time.sleep(1)
    assert not unleash_client.is_enabled("ThisFlagDoesn'tExist")
    assert unleash_client.is_enabled("ThisFlagDoesn'tExist", default_value=True)


@responses.activate
def test_uc_not_initialized_isenabled():
    unleash_client = UnleashClient(URL, APP_NAME)
    assert not unleash_client.is_enabled("ThisFlagDoesn'tExist")
    assert unleash_client.is_enabled("ThisFlagDoesn'tExist", default_value=True)


@responses.activate
def test_uc_get_variant():
    # Set up API
    responses.add(responses.POST, URL + REGISTER_URL, json={}, status=202)
    responses.add(responses.GET, URL + FEATURES_URL, json=MOCK_FEATURE_RESPONSE, status=200)
    responses.add(responses.POST, URL + METRICS_URL, json={}, status=202)

    unleash_client = UnleashClient(URL, APP_NAME)
    # Create Unleash client and check initial load
    unleash_client.initialize_client()

    time.sleep(1)
    # If feature flag is on.
    variant = unleash_client.get_variant("testVariations", context={'userId': '2'})
    assert variant['name'] == 'VarA'
    assert variant['enabled']

    # If feature flag is not.
    variant = unleash_client.get_variant("testVariations", context={'userId': '3'})
    assert variant['name'] == 'disabled'
    assert not variant['enabled']

    unleash_client.destroy()


@responses.activate
def test_uc_not_initialized_getvariant():
    unleash_client = UnleashClient(URL, APP_NAME)
    variant = unleash_client.get_variant("ThisFlagDoesn'tExist")
    assert not variant['enabled']
    assert variant['name'] == 'disabled'


@responses.activate
def test_uc_metrics(unleash_client):
    # Set up API
    responses.add(responses.POST, URL + REGISTER_URL, json={}, status=202)
    responses.add(responses.GET, URL + FEATURES_URL, json=MOCK_FEATURE_RESPONSE, status=200)
    responses.add(responses.POST, URL + METRICS_URL, json={}, status=202)

    # Create Unleash client and check initial load
    unleash_client.initialize_client()
    time.sleep(1)
    assert unleash_client.is_enabled("testFlag")

    time.sleep(12)
    request = json.loads(responses.calls[-1].request.body)
    assert request['bucket']["toggles"]["testFlag"]["yes"] == 1


@responses.activate
def test_uc_disabled_registration(unleash_client_toggle_only):
    unleash_client = unleash_client_toggle_only
    # Set up APIs
    responses.add(responses.POST, URL + REGISTER_URL, json={}, status=401)
    responses.add(responses.GET, URL + FEATURES_URL, json=MOCK_FEATURE_RESPONSE, status=200)
    responses.add(responses.POST, URL + METRICS_URL, json={}, status=401)

    unleash_client.initialize_client()
    unleash_client.is_enabled("testFlag")
    time.sleep(20)
    assert unleash_client.is_enabled("testFlag")

    for api_call in responses.calls:
        assert '/api/client/features' in api_call.request.url


@responses.activate
def test_uc_server_error(unleash_client):
    # Verify that Unleash Client will still fall back gracefully if SERVER ANGRY RAWR, and then recover gracefully.

    unleash_client = unleash_client
    # Set up APIs
    responses.add(responses.POST, URL + REGISTER_URL, json={}, status=401)
    responses.add(responses.GET, URL + FEATURES_URL, status=500)
    responses.add(responses.POST, URL + METRICS_URL, json={}, status=401)

    unleash_client.initialize_client()
    assert not unleash_client.is_enabled("testFlag")

    responses.remove(responses.GET, URL + FEATURES_URL)
    responses.add(responses.GET, URL + FEATURES_URL, json=MOCK_FEATURE_RESPONSE, status=200)
    time.sleep(20)
    assert unleash_client.is_enabled("testFlag")


@responses.activate
def test_uc_server_error_recovery(unleash_client):
    # Verify that Unleash Client will still fall back gracefully if SERVER ANGRY RAWR, and then recover gracefully.

    unleash_client = unleash_client
    # Set up APIs
    responses.add(responses.POST, URL + REGISTER_URL, json={}, status=401)
    responses.add(responses.GET, URL + FEATURES_URL, status=500)
    responses.add(responses.POST, URL + METRICS_URL, json={}, status=401)

    unleash_client.initialize_client()
    assert not unleash_client.is_enabled("testFlag")

    responses.remove(responses.GET, URL + FEATURES_URL)
    responses.add(responses.GET, URL + FEATURES_URL, json=MOCK_FEATURE_RESPONSE, status=200)
    time.sleep(20)
    assert unleash_client.is_enabled("testFlag")
