package ru.yandex.wmconsole.servantlet;

import java.net.URL;
import java.util.List;
import java.util.regex.Pattern;

import org.apache.commons.lang.StringUtils;
import org.apache.tomcat.jdbc.pool.DataSource;
import org.apache.tomcat.jdbc.pool.PoolConfiguration;
import org.springframework.beans.factory.annotation.Required;

import ru.yandex.common.framework.core.ServRequest;
import ru.yandex.common.framework.core.ServResponse;
import ru.yandex.webmaster.common.util.xml.SimpleXmlBuilder;
import ru.yandex.wmconsole.data.info.BriefHostInfo;
import ru.yandex.wmconsole.data.info.HostDbHostInfo;
import ru.yandex.wmconsole.data.partition.WMCPartition;
import ru.yandex.wmconsole.data.wrappers.NoDataWrapper;
import ru.yandex.wmconsole.data.wrappers.StringWrapper;
import ru.yandex.wmconsole.service.HostDbHostInfoService;
import ru.yandex.wmtools.common.SupportedProtocols;
import ru.yandex.wmtools.common.data.wrappers.SimpleWrapper;
import ru.yandex.wmtools.common.error.InternalException;
import ru.yandex.wmtools.common.error.UserException;
import ru.yandex.wmtools.common.util.XmlDataWrapper;

/**
 * Outputs database info about a given host.
 *
 * @author ailyin
 */
public class DatabaseHelperServantlet extends WMCAuthenticationServantlet {
    private static final String PARAM_HOSTNAME = "host";

    private static final String TAG_HOST_ID = "host-id";
    private static final String TAG_HOST_DB_HOST_ID = "host-db-host-id";

    private static final String USERDB_NAME_PASSWORD = "user_ro_dev";
    private static final String HOSTDB_NAME_PASSWORD = "host_ro_dev";

    private static final Pattern ALL_DIGITS = Pattern.compile("[0-9]+");

    private HostDbHostInfoService hostDbHostInfoService;

    private List<DataSource> dataSource;
    private List<DataSource> readDataSource;

    @Override
    protected void doProcess(ServRequest req, ServResponse res, long userId) throws UserException, InternalException {
        // common info

        BriefHostInfo hostInfo;
        String hostnameStr = StringUtils.trimToEmpty(getRequiredStringParam(req, PARAM_HOSTNAME));
        boolean hostIsHostId = false;
        String hostname = null;
        if (ALL_DIGITS.matcher(hostnameStr).matches()) {
            hostIsHostId = true;
            hostInfo = getHostInfoService().getBriefHostInfoByIdOrName(hostnameStr);
        } else {
            URL url = prepareUrl(hostnameStr, true);
            hostname = SupportedProtocols.getCanonicalHostname(url);
            hostInfo = getHostInfoService().getBriefHostInfoByIdOrName(hostname);
        }

        // user db info
        if (hostInfo == null) {
            res.addData(new NoDataWrapper(TAG_HOST_ID));
        } else {
            res.addData(new StringWrapper(hostInfo.getName(), "hostname"));
            res.addData(new SimpleWrapper<>(hostInfo.getId(), TAG_HOST_ID));
            res.addData(new SimpleWrapper<>(hostInfo.getId() % 512, "user-partition"));
        }
        res.addData(new StringWrapper(getConnectionCommand(getUserDs(dataSource), USERDB_NAME_PASSWORD),
                "user-db-command"));

        // host db info
        if (!hostIsHostId && hostname != null) {
            int dbIndex = WMCPartition.getStringHash(hostname, 8);
            res.addData(new SimpleWrapper<>(dbIndex, "db-index"));
            HostDbHostInfo hostDbHostInfo = hostDbHostInfoService.getHostDbHostInfo(hostname, true);
            if (hostDbHostInfo == null) {
                res.addData(new NoDataWrapper(TAG_HOST_DB_HOST_ID));
            } else {
                res.addData(new SimpleWrapper<>(hostDbHostInfo.getHostDbHostId(), TAG_HOST_DB_HOST_ID));
                res.addData(new SimpleWrapper<>(hostDbHostInfo.getHostDbHostId() % 512, "host-partition"));
            }
            res.addData(new StringWrapper(getConnectionCommand(getShardDs(dataSource, dbIndex),
                    HOSTDB_NAME_PASSWORD), "host-db-command"));
        }

        for (int i = 0; i < dataSource.size(); i++) {
            DbInfo rwDs = getDbInfo(dataSource.get(i));
            DbInfo roDs = getDbInfo(readDataSource.get(i));
            res.addData(new DatabaseXmlWrapper(i, rwDs, roDs));
        }
    }

    private static String getConnectionCommand(DataSource ds, String userPassword) {
        DbInfo dbInfo = getDbInfo(ds);
        if (dbInfo == null) {
            return "UNKNOWN";
        }

        String result;
        if (dbInfo.port != null) {
            result = String.format("mysql -h %s -P %s -A -u %s -p%s %s", dbInfo.host, dbInfo.port, userPassword, userPassword, dbInfo.dbName);
        } else {
            result = String.format("mysql -h %s -A -u %s -p%s %s", dbInfo.host, userPassword, userPassword, dbInfo.dbName);
        }
        return result;
    }

    private static DbInfo getDbInfo(DataSource ds) {
        PoolConfiguration poolProperties = ds.getPoolProperties();
        String url = poolProperties.getUrl();
        int hostStart = url.indexOf("//");
        if (hostStart <= 0) {
            return null;
        }
        int hostEnd = url.indexOf('?', hostStart + 2);
        if (hostEnd <= 0 ){
            hostEnd = url.length();
        }
        String hostPortDb = url.substring(hostStart + 2, hostEnd);
        int portStart = hostPortDb.indexOf(':');
        int portEnd = hostPortDb.indexOf('/');
        if (portEnd < 0) {
            return null;
        }
        String host;
        String port = null;
        if (portStart > 0) {
            host = hostPortDb.substring(0, portStart);
            port = hostPortDb.substring(portStart + 1, portEnd);
        } else {
            host = hostPortDb.substring(0, portEnd);
        }
        String dbName = hostPortDb.substring(portEnd + 1);
        return new DbInfo(host, port, dbName);
    }

    private static DataSource getUserDs(List<DataSource> dataSources) {
        int size = dataSources.size();
        return dataSources.get(size - 1);
    }

    private static DataSource getShardDs(List<DataSource> dataSource, int shard) {
        return dataSource.get(shard);
    }

    private static class DbInfo {
        private final String host;
        private final String port;
        private final String dbName;

        private DbInfo(String host, String port, String dbName) {
            this.host = host;
            this.port = port;
            this.dbName = dbName;
        }
    }

    private static class DatabaseXmlWrapper extends XmlDataWrapper<DbInfo> {
        private final int index;
        private final DbInfo rwDs;
        private final DbInfo roDs;

        public DatabaseXmlWrapper(int index, DbInfo rwDs, DbInfo roDs) {
            super(rwDs);
            this.index = index;
            this.rwDs = rwDs;
            this.roDs = roDs;
        }

        @Override
        protected void doToXml(StringBuilder result) {
            SimpleXmlBuilder xmlBuilder = new SimpleXmlBuilder(result);
            xmlBuilder.open("database");
            xmlBuilder.element("index", index);
            xmlBuilder.element("name", rwDs.dbName);
            xmlBuilder.element("master-server", rwDs.host + ":" + rwDs.port);
            xmlBuilder.element("slave-server", roDs.host + ":" + roDs.port);
            xmlBuilder.close();

            xmlBuilder.flush();
        }
    }

    @Required
    public void setHostDbHostInfoService(HostDbHostInfoService hostDbHostInfoService) {
        this.hostDbHostInfoService = hostDbHostInfoService;
    }

    @Required
    public void setDataSource(List<DataSource> dataSource) {
        this.dataSource = dataSource;
    }

    @Required
    public void setReadDataSource(List<DataSource> readDataSource) {
        this.readDataSource = readDataSource;
    }
}
