package ru.yandex.autotests.direct.db.steps.base;

import org.apache.commons.lang3.exception.ExceptionUtils;
import org.joda.time.DateTime;
import org.jooq.Configuration;
import org.jooq.DSLContext;
import org.jooq.SQLDialect;
import org.jooq.conf.RenderKeywordStyle;
import org.jooq.conf.Settings;
import org.jooq.impl.DSL;
import org.jooq.impl.DefaultConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ru.yandex.autotests.direct.db.steps.AutoIncSteps;
import ru.yandex.autotests.direct.db.steps.ShardingSteps;
import ru.yandex.autotests.direct.utils.BaseSteps;
import ru.yandex.qatools.allure.annotations.Step;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;

/*
* todo javadoc
*/
public abstract class BaseDbSteps extends BaseSteps<DBStepsContext> {
    private Logger log = LoggerFactory.getLogger(this.getClass());
    private Logger execLog = LoggerFactory.getLogger("exec");

    static {
        System.setProperty("org.jooq.no-logo", "true");
    }

    private DSLContext dslContext(Connection connection) {
        Settings settings = new Settings();
        settings.setRenderKeywordStyle(RenderKeywordStyle.UPPER);

        Configuration conf = new DefaultConfiguration();
        conf.set(settings);
        conf.set(connection);
        conf.set(SQLDialect.MYSQL);

        return DSL.using(conf);
    }


    protected Connection initConnection() {
        String url = getUrl();
        try {
            return DriverManager.getConnection(url, getProps());
        } catch (SQLException e) {
            throw new DirectDbStepsException("Cannot open connection to db " + url, e);
        }
    }

    protected abstract String getUrl();

    private Properties getProps() {
        Properties props = new Properties();
        props.put("user", getContext().getUser());
        props.put("useUnicode", "true");
        props.put("characterEncoding", "utf8");
        props.put("password", getContext().getPassword());
        props.put("zeroDateTimeBehavior", "convertToNull");
        props.put("serverTimezone", "Europe/Moscow");
        props.put("useSSL", "false");
        String hostAddress = "";
        try {
            InetAddress localhost = InetAddress.getLocalHost();
            hostAddress = localhost.getHostAddress();
        } catch (UnknownHostException e) {
            log().warn(ExceptionUtils.getStackTrace(e));
        }
        props.put("connectionAttributes",
                "program_name:db_steps" + "," +
                        "start_time:" + DateTime.now().toString().replace(":", ".") + "," +
                        "class_name:" + this.getClass().getName() + "," +
                        "host_ip:" + hostAddress);
        return props;
    }

    protected Logger log() {
        return log;
    }

    protected <T> T exec(StepCallable<T> callable) {
        return exec("exec", callable);
    }

    @Step("DB: {0}")
    protected <T> T exec(String stepName, StepCallable<T> callable) {
        return silentExecution(callable);
    }

    protected <T> T silentExecution(StepCallable<T> callable) {
        execLog.info("Start execution");
        try (Connection conn = initConnection()) {
            return callable.run(dslContext(conn));
        } catch (SQLException e) {
            throw new DirectDbStepsException("Ошибка SQL", e);
        } catch (DirectDbStepsException e) {
            throw e;
        } catch (Exception e) {
            throw new DirectDbStepsException("Ошибка выполнения операции", e);
        } finally {
            execLog.info("Stop execution");
        }
    }

    protected void run(StepRunnable callable) {
        run("run", callable);
    }

    @Step("DB: {0}")
    protected void run(String stepName, StepRunnable callable) {
        silentRun(callable);
    }

    protected void silentRun(StepRunnable callable) {
        silentExecution(dslContext -> {
            callable.run(dslContext);
            return null;
        });
    }

    protected ShardingSteps shardingSteps() {
        return BaseSteps.getInstance(ShardingSteps.class, getContext());
    }

    protected AutoIncSteps autoIncSteps() {
        return BaseSteps.getInstance(AutoIncSteps.class, getContext());
    }
}
