import pytest
import six
import sqlalchemy as sa
from sqlalchemy.util.langhelpers import symbol

from sqlalchemy_utils.path import AttrPath, Path


@pytest.fixture
def Document(Base):
    class Document(Base):
        __tablename__ = 'document'
        id = sa.Column(sa.Integer, primary_key=True)
        name = sa.Column(sa.Unicode(255))
        locale = sa.Column(sa.String(10))
    return Document


@pytest.fixture
def Section(Base, Document):
    class Section(Base):
        __tablename__ = 'section'
        id = sa.Column(sa.Integer, primary_key=True)
        name = sa.Column(sa.Unicode(255))
        locale = sa.Column(sa.String(10))

        document_id = sa.Column(
            sa.Integer, sa.ForeignKey(Document.id)
        )

        document = sa.orm.relationship(Document, backref='sections')
    return Section


@pytest.fixture
def SubSection(Base, Section):
    class SubSection(Base):
        __tablename__ = 'subsection'
        id = sa.Column(sa.Integer, primary_key=True)
        name = sa.Column(sa.Unicode(255))
        locale = sa.Column(sa.String(10))

        section_id = sa.Column(
            sa.Integer, sa.ForeignKey(Section.id)
        )

        section = sa.orm.relationship(Section, backref='subsections')
    return SubSection


class TestAttrPath(object):

    @pytest.fixture
    def init_models(self, Document, Section, SubSection):
        pass

    def test_direction(self, SubSection):
        assert (
            AttrPath(SubSection, 'section').direction == symbol('MANYTOONE')
        )

    def test_invert(self, Document, Section, SubSection):
        path = ~ AttrPath(SubSection, 'section.document')
        assert path.parts == [
            Document.sections,
            Section.subsections
        ]
        assert str(path.path) == 'sections.subsections'

    def test_len(self, SubSection):
        len(AttrPath(SubSection, 'section.document')) == 2

    def test_init(self, SubSection):
        path = AttrPath(SubSection, 'section.document')
        assert path.class_ == SubSection
        assert path.path == Path('section.document')

    def test_iter(self, Section, SubSection):
        path = AttrPath(SubSection, 'section.document')
        assert list(path) == [
            SubSection.section,
            Section.document
        ]

    def test_repr(self, SubSection):
        path = AttrPath(SubSection, 'section.document')
        assert repr(path) == (
            "AttrPath(SubSection, 'section.document')"
        )

    def test_index(self, Section, SubSection):
        path = AttrPath(SubSection, 'section.document')
        assert path.index(Section.document) == 1
        assert path.index(SubSection.section) == 0

    def test_getitem(self, Section, SubSection):
        path = AttrPath(SubSection, 'section.document')
        assert path[0] is SubSection.section
        assert path[1] is Section.document

    def test_getitem_with_slice(self, Section, SubSection):
        path = AttrPath(SubSection, 'section.document')
        assert path[:] == AttrPath(SubSection, 'section.document')
        assert path[:-1] == AttrPath(SubSection, 'section')
        assert path[1:] == AttrPath(Section, 'document')

    def test_eq(self, SubSection):
        assert (
            AttrPath(SubSection, 'section.document') ==
            AttrPath(SubSection, 'section.document')
        )
        assert not (
            AttrPath(SubSection, 'section') ==
            AttrPath(SubSection, 'section.document')
        )

    def test_ne(self, SubSection):
        assert not (
            AttrPath(SubSection, 'section.document') !=
            AttrPath(SubSection, 'section.document')
        )
        assert (
            AttrPath(SubSection, 'section') !=
            AttrPath(SubSection, 'section.document')
        )


class TestPath(object):
    def test_init(self):
        path = Path('attr.attr2')
        assert path.path == 'attr.attr2'

    def test_init_with_path_object(self):
        path = Path(Path('attr.attr2'))
        assert path.path == 'attr.attr2'

    def test_iter(self):
        path = Path('s.s2.s3')
        assert list(path) == ['s', 's2', 's3']

    @pytest.mark.parametrize(('path', 'length'), (
        (Path('s.s2.s3'), 3),
        (Path('s.s2'), 2),
        (Path(''), 0)
    ))
    def test_len(self, path, length):
        return len(path) == length

    def test_reversed(self):
        path = Path('s.s2.s3')
        assert list(reversed(path)) == ['s3', 's2', 's']

    def test_repr(self):
        path = Path('s.s2')
        assert repr(path) == "Path('s.s2')"

    def test_getitem(self):
        path = Path('s.s2')
        assert path[0] == 's'
        assert path[1] == 's2'

    def test_str(self):
        assert str(Path('s.s2')) == 's.s2'

    def test_index(self):
        assert Path('s.s2.s3').index('s2') == 1

    def test_unicode(self):
        assert six.text_type(Path('s.s2')) == u's.s2'

    def test_getitem_with_slice(self):
        path = Path('s.s2.s3')
        assert path[1:] == Path('s2.s3')

    @pytest.mark.parametrize(('test', 'result'), (
        (Path('s.s2') == Path('s.s2'), True),
        (Path('s.s2') == Path('s.s3'), False)
    ))
    def test_eq(self, test, result):
        assert test is result

    @pytest.mark.parametrize(('test', 'result'), (
        (Path('s.s2') != Path('s.s2'), False),
        (Path('s.s2') != Path('s.s3'), True)
    ))
    def test_ne(self, test, result):
        assert test is result
