import os
import os.path
import random

import pytest
import testing.postgresql
import sqlalchemy
import sqlalchemy.pool


@pytest.fixture(scope="session")
def postgresql_factory(request, postgres_opt):
    if postgres_opt:
        return running_postgresql_factory(request, postgres_opt)
    return spawned_postgresql_factory(request)


def _execute_autocommit_query(dsn, query):
    if isinstance(dsn, dict):
        dsn = sqlalchemy.engine.url.URL("postgresql", **dsn)
    e = sqlalchemy.create_engine(
        dsn,
        poolclass=sqlalchemy.pool.NullPool,
        echo=True,
        echo_pool=True
        )
    c = e.connect()
    c.execute("COMMIT")
    c.execute(query)
    c.close()
    e.dispose()


def randstr(length):
    return ''.join(
        random.choice('abcdefghijklmnopqrstuvwxyz')
        for _ in range(length))


def running_postgresql_factory(request, dsn):
    mekansm_template_db = "mekansm_template_" + randstr(5)
    _execute_autocommit_query(
        dsn, "DROP DATABASE IF EXISTS " + mekansm_template_db)
    _execute_autocommit_query(
        dsn,
        "CREATE DATABASE {} TEMPLATE template0".format(mekansm_template_db))
    e = sqlalchemy.create_engine(dsn)
    mekansm_dsn = e.url.translate_connect_args()
    mekansm_dsn["database"] = mekansm_template_db
    install_db_migrations(mekansm_dsn)

    class RunningPostgresFactory(object):

        def __init__(self):
            self._name = None

        @property
        def name(self):
            if self._name is None:
                self._name = "mekansm_{}".format(randstr(20))
                _execute_autocommit_query(
                        mekansm_dsn,
                        "CREATE DATABASE {} TEMPLATE {}".format(
                                self._name, mekansm_template_db))
            return self._name

        def dsn(self):
            dsn = mekansm_dsn.copy()
            dsn["database"] = self.name
            dsn["user"] = dsn.pop("username")
            return dsn

        def stop(self):
            _execute_autocommit_query(
                mekansm_dsn, "DROP DATABASE " + self.name)

    def finalizer():
        _execute_autocommit_query(dsn, "DROP DATABASE " + mekansm_template_db)

    request.addfinalizer(finalizer)
    return RunningPostgresFactory


def spawned_postgresql_factory(request):

    def on_initialized(instance):
        return install_db_migrations(instance.url())

    factory = testing.postgresql.PostgresqlFactory(
        cache_initialized_db=True, on_initialized=on_initialized)

    def finalizer():
        factory.clear_cache()

    request.addfinalizer(finalizer)
    return factory


@pytest.fixture(scope="function")
def postgresql_db(request, postgresql_factory):
    db = postgresql_factory()

    def finalizer():
        db.stop()

    request.addfinalizer(finalizer)
    return db


@pytest.fixture(scope="function")
def mekansm_dbconf(postgresql_db):
    """MekansmDB factory.

    :returns: A dictionary containing the db configuration
    :rtype: dict
    """
    dsn = postgresql_db.dsn()
    return {
        "host": dsn["host"],
        "port": dsn["port"],
        "user": dsn["user"],
        "database": dsn["database"],
        "password": dsn.get("password", ""),
        "engine": {
            "poolclass": sqlalchemy.pool.NullPool,
            "echo": True,
            "echo_pool": True
        }
    }


def install_db_migrations(dsn):
    process_migrations(dsn, goose_up_handler)


def uninstall_db_migrations(dsn):
    process_migrations(dsn, goose_down_handler)


def goose_up_handler(fh):
    lines = []
    for line in fh.readlines():
        if line.startswith("-- +goose Down"):
            break
        else:
            lines.append(line.rstrip("\n"))
    return lines


def goose_down_handler(fh):
    lines = []
    started = False
    for line in fh.readlines():
        if line.startswith("-- +goose Down"):
            started = True
        elif started:
            lines.append(line.rstrip("\n"))
    return lines


def process_migrations(dsn, file_handler):
    lines = []
    d = os.path.join(os.path.dirname(__file__), "../../db/migrations/")
    for fname in sorted(os.listdir(d)):
        if fname.endswith(".sql"):
            lines.append("-- Migration File: {}".format(fname))
            with open(os.path.join(d, fname)) as fh:
                lines.extend(file_handler(fh))
    _execute_autocommit_query(dsn, "\n".join(lines))
