from sqlalchemy.testing import fixtures
from sqlalchemy.sql.ddl import SchemaGenerator, SchemaDropper
from sqlalchemy import MetaData, Table, Column, Integer, Sequence, ForeignKey
from sqlalchemy import schema
from sqlalchemy.testing.mock import Mock


class EmitDDLTest(fixtures.TestBase):

    def _mock_connection(self, item_exists):
        def has_item(connection, name, schema):
            return item_exists(name)

        return Mock(dialect=Mock(
                    supports_sequences=True,
                    has_table=Mock(side_effect=has_item),
                    has_sequence=Mock(side_effect=has_item),
                    supports_comments=True,
                    inline_comments=False,
                    )
                    )

    def _mock_create_fixture(self, checkfirst, tables,
                             item_exists=lambda item: False):
        connection = self._mock_connection(item_exists)

        return SchemaGenerator(connection.dialect, connection,
                               checkfirst=checkfirst,
                               tables=tables)

    def _mock_drop_fixture(self, checkfirst, tables,
                           item_exists=lambda item: True):
        connection = self._mock_connection(item_exists)

        return SchemaDropper(connection.dialect, connection,
                             checkfirst=checkfirst,
                             tables=tables)

    def _table_fixture(self):
        m = MetaData()

        return (m, ) + tuple(
            Table('t%d' % i, m, Column('x', Integer))
            for i in range(1, 6)
        )

    def _use_alter_fixture_one(self):
        m = MetaData()

        t1 = Table(
            't1', m, Column('id', Integer, primary_key=True),
            Column('t2id', Integer, ForeignKey('t2.id'))
        )
        t2 = Table(
            't2', m, Column('id', Integer, primary_key=True),
            Column('t1id', Integer, ForeignKey('t1.id'))
        )
        return m, t1, t2

    def _fk_fixture_one(self):
        m = MetaData()

        t1 = Table(
            't1', m, Column('id', Integer, primary_key=True),
            Column('t2id', Integer, ForeignKey('t2.id'))
        )
        t2 = Table(
            't2', m, Column('id', Integer, primary_key=True),
        )
        return m, t1, t2

    def _table_seq_fixture(self):
        m = MetaData()

        s1 = Sequence('s1')
        s2 = Sequence('s2')
        t1 = Table('t1', m, Column("x", Integer, s1, primary_key=True))
        t2 = Table('t2', m, Column("x", Integer, s2, primary_key=True))

        return m, t1, t2, s1, s2

    def _table_comment_fixture(self):
        m = MetaData()

        c1 = Column('id', Integer, comment='c1')

        t1 = Table(
            't1', m, c1,
            comment='t1'
        )

        return m, t1, c1

    def test_comment(self):
        m, t1, c1 = self._table_comment_fixture()

        generator = self._mock_create_fixture(
            False, [t1], item_exists=lambda t: t not in ("t1",))

        self._assert_create_comment([t1, t1, c1], generator, m)

    def test_create_seq_checkfirst(self):
        m, t1, t2, s1, s2 = self._table_seq_fixture()
        generator = self._mock_create_fixture(
            True, [
                t1, t2], item_exists=lambda t: t not in (
                "t1", "s1"))

        self._assert_create([t1, s1], generator, m)

    def test_drop_seq_checkfirst(self):
        m, t1, t2, s1, s2 = self._table_seq_fixture()
        generator = self._mock_drop_fixture(
            True, [
                t1, t2], item_exists=lambda t: t in (
                "t1", "s1"))

        self._assert_drop([t1, s1], generator, m)

    def test_create_collection_checkfirst(self):
        m, t1, t2, t3, t4, t5 = self._table_fixture()
        generator = self._mock_create_fixture(
            True, [
                t2, t3, t4], item_exists=lambda t: t not in (
                "t2", "t4"))

        self._assert_create_tables([t2, t4], generator, m)

    def test_drop_collection_checkfirst(self):
        m, t1, t2, t3, t4, t5 = self._table_fixture()
        generator = self._mock_drop_fixture(
            True, [
                t2, t3, t4], item_exists=lambda t: t in (
                "t2", "t4"))

        self._assert_drop_tables([t2, t4], generator, m)

    def test_create_collection_nocheck(self):
        m, t1, t2, t3, t4, t5 = self._table_fixture()
        generator = self._mock_create_fixture(
            False, [
                t2, t3, t4], item_exists=lambda t: t not in (
                "t2", "t4"))

        self._assert_create_tables([t2, t3, t4], generator, m)

    def test_create_empty_collection(self):
        m, t1, t2, t3, t4, t5 = self._table_fixture()
        generator = self._mock_create_fixture(
            True,
            [],
            item_exists=lambda t: t not in (
                "t2",
                "t4"))

        self._assert_create_tables([], generator, m)

    def test_drop_empty_collection(self):
        m, t1, t2, t3, t4, t5 = self._table_fixture()
        generator = self._mock_drop_fixture(
            True,
            [],
            item_exists=lambda t: t in (
                "t2",
                "t4"))

        self._assert_drop_tables([], generator, m)

    def test_drop_collection_nocheck(self):
        m, t1, t2, t3, t4, t5 = self._table_fixture()
        generator = self._mock_drop_fixture(
            False, [
                t2, t3, t4], item_exists=lambda t: t in (
                "t2", "t4"))

        self._assert_drop_tables([t2, t3, t4], generator, m)

    def test_create_metadata_checkfirst(self):
        m, t1, t2, t3, t4, t5 = self._table_fixture()
        generator = self._mock_create_fixture(
            True,
            None,
            item_exists=lambda t: t not in (
                "t2",
                "t4"))

        self._assert_create_tables([t2, t4], generator, m)

    def test_drop_metadata_checkfirst(self):
        m, t1, t2, t3, t4, t5 = self._table_fixture()
        generator = self._mock_drop_fixture(
            True,
            None,
            item_exists=lambda t: t in (
                "t2",
                "t4"))

        self._assert_drop_tables([t2, t4], generator, m)

    def test_create_metadata_nocheck(self):
        m, t1, t2, t3, t4, t5 = self._table_fixture()
        generator = self._mock_create_fixture(
            False,
            None,
            item_exists=lambda t: t not in (
                "t2",
                "t4"))

        self._assert_create_tables([t1, t2, t3, t4, t5], generator, m)

    def test_drop_metadata_nocheck(self):
        m, t1, t2, t3, t4, t5 = self._table_fixture()
        generator = self._mock_drop_fixture(
            False,
            None,
            item_exists=lambda t: t in (
                "t2",
                "t4"))

        self._assert_drop_tables([t1, t2, t3, t4, t5], generator, m)

    def test_create_metadata_auto_alter_fk(self):
        m, t1, t2 = self._use_alter_fixture_one()
        generator = self._mock_create_fixture(
            False, [t1, t2]
        )
        self._assert_create_w_alter(
            [t1, t2] +
            list(t1.foreign_key_constraints) +
            list(t2.foreign_key_constraints),
            generator,
            m
        )

    def test_create_metadata_inline_fk(self):
        m, t1, t2 = self._fk_fixture_one()
        generator = self._mock_create_fixture(
            False, [t1, t2]
        )
        self._assert_create_w_alter(
            [t1, t2] +
            list(t1.foreign_key_constraints) +
            list(t2.foreign_key_constraints),
            generator,
            m
        )

    def _assert_create_tables(self, elements, generator, argument):
        self._assert_ddl(schema.CreateTable, elements, generator, argument)

    def _assert_drop_tables(self, elements, generator, argument):
        self._assert_ddl(schema.DropTable, elements, generator, argument)

    def _assert_create(self, elements, generator, argument):
        self._assert_ddl(
            (schema.CreateTable, schema.CreateSequence),
            elements, generator, argument)

    def _assert_drop(self, elements, generator, argument):
        self._assert_ddl(
            (schema.DropTable, schema.DropSequence),
            elements, generator, argument)

    def _assert_create_w_alter(self, elements, generator, argument):
        self._assert_ddl(
            (schema.CreateTable, schema.CreateSequence, schema.AddConstraint),
            elements, generator, argument)

    def _assert_drop_w_alter(self, elements, generator, argument):
        self._assert_ddl(
            (schema.DropTable, schema.DropSequence, schema.DropConstraint),
            elements, generator, argument)

    def _assert_create_comment(self, elements, generator, argument):
        self._assert_ddl(
            (schema.CreateTable, schema.SetTableComment, schema.SetColumnComment),
            elements, generator, argument)

    def _assert_ddl(self, ddl_cls, elements, generator, argument):
        generator.traverse_single(argument)
        for call_ in generator.connection.execute.mock_calls:
            c = call_[1][0]
            assert isinstance(c, ddl_cls)
            assert c.element in elements, "element %r was not expected"\
                % c.element
            elements.remove(c.element)
            if getattr(c, 'include_foreign_key_constraints', None) is not None:
                elements[:] = [
                    e for e in elements
                    if e not in set(c.include_foreign_key_constraints)]
        assert not elements, "elements remain in list: %r" % elements
