import pytest
from sqlalchemy import MetaData, Table
from sqlalchemy.schema import CreateTable
from sqlalchemy.exc import NoSuchTableError

from sqlalchemy_redshift import dialect

from rs_sqla_test_utils import models, utils


def table_to_ddl(table):
    return str(CreateTable(table).compile(
        dialect=dialect.RedshiftDialect()
    ))


models_and_ddls = [
    (models.ReflectionDistKey, """
    CREATE TABLE reflection_distkey (
        col1 INTEGER NOT NULL,
        col2 INTEGER,
        PRIMARY KEY (col1)
    ) DISTSTYLE KEY DISTKEY (col1)
    """),
    (models.ReflectionSortKey, """
    CREATE TABLE reflection_sortkey (
        col1 INTEGER NOT NULL,
        col2 INTEGER,
        PRIMARY KEY (col1)
    ) DISTSTYLE EVEN SORTKEY (col1, col2)
    """),
    (models.ReflectionInterleavedSortKey, """
    CREATE TABLE reflection_interleaved_sortkey (
        col1 INTEGER NOT NULL,
        col2 INTEGER,
        PRIMARY KEY (col1)
    ) DISTSTYLE EVEN INTERLEAVED SORTKEY (col1, col2)
    """),
    (models.ReflectionSortKeyDistKeyWithSpaces, """
    CREATE TABLE sort_key_with_spaces (
        "col with spaces" INTEGER NOT NULL
    ) DISTSTYLE KEY DISTKEY ("col with spaces") SORTKEY ("col with spaces")
    """),
    (models.ReflectionUniqueConstraint, """
    CREATE TABLE reflection_unique_constraint (
        col1 INTEGER NOT NULL,
        col2 INTEGER,
        PRIMARY KEY (col1),
        UNIQUE (col1, col2)
    ) DISTSTYLE EVEN
    """),
    (models.ReflectionPrimaryKeyConstraint, """
    CREATE TABLE reflection_pk_constraint (
        col1 INTEGER NOT NULL,
        col2 INTEGER NOT NULL,
        PRIMARY KEY (col1, col2)
    ) DISTSTYLE EVEN
    """),
    (models.ReflectionNamedPrimaryKeyConstraint, """
    CREATE TABLE reflection_named_pk_constraint (
        col1 INTEGER NOT NULL,
        col2 INTEGER NOT NULL,
        CONSTRAINT reflection_named_pk_constraint__pkey
            PRIMARY KEY (col1, col2)
    ) DISTSTYLE EVEN
    """),
    (models.ReflectionForeignKeyConstraint, """
    CREATE TABLE reflection_fk_constraint (
        col1 INTEGER NOT NULL,
        col2 INTEGER,
        PRIMARY KEY (col1),
        FOREIGN KEY(col1) REFERENCES reflection_unique_constraint (col1)
    ) DISTSTYLE EVEN
    """),
    (models.ReflectionNamedForeignKeyConstraint, """
    CREATE TABLE reflection_named_fk_constraint (
        col1 INTEGER NOT NULL,
        col2 INTEGER,
        PRIMARY KEY (col1),
        CONSTRAINT reflection_named_fk_constraint__fk
            FOREIGN KEY(col1)
            REFERENCES reflection_unique_constraint (col1)
    ) DISTSTYLE EVEN
    """),
    (models.ReflectionDefaultValue, """
    CREATE TABLE reflection_default_value (
        col1 INTEGER NOT NULL,
        col2 INTEGER DEFAULT 5,
        PRIMARY KEY (col1)
    ) DISTSTYLE EVEN
    """),
    (models.ReflectionIdentity, """
    CREATE TABLE reflection_identity (
        col1 INTEGER NOT NULL,
        col2 INTEGER IDENTITY(1,3),
        col3 INTEGER,
        PRIMARY KEY (col1)
    ) DISTSTYLE EVEN
    """),
    (models.ReflectionDelimitedTableName, """
    CREATE TABLE other_schema."this.table" (
        id INTEGER NOT NULL,
        PRIMARY KEY (id)
    ) DISTSTYLE EVEN
    """),
    (models.ReflectionDelimitedTableNoSchema, """
    CREATE TABLE "this.table" (
        id INTEGER NOT NULL,
        PRIMARY KEY (id)
    ) DISTSTYLE EVEN
    """),
    (models.BasicInOtherSchema, """
    CREATE TABLE other_schema.basic (
        col1 INTEGER NOT NULL,
        PRIMARY KEY (col1)
    ) DISTSTYLE KEY DISTKEY (col1) SORTKEY (col1)
    """),
    pytest.mark.xfail((models.ReflectionDelimitedIdentifiers1, '''
    CREATE TABLE "group" (
        "this ""is it""" INTEGER NOT NULL,
        "and this also" INTEGER,
        PRIMARY KEY ("this ""is it""")
    ) DISTSTYLE EVEN
    ''')),
    pytest.mark.xfail((models.ReflectionDelimitedIdentifiers2, '''
    CREATE TABLE "column" (
            "excellent! & column" INTEGER NOT NULL,
            "most @exce.llent " INTEGER,
            PRIMARY KEY ("excellent! & column"),
    ) DISTSTYLE EVEN
    ''')),
    (models.ReflectionCustomReservedWords, '''
    CREATE TABLE "aes256" (
        "open" INTEGER,
        "tag" INTEGER,
        pkey INTEGER NOT NULL,
        PRIMARY KEY (pkey)
    ) DISTSTYLE EVEN
    '''),
    (models.Referencing, '''
    CREATE TABLE other_schema.referencing (
        referenced_table_id INTEGER NOT NULL,
        PRIMARY KEY (referenced_table_id),
        FOREIGN KEY(referenced_table_id) REFERENCES
            other_schema.referenced (id)
    ) DISTSTYLE EVEN
    '''),
    (models.Referenced, '''
    CREATE TABLE other_schema.referenced (
        id INTEGER IDENTITY(1,1) NOT NULL,
        PRIMARY KEY (id)
    ) DISTSTYLE EVEN
    '''),
    (models.ReflectionCompositeForeignKeyConstraint, '''
    CREATE TABLE reflection_composite_fk_constraint (
        id INTEGER NOT NULL,
        col1 INTEGER,
        col2 INTEGER,
        PRIMARY KEY (id),
        FOREIGN KEY(col1, col2)
        REFERENCES reflection_pk_constraint (col1, col2)
    ) DISTSTYLE EVEN
    '''),
]


@pytest.mark.parametrize("model, ddl", models_and_ddls)
def test_definition(model, ddl):
    model_ddl = table_to_ddl(model.__table__)
    assert utils.clean(model_ddl) == utils.clean(ddl)


@pytest.mark.parametrize("model, ddl", models_and_ddls)
def test_reflection(redshift_session, model, ddl):
    metadata = MetaData(bind=redshift_session.bind)
    schema = model.__table__.schema
    table = Table(model.__tablename__, metadata,
                  schema=schema, autoload=True)
    introspected_ddl = table_to_ddl(table)
    assert utils.clean(introspected_ddl) == utils.clean(ddl)


def test_no_table_reflection(redshift_session):
    metadata = MetaData(bind=redshift_session.bind)
    with pytest.raises(NoSuchTableError):
        Table('foobar', metadata, autoload=True)


def test_no_search_path_leak(redshift_session):
    metadata = MetaData(bind=redshift_session.bind)
    Table('basic', metadata, autoload=True)
    result = redshift_session.execute("SHOW search_path")
    search_path = result.scalar()
    assert 'other_schema' not in search_path