from contextlib import contextmanager

import pytest
import psycopg2
import psycopg2.extras
from os import getenv


# TODO: should this be somehow be divined from environment?
POSTGRES_USER = getenv("PGUSER", "postgres")
POSTGRES_HOST = getenv("PGHOST", "localhost")
POSTGRES_PORT = getenv("PGPORT", 5432)
POSTGRES_PASSWORD = getenv("PGPASSWORD", "")

TEST_DB_NAME = "_test_db"
FOREIGN_TEST_DB_NAME = "_foreign_test_db"


def db_connection(dbname=None):
    conn = psycopg2.connect(
        user=POSTGRES_USER,
        host=POSTGRES_HOST,
        password=POSTGRES_PASSWORD,
        port=POSTGRES_PORT,
        database=dbname,
    )
    conn.autocommit = True
    return conn


try:
    conn = db_connection(dbname=None)
    CAN_CONNECT_TO_DB = True
    SERVER_VERSION = conn.server_version
except:
    CAN_CONNECT_TO_DB = False
    SERVER_VERSION = 0


dbtest = pytest.mark.skipif(
    not CAN_CONNECT_TO_DB,
    reason="Need a postgres instance at localhost accessible by user "
    "'%s'" % POSTGRES_USER,
)


def create_db(dbname=TEST_DB_NAME):
    with db_connection(dbname=None).cursor() as cur:
        try:
            cur.execute("""CREATE DATABASE %s""" % dbname)
        except:
            pass


def setup_db(conn):
    with conn.cursor() as cur:
        # schemas
        cur.execute("create schema schema1")
        cur.execute("create schema schema2")
        cur.execute("create schema schema3")

        # tables
        cur.execute(
            "create table tbl1(id1 integer, txt1 text, CONSTRAINT id_text PRIMARY KEY(id1, txt1))"
        )
        cur.execute("create table tbl2(id2 serial, txt2 text)")
        cur.execute("create table schema2.tbl2(id2 serial, txt2 text)")
        cur.execute("create table schema1.tbl2(id2 serial, txt2 text)")
        cur.execute("create table schema1.s1_tbl1(id1 integer, txt1 text)")
        cur.execute("create table tbl3(c3 circle, exclude using gist (c3 with &&))")
        cur.execute('create table "Inh1"(value1 integer) inherits (tbl1)')
        cur.execute("create table inh2(value2 integer) inherits (tbl1, tbl2)")
        cur.execute(
            """
        create table schema3.test_generated_default 
        (id int GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, some_stuff text)"""
        )

        # views
        cur.execute("create view vw1 as select * from tbl1")
        cur.execute(
            """create view schema1.s1_vw1 as
                       select * from schema1.s1_tbl1"""
        )

        # materialized views
        cur.execute("create materialized view mvw1 as select * from tbl1")
        cur.execute(
            """create materialized view schema1.s1_mvw1 as
                       select * from schema1.s1_tbl1"""
        )

        # datatype
        cur.execute("create type foo AS (a int, b text)")

        # functions
        cur.execute(
            """create function func1() returns int language sql as
                       $$select 1$$"""
        )
        cur.execute(
            """create function schema1.s1_func1() returns int language
                       sql as $$select 2$$"""
        )

        # domains
        cur.execute("create domain gender_t char(1)" " check (value in ('F', 'M'))")
        cur.execute("create domain schema1.smallint_t smallint")
        cur.execute("create domain schema1.bigint_t bigint")
        cur.execute("comment on domain schema1.bigint_t is" " 'a really large integer'")


def teardown_db(conn):
    with conn.cursor() as cur:
        cur.execute(
            """
            DROP SCHEMA public CASCADE;
            CREATE SCHEMA public;
            DROP SCHEMA IF EXISTS schema1 CASCADE;
            DROP SCHEMA IF EXISTS schema2 CASCADE;
            DROP SCHEMA IF EXISTS schema3 CASCADE;"""
        )


def setup_foreign(conn):

    foreign_conn = db_connection(FOREIGN_TEST_DB_NAME)
    with foreign_conn.cursor() as foreign_cur:
        foreign_cur.execute("create table if not exists foreign_foo (a int, b text)")

    with conn.cursor() as cur:

        # foreign database wrapper
        cur.execute("create extension if not exists postgres_fdw")
        cur.execute(
            "create server if not exists foreign_db_server "
            "foreign data wrapper postgres_fdw "
            "options (host '127.0.0.1', dbname %s )",
            (FOREIGN_TEST_DB_NAME,),
        )
        cur.execute(
            "create user mapping if not exists for current_user "
            "server foreign_db_server "
            "options (user 'postgres') "
        )
        cur.execute(
            "create foreign table if not exists foreign_foo (a int, b text) "
            "server foreign_db_server "
            "options (schema_name 'public', table_name 'foreign_foo') "
        )


def teardown_foreign(conn):

    with conn.cursor() as cur:

        cur.execute("drop server if exists foreign_db_server cascade")
        cur.execute("drop extension if exists postgres_fdw")
        cur.execute("drop database if exists %s" % FOREIGN_TEST_DB_NAME)


@contextmanager
def foreign_db_environ():
    conn2 = db_connection(dbname=TEST_DB_NAME)
    create_db(FOREIGN_TEST_DB_NAME)
    setup_foreign(conn2)
    yield
    teardown_foreign(conn2)
