# 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 re
from typing import NamedTuple

from hypothesistooling.__main__ import PYTHONS as pythons_map

PYTHON_VERSIONS = [v for v in pythons_map if re.fullmatch(r"3\.\d\d?", v)]

try:
    from numpy import __version__ as np_version
except ImportError:
    NP1 = False
else:
    NP1 = np_version.startswith("1.")

ASSUME_REVEALED_TYPES = [
    ("assume(False)", "Never"),
    ("assume(None)", "Never"),
    ("assume(True)", "Literal[True]"),
    ("assume(1)", "Literal[True]"),
]

REVEALED_TYPES = [
    ("integers()", "int"),
    ("text()", "str"),
    ("integers().map(str)", "str"),
    ("booleans().filter(bool)", "bool"),
    ("tuples()", "tuple[()]"),
    ("tuples(integers())", "tuple[int]"),
    ("tuples(integers(), text())", "tuple[int, str]"),
    (
        "tuples(integers(), text(), integers(), text(), integers())",
        "tuple[int, str, int, str, int]",
    ),
    (
        "tuples(text(), text(), text(), text(), text(), text())",
        "tuple[Any, ...]",
    ),
    ("lists(none())", "list[None]"),
    ("integers().filter(lambda x: x > 0)", "int"),
    ("booleans().filter(lambda x: x)", "bool"),
    ("integers().map(bool).filter(lambda x: x)", "bool"),
    ("integers().flatmap(lambda x: lists(floats()))", "list[float]"),
    ("one_of([integers(), integers()])", "int"),
    ("one_of([integers(), floats()])", "float"),
    ("one_of([integers(), none()])", "int | None"),
    ("one_of(integers(), none())", "int | None"),
    ("one_of(integers(), text())", "int | str"),
    ("recursive(integers(), lists)", "list[Any] | int"),
    # We have overloads for up to five types, then fall back to Any.
    # (why five?  JSON atoms are None|bool|int|float|str and we do that a lot)
    (
        "one_of(integers(), text(), none(), binary(), builds(list), builds(dict))",
        "Any",
    ),
    ("from_regex(r'.', alphabet=None)", "str"),
    ("fixed_dictionaries({'a': booleans()})", "dict[str, bool]"),
    ("fixed_dictionaries({'a': booleans(), 'b': integers()})", "dict[str, int]"),
    ("fixed_dictionaries({}, optional={'a': booleans()})", "dict[str, bool]"),
    (
        "fixed_dictionaries({'a': booleans()}, optional={1: booleans()})",
        "dict[str | int, bool]",
    ),
    (
        "fixed_dictionaries({'a': booleans()}, optional={1: integers()})",
        "dict[str | int, bool | int]",
    ),
]


FILTER_TYPEGUARD_PREAMBLE = """\
from typing import TypeGuard
from hypothesis.strategies import *
def is_str(x: object) -> TypeGuard[str]:
    return isinstance(x, str)
"""

FILTER_TYPEGUARD_REVEALED_TYPES = [
    ("from_type(object).filter(is_str)", "str"),
    ("one_of(integers(), text()).filter(is_str)", "str"),
]


class DifferingRevealedTypes(NamedTuple):
    value: str
    mypy: str
    pyright: str


DIFF_REVEALED_TYPES = [
    DifferingRevealedTypes("none() | integers()", "None | int", "int | None"),
    DifferingRevealedTypes(
        "data()", "hypothesis.strategies._internal.core.DataObject", "DataObject"
    ),
    # We have overloads for up to five types, then fall back to Any.
    # (why five?  JSON atoms are None|bool|int|float|str and we do that a lot)
    DifferingRevealedTypes(
        "one_of(integers(), text(), none(), binary(), builds(list))",
        "int | str | None | bytes | list[Never]",
        "int | str | bytes | list[Unknown] | None",
    ),
    DifferingRevealedTypes(
        "dictionaries(integers(), datetimes())",
        "dict[int, datetime.datetime]",
        "dict[int, datetime]",
    ),
    # mypy joins heterogeneous values to their common base, pyright keeps the union
    DifferingRevealedTypes(
        "fixed_dictionaries({'a': text(), 'b': integers()})",
        "dict[str, object]",
        "dict[str, int | str]",
    ),
]


NUMPY_REVEALED_TYPES = [
    (
        'arrays(dtype=np.dtype("int32"), shape=1)',
        "ndarray[tuple[Any, ...], dtype[signedinteger[_32Bit]]]",
    ),
    # (
    #     "arrays(dtype=np.dtype(int), shape=1)",
    #     "ndarray[tuple[int, ...], dtype[Union[signedinteger[Union[_32Bit, _64Bit]], bool[bool]]]]",
    #     # FIXME: `dtype[signedinteger[_32Bit | _64Bit] | bool[bool]]]]` on mypy now
    # ),
    (
        "boolean_dtypes()",
        "dtype[bool[bool]]",  # np.bool[builtins.bool]
    ),
    (
        "unsigned_integer_dtypes(sizes=8)",
        "dtype[unsignedinteger[_8Bit]]",
    ),
    (
        "unsigned_integer_dtypes(sizes=16)",
        "dtype[unsignedinteger[_16Bit]]",
    ),
    (
        "unsigned_integer_dtypes(sizes=32)",
        "dtype[unsignedinteger[_32Bit]]",
    ),
    (
        "unsigned_integer_dtypes(sizes=64)",
        "dtype[unsignedinteger[_64Bit]]",
    ),
    (
        "unsigned_integer_dtypes()",
        "dtype[unsignedinteger[Any]]",
    ),
    (
        "unsigned_integer_dtypes(sizes=(8, 16))",
        "dtype[unsignedinteger[Any]]",
    ),
    (
        "integer_dtypes(sizes=8)",
        "dtype[signedinteger[_8Bit]]",
    ),
    (
        "integer_dtypes(sizes=16)",
        "dtype[signedinteger[_16Bit]]",
    ),
    (
        "integer_dtypes(sizes=32)",
        "dtype[signedinteger[_32Bit]]",
    ),
    (
        "integer_dtypes(sizes=64)",
        "dtype[signedinteger[_64Bit]]",
    ),
    (
        "integer_dtypes()",
        "dtype[signedinteger[Any]]",
    ),
    (
        "integer_dtypes(sizes=(8, 16))",
        "dtype[signedinteger[Any]]",
    ),
    (
        "floating_dtypes(sizes=16)",
        "dtype[floating[_16Bit]]",
    ),
    (
        "floating_dtypes(sizes=32)",
        "dtype[floating[_32Bit]]",
    ),
    (
        "floating_dtypes(sizes=64)",
        "dtype[float64]",
    ),
    (
        "floating_dtypes(sizes=128)",
        "dtype[floating[_128Bit]]",
    ),
    (
        "floating_dtypes()",
        "dtype[floating[Any]]",
    ),
    (
        "floating_dtypes(sizes=(16, 32))",
        "dtype[floating[Any]]",
    ),
    (
        "complex_number_dtypes(sizes=64)",
        "dtype[complexfloating[_32Bit, _32Bit]]",
    ),
    (
        "complex_number_dtypes(sizes=128)",
        "dtype[complex128]",
    ),
    (
        "complex_number_dtypes(sizes=256)",
        "dtype[complexfloating[_128Bit, _128Bit]]",
    ),
    (
        "complex_number_dtypes()",
        "dtype[complexfloating[Any, Any]]",
    ),
    (
        "complex_number_dtypes(sizes=(64, 128))",
        "dtype[complexfloating[Any, Any]]",
    ),
    (
        "integer_array_indices(shape=(2, 3))",
        "tuple[ndarray[tuple[Any, ...], dtype[signedinteger[Any]]], ...]",
    ),
    (
        'integer_array_indices(shape=(2, 3), dtype=np.dtype("int32"))',
        "tuple[ndarray[tuple[Any, ...], dtype[signedinteger[_32Bit]]], ...]",
    ),
    (
        'integer_array_indices(shape=(2, 3), dtype=np.dtype("uint8"))',
        "tuple[ndarray[tuple[Any, ...], dtype[unsignedinteger[_8Bit]]], ...]",
    ),
    # basic_indices with allow_ellipsis=False (no EllipsisType differences)
    (
        "basic_indices((3, 4), allow_ellipsis=False)",
        "int | slice[Any, Any, Any] | tuple[int | slice[Any, Any, Any], ...]",
    ),
]

# basic_indices tests where mypy/pyright differ in EllipsisType representation
NUMPY_DIFF_REVEALED_TYPES = [
    # mypy uses types.EllipsisType, pyright uses EllipsisType
    DifferingRevealedTypes(
        "basic_indices((3, 4))",
        "int | slice[Any, Any, Any] | types.EllipsisType | tuple[int | slice[Any, Any, Any] | types.EllipsisType, ...]",
        "int | slice[Any, Any, Any] | EllipsisType | tuple[int | slice[Any, Any, Any] | EllipsisType, ...]",
    ),
    # pyright also reorders None to the end
    DifferingRevealedTypes(
        "basic_indices((3, 4), allow_newaxis=True, allow_ellipsis=False)",
        "int | slice[Any, Any, Any] | None | tuple[int | slice[Any, Any, Any] | None, ...]",
        "int | slice[Any, Any, Any] | tuple[int | slice[Any, Any, Any] | None, ...] | None",
    ),
    DifferingRevealedTypes(
        "basic_indices((3, 4), allow_newaxis=True)",
        "int | slice[Any, Any, Any] | None | types.EllipsisType | tuple[int | slice[Any, Any, Any] | None | types.EllipsisType, ...]",
        "int | slice[Any, Any, Any] | EllipsisType | tuple[int | slice[Any, Any, Any] | EllipsisType | None, ...] | None",
    ),
]
