import numpy as np
import pytest

from evalml.tuners import NoParamsException, RandomSearchTuner
from evalml.tuners.tuner import Tuner

random_seed = 0


def test_random_search_tuner_inheritance():
    assert issubclass(RandomSearchTuner, Tuner)


def test_random_search_tuner_unique_values(dummy_pipeline_hyperparameters):
    tuner = RandomSearchTuner(dummy_pipeline_hyperparameters, random_seed=random_seed)
    generated_parameters = []
    for i in range(3):
        params = tuner.propose()
        generated_parameters.append(params)
    assert len(generated_parameters) == 3
    for i in range(3):
        assert generated_parameters[i].keys() == dummy_pipeline_hyperparameters.keys()
        assert (
            generated_parameters[i]["Mock Classifier"].keys()
            == dummy_pipeline_hyperparameters["Mock Classifier"].keys()
        )


def test_random_search_tuner_no_params(dummy_pipeline_hyperparameters_small):
    tuner = RandomSearchTuner(
        dummy_pipeline_hyperparameters_small,
        random_seed=random_seed,
        with_replacement=False,
    )
    error_text = "Cannot create a unique set of unexplored parameters. Try expanding the search space."
    with pytest.raises(NoParamsException, match=error_text):
        for i in range(10):
            tuner.propose()


def test_random_search_tuner_with_replacement(dummy_pipeline_hyperparameters):
    tuner = RandomSearchTuner(
        dummy_pipeline_hyperparameters,
        random_seed=random_seed,
        with_replacement=True,
    )
    for i in range(10):
        proposal = tuner.propose()
        assert isinstance(proposal, dict)
        assert proposal.keys() == dummy_pipeline_hyperparameters.keys()
        assert (
            proposal["Mock Classifier"].keys()
            == dummy_pipeline_hyperparameters["Mock Classifier"].keys()
        )


def test_random_search_tuner_basic(
    dummy_pipeline_hyperparameters,
    dummy_pipeline_hyperparameters_unicode,
):
    tuner = RandomSearchTuner(dummy_pipeline_hyperparameters, random_seed=random_seed)
    proposed_params = tuner.propose()
    assert proposed_params == {
        "Mock Classifier": {
            "param a": 5,
            "param b": 8.442657485810175,
            "param c": "option c",
            "param d": np.inf,
        },
    }
    tuner.add(proposed_params, 0.5)

    tuner = RandomSearchTuner(
        dummy_pipeline_hyperparameters_unicode,
        random_seed=random_seed,
    )
    proposed_params = tuner.propose()
    assert proposed_params == {
        "Mock Classifier": {
            "param a": 5,
            "param b": 8.442657485810175,
            "param c": "option c 💩",
            "param d": np.inf,
        },
    }
    tuner.add(proposed_params, 0.5)


def test_random_search_tuner_space_types():
    tuner = RandomSearchTuner(
        {"Mock Classifier": {"param a": (0, 10)}},
        random_seed=random_seed,
    )
    proposed_params = tuner.propose()
    assert proposed_params == {"Mock Classifier": {"param a": 5}}

    tuner = RandomSearchTuner(
        {"Mock Classifier": {"param a": (0, 10.0)}},
        random_seed=random_seed,
    )
    proposed_params = tuner.propose()
    assert proposed_params == {"Mock Classifier": {"param a": 5.488135039273248}}

    tuner = RandomSearchTuner(
        {"Mock Classifier": {"param a": 10.0}},
        random_seed=random_seed,
    )
    proposed_params = tuner.propose()
    assert proposed_params == {"Mock Classifier": {}}


def test_random_search_tuner_invalid_space():
    bound_error_text = "has to be less than the upper bound"
    with pytest.raises(ValueError, match=bound_error_text):
        RandomSearchTuner(
            {"Mock Classifier": {"param a": (1, 0)}},
            random_seed=random_seed,
        )
    with pytest.raises(ValueError, match=bound_error_text):
        RandomSearchTuner(
            {"Mock Classifier": {"param a": (0, 0)}},
            random_seed=random_seed,
        )
