# This file is part of Hypothesis, which may be found at
# https://github.com/HypothesisWorks/hypothesis/
#
# Copyright the Hypothesis Authors.
# Individual contributors are listed in AUTHORS.rst and the git log.
#
# This Source Code Form is subject to the terms of the Mozilla Public License,
# v. 2.0. If a copy of the MPL was not distributed with this file, You can
# obtain one at https://mozilla.org/MPL/2.0/.

import math
import sys
from types import SimpleNamespace

import pytest

from hypothesis import given, settings, strategies as st
from hypothesis.configuration import StorageDirectory
from hypothesis.internal.conjecture import providers
from hypothesis.internal.conjecture.choice import choice_equal
from hypothesis.internal.conjecture.providers import CONSTANTS_CACHE
from hypothesis.internal.constants_ast import Constants

from tests.common.debug import find_any
from tests.common.utils import Why, xfail_on_crosshair


# I tried using @given(st.integers()) here, but I think there is a bad interaction
# with CONSTANTS_CACHE when testing it inside of a hypothesis test.
@pytest.mark.parametrize("value", [2**20 - 50, 2**10 - 10, 129387123, -19827321, 0])
def test_can_draw_local_constants_integers(monkeypatch, value):
    # _get_local_constants normally invalidates this cache for us, but we're
    # monkeypatching it.
    CONSTANTS_CACHE.cache.clear()
    monkeypatch.setattr(
        providers, "_get_local_constants", lambda: Constants(integers={value})
    )
    find_any(st.integers(), lambda v: choice_equal(v, value))


@xfail_on_crosshair(Why.undiscovered)  # I think float_to_int is difficult for crosshair
@pytest.mark.parametrize("value", [1.2938, -1823.0239, 1e999, math.nan])
def test_can_draw_local_constants_floats(monkeypatch, value):
    CONSTANTS_CACHE.cache.clear()
    monkeypatch.setattr(
        providers, "_get_local_constants", lambda: Constants(floats={value})
    )
    find_any(st.floats(), lambda v: choice_equal(v, value))


@pytest.mark.parametrize("value", [b"abdefgh", b"a" * 50])
def test_can_draw_local_constants_bytes(monkeypatch, value):
    CONSTANTS_CACHE.cache.clear()
    monkeypatch.setattr(
        providers, "_get_local_constants", lambda: Constants(bytes={value})
    )
    find_any(st.binary(), lambda v: choice_equal(v, value))


@pytest.mark.parametrize("value", ["abdefgh", "a" * 50])
def test_can_draw_local_constants_string(monkeypatch, value):
    CONSTANTS_CACHE.cache.clear()
    monkeypatch.setattr(
        providers, "_get_local_constants", lambda: Constants(strings={value})
    )
    # we have a bunch of strings in GLOBAL_CONSTANTS, so it might take a while
    # to generate our local constant.
    find_any(
        st.text(),
        lambda v: choice_equal(v, value),
        settings=settings(max_examples=5_000),
    )


def test_actual_collection(monkeypatch, tmp_path):
    # covering test for doing some real work collecting constants. We'll fake
    # hypothesis as being the "local" module, just to get some real constant
    # collection going.

    # reset cache checks
    monkeypatch.setattr(providers, "_sys_modules_len", None)
    monkeypatch.setattr(providers, "_seen_modules", set())
    monkeypatch.setattr(providers, "is_local_module_file", lambda f: "hypothesis" in f)
    # Without monkeypatching this, this test only covers all the right lines the
    # first time it's executed, since it falls back to the cache the second time.
    # Reset to a guaranteed-empty storage directory to ensure consistent coverage.
    monkeypatch.setattr(
        "hypothesis.internal.constants_ast.storage_directory",
        lambda *names, **kwargs: StorageDirectory(
            tmp_path.joinpath(*names), home_directory=tmp_path
        ),
    )

    @given(st.integers())
    @settings(max_examples=100)
    def f(n):
        pass

    f()


def test_unhashable_sys_modules_entry(monkeypatch):
    # Regression test for https://github.com/HypothesisWorks/hypothesis/issues/4660
    # Some packages (e.g. cog) place unhashable objects like SimpleNamespace
    # in sys.modules. This should not crash _get_local_constants.
    monkeypatch.setattr(providers, "_sys_modules_len", None)
    monkeypatch.setattr(providers, "_seen_modules", set())
    monkeypatch.setitem(sys.modules, "_unhashable_test_mod", SimpleNamespace())

    providers._get_local_constants()
