from typing import Any, Optional, Union, TypeVar, Dict, List, Tuple, Callable, Type, IO, overload, Text as typing_Text
from typing_extensions import Protocol
from .type_api import TypeEngine as TypeEngine, TypeDecorator as TypeDecorator, Emulated, NativeForEmulated as NativeForEmulated
from .base import SchemaEventTarget as SchemaEventTarget
from .schema import MetaData
from datetime import datetime, date, time, timedelta
from ..engine import Dialect, Engine, Connection, Connectable
from .. import util
import decimal
import sys

_T = TypeVar('_T')

class _LookupExpressionAdapter(object):
    comparator_factory: Type[Any] = ...

class Concatenable(object):
    comparator_factory: Type[Any] = ...

class Indexable(object):
    comparator_factory: Type[Any] = ...

# Docs say that String is unicode when DBAPI supports it
# but it should be all major DBAPIs now.
class String(Concatenable, TypeEngine[typing_Text]):
    __visit_name__: str = ...
    length: Optional[int] = ...
    collation: Optional[str] = ...
    convert_unicode: Union[bool, str] = ...
    unicode_error: Optional[str] = ...
    @overload
    def __init__(self, length: Optional[int] = ..., collation: Optional[str] = ...,
                 convert_unicode: bool = ..., _warn_on_bytestring: bool = ...) -> None: ...
    @overload
    def __init__(self, length: Optional[int] = ..., collation: Optional[str] = ...,
                 convert_unicode: str = ..., unicode_error: Optional[str] = ...,
                 _warn_on_bytestring: bool = ...) -> None: ...
    def literal_processor(self, dialect: Dialect) -> Callable[[str], str]: ...
    def bind_processor(self, dialect: Dialect) -> Optional[Callable[[str], str]]: ...
    def result_processor(self, dialect: Dialect, coltype: Any) -> Optional[Callable[[Optional[Any]], Optional[str]]]: ...
    @property
    def python_type(self) -> Type[typing_Text]: ...
    def get_dbapi_type(self, dbapi: Any) -> Any: ...

class Text(String):
    __visit_name__: str = ...

class Unicode(String):
    __visit_name__: str = ...
    @overload
    def __init__(self, length: Optional[int] = ..., collation: Optional[str] = ...,
                 convert_unicode: bool = ..., _warn_on_bytestring: bool = ...) -> None: ...
    @overload
    def __init__(self, length: Optional[int] = ..., collation: Optional[str] = ...,
                 convert_unicode: str = ..., unicode_error: Optional[str] = ...,
                 _warn_on_bytestring: bool = ...) -> None: ...

class UnicodeText(Text):
    __visit_name__: str = ...
    @overload
    def __init__(self, length: Optional[int] = ..., collation: Optional[str] = ...,
                 convert_unicode: bool = ..., _warn_on_bytestring: bool = ...) -> None: ...
    @overload
    def __init__(self, length: Optional[int] = ..., collation: Optional[str] = ...,
                 convert_unicode: str = ..., unicode_error: Optional[str] = ...,
                 _warn_on_bytestring: bool = ...) -> None: ...

class Integer(_LookupExpressionAdapter, TypeEngine[int]):
    __visit_name__: str = ...
    def get_dbapi_type(self, dbapi): ...
    @property
    def python_type(self) -> Type[int]: ...
    def literal_processor(self, dialect: Dialect) -> Callable[[int], str]: ...

class SmallInteger(Integer):
    __visit_name__: str = ...

class BigInteger(Integer):
    __visit_name__: str = ...

# TODO: This can also be float (however, decimal is the default)
class Numeric(_LookupExpressionAdapter, TypeEngine[decimal.Decimal]):
    __visit_name__: str = ...
    precision: Optional[int] = ...
    scale: Optional[int] = ...
    decimal_return_scale: Optional[int] = ...
    asdecimal: bool = ...
    def __init__(self, precision: Optional[int] = ..., scale: Optional[int] = ...,
                 decimal_return_scale: Optional[int] = ..., asdecimal: bool = ...) -> None: ...
    def get_dbapi_type(self, dbapi: Any) -> Any: ...
    def literal_processor(self, dialect: Dialect) -> Callable[[Union[float, decimal.Decimal]], str]: ...
    @property
    def python_type(self) -> Union[Type[float], Type[decimal.Decimal]]: ...  # type: ignore  # return type incompatible
    def bind_processor(self, dialect: Dialect) -> Optional[Callable[[Optional[str]], decimal.Decimal]]: ...
    def result_processor(self, dialect: Dialect, coltype: Any) -> Optional[Callable[[Optional[Any]],
                                                                                    Optional[Union[float, decimal.Decimal]]]]: ...

class Float(Numeric):
    __visit_name__: str = ...
    scale: Optional[int] = ...
    precision: Optional[int] = ...
    asdecimal: bool = ...
    decimal_return_scale: Optional[int] = ...
    def __init__(self, precision: Optional[int] = ..., asdecimal: bool = ...,
                 decimal_return_scale: Optional[int] = ..., **kwargs: Any) -> None: ...
    def result_processor(self, dialect: Dialect, coltype: Any) -> Optional[Callable[[Optional[Any]],
                                                                                    Optional[Union[float, decimal.Decimal]]]]: ...

class DateTime(_LookupExpressionAdapter, TypeEngine[datetime]):
    __visit_name__: str = ...
    timezone: bool = ...
    def __init__(self, timezone: bool = ...) -> None: ...
    def get_dbapi_type(self, dbapi: Any) -> Any: ...
    @property
    def python_type(self) -> Type[datetime]: ...

class Date(_LookupExpressionAdapter, TypeEngine[date]):
    __visit_name__: str = ...
    def get_dbapi_type(self, dbapi: Any) -> Any: ...
    @property
    def python_type(self) -> Type[date]: ...

class Time(_LookupExpressionAdapter, TypeEngine[time]):
    __visit_name__: str = ...
    timezone: bool = ...
    def __init__(self, timezone: bool = ...) -> None: ...
    def get_dbapi_type(self, dbapi: Any) -> Any: ...
    @property
    def python_type(self) -> Type[time]: ...

if sys.version_info[0] < 3:
    _BinaryType = str
else:
    _BinaryType = bytes

class _Binary(TypeEngine[_BinaryType]):
    length: int = ...
    def __init__(self, length: Optional[int] = ...) -> None: ...
    def literal_processor(self, dialect: Dialect) -> Callable[[_BinaryType], str]: ...
    @property
    def python_type(self) -> Type[_BinaryType]: ...
    def bind_processor(self, dialect: Dialect) -> Optional[Callable[[_BinaryType], Any]]: ...
    def result_processor(self, dialect: Dialect, coltype: Any) -> Optional[Callable[[Optional[Any]], Optional[_BinaryType]]]: ...
    def coerce_compared_value(self, op: Any, value: Any) -> TypeEngine[Any]: ...
    def get_dbapi_type(self, dbapi: Any) -> Any: ...

class LargeBinary(_Binary):
    __visit_name__: str = ...
    def __init__(self, length: Optional[int] = ...) -> None: ...

class Binary(LargeBinary):
    def __init__(self, length: Optional[int] = ...) -> None: ...

class SchemaType(SchemaEventTarget):
    name: Optional[str] = ...
    schema: Optional[str] = ...
    metadata: Optional[MetaData] = ...
    inherit_schema: bool = ...
    def __init__(self, name: Optional[str] = ..., schema: Optional[str] = ...,
                 metadata: Optional[MetaData] = ..., inherit_schema: bool = ...,
                 quote: Optional[bool] = ..., _create_events: bool = ...) -> None: ...
    def copy(self, **kw: Any) -> Any: ...
    def adapt(self, cls: Type[_T], **kw: Any) -> _T: ...
    @property
    def bind(self) -> Optional[Union[Engine, Connection]]: ...
    def create(self, bind: Optional[Connectable] = ..., checkfirst: bool = ...) -> None: ...
    def drop(self, bind: Optional[Connectable] = ..., checkfirst: bool = ...) -> None: ...

_E = TypeVar('_E', bound=Enum)

class Enum(Emulated, String, SchemaType):
    __visit_name__: str = ...
    native_enum: bool = ...
    create_constraint: bool = ...
    validate_strings: bool = ...
    enums: List[typing_Text] = ...  # This is set inside _setup_for_values.
    def __init__(self, *enums: Any, **kw: Any) -> None: ...
    @property
    def native(self) -> bool: ...
    comparator_factory: Type[Any] = ...
    def adapt_to_emulated(self, impltype: Type[_T], **kw: Any) -> _T: ...
    def adapt(self, cls: Type[Any], **kw: Any) -> Any: ...
    def literal_processor(self, dialect: Dialect): ...
    def bind_processor(self, dialect: Dialect): ...
    def result_processor(self, dialect: Dialect, coltype: Any): ...
    def copy(self: _E, **kw: Any) -> _E: ...
    @property
    def python_type(self): ...

class _Pickler(Protocol):
    if sys.version_info >= (3, 0):
        def dump(self, obj: Any, file: IO[bytes], protocol: Optional[int] = ..., *,
                 fix_imports: bool = ...) -> None: ...
        def dumps(self, obj: Any, protocol: Optional[int] = ..., *,
                  fix_imports: bool = ...) -> bytes: ...
        def loads(self, bytes_object: bytes, *, fix_imports: bool = ...,
                  encoding: str = ..., errors: str = ...) -> Any: ...
        def load(self, file: IO[bytes], *, fix_imports: bool = ..., encoding: str = ...,
                 errors: str = ...) -> Any: ...
    else:
        def dump(self, obj: Any, file: IO[bytes], protocol: Optional[int] = ...) -> None: ...
        def dumps(self, obj: Any, protocol: Optional[int] = ...) -> bytes: ...
        def load(self, file: IO[bytes]) -> Any: ...
        def loads(self, string: bytes) -> Any: ...

class PickleType(TypeDecorator[Any]):
    impl: Any = ...  # impl is Type[TypeEngine[LargeBinary]] on the class, but TypeEngine[LargeBinary] on instances
    protocol: int = ...
    pickler: _Pickler = ...
    comparator: Optional[Callable[[Any, Any], bool]] = ...
    def __init__(self, protocol: int = ..., pickler: Optional[_Pickler] = ...,
                 comparator: Optional[Callable[[Any, Any], bool]] = ...) -> None: ...
    def __reduce__(self) -> Tuple[Type[PickleType], Tuple[int, None, Optional[Callable[[Any, Any], bool]]]]: ...
    def bind_processor(self, dialect: Dialect) -> Callable[[Optional[Any]], Optional[str]]: ...
    def result_processor(self, dialect: Dialect, coltype: Any) -> Callable[[Optional[Any]], Any]: ...
    def compare_values(self, x: Any, y: Any) -> bool: ...

class Boolean(Emulated, TypeEngine[bool], SchemaType):
    __visit_name__: str = ...
    create_constraint: bool = ...
    name: str = ...
    def __init__(self, create_constraint: bool = ..., name: Optional[str] = ...,
                 _create_events: bool = ...) -> None: ...
    @property
    def python_type(self) -> Type[bool]: ...
    def literal_processor(self, dialect: Dialect) -> Callable[[Optional[bool]], str]: ...
    def bind_processor(self, dialect: Dialect) -> Callable[[Optional[bool]], Optional[Union[bool, int]]]: ...
    def result_processor(self, dialect: Dialect, coltype: Any) -> Optional[Callable[[Optional[int]], bool]]: ...

class _AbstractInterval(_LookupExpressionAdapter, TypeEngine[_T]):
    def coerce_compared_value(self, op: Any, value: Any) -> TypeEngine[Any]: ...

# "comparator_factory" of "_LookupExpressionAdapter" and "TypeDecorator" are incompatible
class Interval(Emulated, _AbstractInterval[timedelta], TypeDecorator[timedelta]):  # type: ignore
    impl: Any = ...  # impl is Type[TypeEngine[DateTime]] on the class, but TypeEngine[DateTime] on instances
    epoch: datetime = ...
    native: bool = ...
    second_precision: Optional[float] = ...
    day_precision: Optional[float] = ...
    def __init__(self, native: bool = ..., second_precision: Optional[float] = ...,
                 day_precision: Optional[float] = ...) -> None: ...
    def adapt_to_emulated(self, impltype: Type[Any], **kw: Any) -> Any: ...
    @property
    def python_type(self) -> Type[timedelta]: ...
    def bind_processor(self, dialect: Dialect) -> Callable[[Optional[timedelta]], str]: ...
    def result_processor(self, dialect: Dialect, coltype: Any) -> Callable[[Optional[str]], Optional[timedelta]]: ...

_JSONT = Union[Dict[str, Any], List[Any]]

class JSON(Indexable, TypeEngine[_JSONT]):
    __visit_name__: str = ...
    hashable: bool = ...
    NULL: util.symbol = ...
    none_as_null: bool = ...
    def __init__(self, none_as_null: bool = ...) -> None: ...
    class JSONElementType(TypeEngine[Union[int, str]]):
        def string_bind_processor(self, dialect): ...
        def string_literal_processor(self, dialect): ...
        def bind_processor(self, dialect): ...
        def literal_processor(self, dialect): ...
    class JSONIndexType(JSONElementType): ...
    class JSONPathType(JSONElementType): ...
    comparator_factory: Type[Any] = ...
    # This method returns `dict`, but it seems to be a bug in SQLAlchemy.
    @property
    def python_type(self) -> Type[Dict[str, Any]]: ...
    @property
    def should_evaluate_none(self) -> bool: ...  # type: ignore  # incompatible with supertype "TypeEngine"
    def bind_processor(self, dialect: Dialect) -> Callable[[Optional[_JSONT]], Optional[str]]: ...
    def result_processor(self, dialect: Dialect, coltype: Any) -> Callable[[Optional[str]], Optional[_JSONT]]: ...

class ARRAY(Indexable, Concatenable, TypeEngine[List[_T]]):
    __visit_name__: str = ...
    zero_indexes: bool = ...
    comparator_factory: Type[Any] = ...
    item_type: TypeEngine[_T] = ...
    as_tuple: bool = ...
    dimensions: Optional[int] = ...
    @overload
    def __init__(self, item_type: TypeEngine[_T], as_tuple: bool = ..., dimensions: Optional[int] = ...,
                 zero_indexes: bool = ...) -> None: ...
    @overload
    def __init__(self, item_type: Type[TypeEngine[_T]], as_tuple: bool = ..., dimensions: Optional[int] = ...,
                 zero_indexes: bool = ...) -> None: ...
    @property
    def hashable(self) -> bool: ...  # type: ignore  # incompatible with supertype "TypeEngine"
    @property
    def python_type(self) -> Type[List[_T]]: ...
    def compare_values(self, x: Any, y: Any) -> bool: ...  # this function just does a simple comparison

class REAL(Float):
    __visit_name__: str = ...

class FLOAT(Float):
    __visit_name__: str = ...

class NUMERIC(Numeric):
    __visit_name__: str = ...

class DECIMAL(Numeric):
    __visit_name__: str = ...

class INTEGER(Integer):
    __visit_name__: str = ...

INT = INTEGER

class SMALLINT(SmallInteger):
    __visit_name__: str = ...

class BIGINT(BigInteger):
    __visit_name__: str = ...

class TIMESTAMP(DateTime):
    __visit_name__: str = ...
    def __init__(self, timezone: bool = ...) -> None: ...
    def get_dbapi_type(self, dbapi: Any) -> Any: ...

class DATETIME(DateTime):
    __visit_name__: str = ...

class DATE(Date):
    __visit_name__: str = ...

class TIME(Time):
    __visit_name__: str = ...

class TEXT(Text):
    __visit_name__: str = ...

class CLOB(Text):
    __visit_name__: str = ...

class VARCHAR(String):
    __visit_name__: str = ...

class NVARCHAR(Unicode):
    __visit_name__: str = ...

class CHAR(String):
    __visit_name__: str = ...

class NCHAR(Unicode):
    __visit_name__: str = ...

class BLOB(LargeBinary):
    __visit_name__: str = ...

class BINARY(_Binary):
    __visit_name__: str = ...

class VARBINARY(_Binary):
    __visit_name__: str = ...

class BOOLEAN(Boolean):
    __visit_name__: str = ...

class NullType(TypeEngine[None]):
    __visit_name__: str = ...
    hashable: bool = ...
    def literal_processor(self, dialect: Dialect) -> Callable[[Any], str]: ...
    comparator_factory: Type[Any] = ...

class MatchType(Boolean): ...

NULLTYPE: NullType = ...
BOOLEANTYPE: Boolean = ...
STRINGTYPE: String = ...
INTEGERTYPE: Integer = ...
MATCHTYPE: MatchType = ...
