from sqlalchemy import Column
from sqlalchemy import func
from sqlalchemy import Integer
from sqlalchemy import String
from sqlalchemy.orm import aliased
from sqlalchemy.orm import declarative_base
from sqlalchemy.orm import descriptor_props
from sqlalchemy.orm.interfaces import PropComparator
from sqlalchemy.orm.properties import ColumnProperty
from sqlalchemy.sql import column
from sqlalchemy.testing import eq_
from sqlalchemy.testing import fixtures
from sqlalchemy.util import partial


class MockDescriptor(descriptor_props.DescriptorProperty):
    def __init__(
        self, cls, key, descriptor=None, doc=None, comparator_factory=None
    ):
        self.parent = cls.__mapper__
        self.key = key
        self.doc = doc
        self.descriptor = descriptor
        if comparator_factory:
            self._comparator_factory = partial(comparator_factory, self)
        else:
            self._comparator_factory = lambda mapper: None


class DescriptorInstrumentationTest(fixtures.ORMTest):
    def _fixture(self):
        Base = declarative_base()

        class Foo(Base):
            __tablename__ = "foo"
            id = Column(Integer, primary_key=True)

        return Foo

    def test_fixture(self):
        Foo = self._fixture()

        d = MockDescriptor(Foo, "foo")
        d.instrument_class(Foo.__mapper__)

        assert Foo.foo

    def test_property_wrapped_classlevel(self):
        Foo = self._fixture()
        prop = property(lambda self: None)
        Foo.foo = prop

        d = MockDescriptor(Foo, "foo")
        d.instrument_class(Foo.__mapper__)

        assert Foo().foo is None
        assert Foo.foo is not prop

    def test_property_subclass_wrapped_classlevel(self):
        Foo = self._fixture()

        class myprop(property):
            attr = "bar"

            def method1(self):
                return "method1"

        prop = myprop(lambda self: None)
        Foo.foo = prop

        d = MockDescriptor(Foo, "foo")
        d.instrument_class(Foo.__mapper__)

        assert Foo().foo is None
        assert Foo.foo is not prop
        assert Foo.foo.attr == "bar"
        assert Foo.foo.method1() == "method1"

    def test_comparator(self):
        class Comparator(PropComparator):
            __hash__ = None

            attr = "bar"

            def method1(self):
                return "method1"

            def method2(self, other):
                return "method2"

            def __getitem__(self, key):
                return "value"

            def __eq__(self, other):
                return column("foo") == func.upper(other)

        Foo = self._fixture()
        d = MockDescriptor(Foo, "foo", comparator_factory=Comparator)
        d.instrument_class(Foo.__mapper__)
        eq_(Foo.foo.method1(), "method1")
        eq_(Foo.foo.method2("x"), "method2")
        assert Foo.foo.attr == "bar"
        assert Foo.foo["bar"] == "value"
        eq_((Foo.foo == "bar").__str__(), "foo = upper(:upper_1)")

    def test_aliased_comparator(self):
        class Comparator(ColumnProperty.Comparator):
            __hash__ = None

            def __eq__(self, other):
                return func.foobar(self.__clause_element__()) == func.foobar(
                    other
                )

        Foo = self._fixture()
        Foo._name = Column("name", String)

        def comparator_factory(self, mapper):
            prop = mapper._props["_name"]
            return Comparator(prop, mapper)

        d = MockDescriptor(Foo, "foo", comparator_factory=comparator_factory)
        d.instrument_class(Foo.__mapper__)

        eq_(str(Foo.foo == "ed"), "foobar(foo.name) = foobar(:foobar_1)")
        eq_(
            str(aliased(Foo).foo == "ed"),
            "foobar(foo_1.name) = foobar(:foobar_1)",
        )
