/*
 * Copyright (C) 2016-2022 ActionTech.
 * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher.
 */

package com.actiontech.dble.config.helper;

import com.actiontech.dble.backend.datasource.PhysicalDbGroup;
import com.actiontech.dble.backend.datasource.PhysicalDbInstance;
import com.actiontech.dble.sqlengine.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.*;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class ShowDatabaseHandler {
    private static final Logger LOGGER = LoggerFactory.getLogger(ShowDatabaseHandler.class);
    private final ReentrantLock lock = new ReentrantLock();
    private Map<String, PhysicalDbGroup> dbGroups;
    private Set<String> databases = new HashSet<>();
    private final Condition finishCond = lock.newCondition();
    private boolean isFinish = false;
    private String showDatabases = "show databases";
    private String showDataBasesCols;
    private SQLJob sqlJob;


    public ShowDatabaseHandler(Map<String, PhysicalDbGroup> dbGroups, String showDataBasesCols) {
        this.dbGroups = dbGroups;
        this.showDataBasesCols = showDataBasesCols;
    }

    private void reset() {
        isFinish = false;
        databases.clear();
    }

    public Set<String> execute(String dbGroupName) {
        reset();
        MultiRowSQLQueryResultHandler resultHandler = new MultiRowSQLQueryResultHandler(new String[]{showDataBasesCols}, new ShowDatabasesListener(showDataBasesCols));
        PhysicalDbInstance ds = getPhysicalDbInstance(dbGroupName);
        if (ds != null) {
            sqlJob = new SQLJob(showDatabases, null, resultHandler, ds);
            sqlJob.run();
            waitDone();
        } else {
            LOGGER.warn("No dbInstance is alive, server can not get 'show databases' result");
        }
        return new HashSet<>(databases);
    }


    // for dryrun
    public Set<String> execute(PhysicalDbInstance ds) {
        reset();
        MultiRowSQLQueryResultHandler resultHandler = new MultiRowSQLQueryResultHandler(new String[]{showDataBasesCols}, new ShowDatabasesListener(showDataBasesCols));
        if (ds != null) {
            sqlJob = new OneTimeConnJob(showDatabases, null, resultHandler, ds);
            sqlJob.run();
            waitDone();
        } else {
            LOGGER.warn("No dbInstance is alive, server can not get 'show databases' result");
        }
        return new HashSet<>(databases);
    }

    private PhysicalDbInstance getPhysicalDbInstance(String dbGroupName) {
        PhysicalDbInstance ds = null;
        try {
            PhysicalDbGroup dbGroup = dbGroups.get(dbGroupName);
            if (dbGroup != null) {
                ds = dbGroup.rwSelect(null, false);
            }
        } catch (IOException e) {
            LOGGER.warn("select dbInstance error", e);
        }
        return ds;
    }

    public List<PhysicalDbInstance> getPhysicalDbInstances() {
        List<PhysicalDbInstance> physicalDbInstanceList = new ArrayList<>();
        for (PhysicalDbGroup dbGroup : dbGroups.values()) {
            for (PhysicalDbInstance dsTest : dbGroup.getDbInstances(false)) {
                if (dsTest.isTestConnSuccess()) {
                    physicalDbInstanceList.add(dsTest);
                }
            }
        }
        return physicalDbInstanceList;
    }

    private void waitDone() {
        lock.lock();
        try {
            while (!isFinish) {
                finishCond.await();
            }
        } catch (InterruptedException e) {
            LOGGER.info("[MysqlDatabaseHandler] conn Interrupted: " + e);
            if (sqlJob != null) {
                sqlJob.terminate("thread interrupted");
            }
            throw new IllegalStateException(e);
        } finally {
            lock.unlock();
        }
    }

    private class ShowDatabasesListener implements SQLQueryResultListener<SQLQueryResult<List<Map<String, String>>>> {
        private String showDataBasesCol;

        ShowDatabasesListener(String clickhouseShowDataBasesCol) {
            this.showDataBasesCol = clickhouseShowDataBasesCol;
        }

        @Override
        public void onResult(SQLQueryResult<List<Map<String, String>>> result) {
            if (result.isSuccess()) {
                List<Map<String, String>> rows = result.getResult();
                for (Map<String, String> row : rows) {
                    String databaseName = row.get(showDataBasesCol);
                    databases.add(databaseName);
                }
            }
            handleFinished();
        }

        private void handleFinished() {
            lock.lock();
            try {
                isFinish = true;
                finishCond.signal();
            } finally {
                lock.unlock();
            }
        }
    }

}



