package ru.yandex.wmconsole.service;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;

import org.joda.time.DateTime;
import org.joda.time.Duration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.jdbc.core.simple.ParameterizedRowMapper;

import ru.yandex.wmconsole.data.VerificationStateEnum;
import ru.yandex.wmconsole.data.info.DNSVerificationInfo;
import ru.yandex.wmconsole.data.info.UsersHostsInfo;
import ru.yandex.wmconsole.data.partition.WMCPartition;
import ru.yandex.wmtools.common.error.InternalException;
import ru.yandex.wmtools.common.service.AbstractDbService;

/**
 * @author avhaliullin
 */
public class DNSVerificationsService extends AbstractDbService {
    private static final Logger log = LoggerFactory.getLogger(DNSVerificationsService.class);
    private static final String FIELD_NEXT_TRY = "next_try_at";
    private static final String FIELD_START_TIME = "requested_at";

    private static final String DELETE_DNS_VERIFICATION_QUERY =
            "DELETE FROM " +
                    "       tbl_dns_verifications " +
                    "   WHERE " +
                    "       host_id = ? " +
                    "   AND " +
                    "       user_id = ?";

    private static final String SELECT_DNS_VERIFICATION_INFO_QUERY =
            "SELECT " +
                    "       next_try_at AS " + FIELD_NEXT_TRY + ", " +
                    "       requested_at AS " + FIELD_START_TIME + " " +
                    "   FROM " +
                    "       tbl_dns_verifications " +
                    "   WHERE " +
                    "       host_id = ? " +
                    "   AND " +
                    "       user_id = ?";

    private static final String UPDATE_DNS_VERIFICATION_QUERY =
            "UPDATE " +
                    "       tbl_dns_verifications " +
                    "   SET " +
                    "       next_try_at = ? " +
                    "   WHERE " +
                    "       host_id = ? " +
                    "   AND " +
                    "       user_id = ? ";

    private static final String INSERT_DNS_VERIFICATION_QUERY =
            "INSERT INTO " +
                    "       tbl_dns_verifications (host_id, user_id, next_try_at, requested_at) " +
                    "   VALUES (?, ?, ?, ?) " +
                    "   ON DUPLICATE KEY UPDATE " +
                    "       next_try_at = VALUES(next_try_at), requested_at = VALUES(requested_at)";

    //For the first WCP ms we checking dns every FCT ms
    private Duration frequentCheckPeriod;
    private Duration frequentCheckTime;
    //Checking dns for the first DVPM ms every RCT ms. After we can say, that host isn't verified
    private Duration rareCheckTime;
    private Duration dnsVerificationPeriod;

    private final ParameterizedRowMapper<DNSVerificationInfo> DNS_INFO_MAPPER = new ParameterizedRowMapper<DNSVerificationInfo>() {
        @Override
        public DNSVerificationInfo mapRow(ResultSet resultSet, int i) throws SQLException {
            DateTime nextTryAt = new DateTime(resultSet.getTimestamp(FIELD_NEXT_TRY));
            DateTime requestedAt = new DateTime(resultSet.getTimestamp(FIELD_START_TIME));
            DateTime lastTryAt = getDNSPreviousTryAtTime(requestedAt, nextTryAt);
            return new DNSVerificationInfo(lastTryAt, requestedAt, nextTryAt);
        }
    };

    private DateTime getDNSNextTryAtTime(DateTime now, DateTime requestTime) {
        if (new Duration(requestTime, now).isShorterThan(frequentCheckPeriod)) {
            return now.plus(frequentCheckTime);
        } else {
            return now.plus(rareCheckTime);
        }
    }

    private DateTime getDNSPreviousTryAtTime(DateTime requestTime, DateTime nextTryTime) {
        if (new Duration(requestTime, nextTryTime).isShorterThan(frequentCheckPeriod.plus(frequentCheckTime))) {
            return nextTryTime.minus(frequentCheckTime);
        } else {
            return nextTryTime.minus(rareCheckTime);
        }
    }

    private DNSVerificationInfo getDNSVerificationInfo(UsersHostsInfo info) throws InternalException {
        List<DNSVerificationInfo> list = getJdbcTemplate(WMCPartition.nullPartition())
                .query(SELECT_DNS_VERIFICATION_INFO_QUERY, DNS_INFO_MAPPER, info.getHostId(), info.getUserId());
        if (list == null || list.isEmpty()) {
            return null;
        }
        return list.get(0);
    }

    public void removeFromDNSVerificationsTable(long hostId, long userId) throws InternalException {
        getJdbcTemplate(WMCPartition.nullPartition()).update(
                DELETE_DNS_VERIFICATION_QUERY, hostId, userId);
    }

    public UsersHostsInfo handleDNSVerificationResult(UsersHostsInfo info, boolean reset) throws InternalException {
        if (VerificationStateEnum.VERIFIED.equals(info.getVerificationState())) {
            //Verified - so ok.
            log.debug("Host verified: user_host_id={}", info.getHostId());
            removeFromDNSVerificationsTable(info.getHostId(), info.getUserId());
            return info;
        } else {
            DNSVerificationInfo dnsInfo = getDNSVerificationInfo(info);

            DateTime requestedAt;
            DateTime now = DateTime.now();
            if (reset) {
                requestedAt = now;
            } else {
                requestedAt = dnsInfo.getRequestedAt();
            }

            if (now.isAfter(requestedAt.plus(dnsVerificationPeriod))) {
                //Time is up.
                log.debug("Host wasn't verified: user_host_id={}", info.getHostId());
                removeFromDNSVerificationsTable(info.getHostId(), info.getUserId());
                return info;
            }

            //Not verified, but will try again.
            DateTime nextTryAt = getDNSNextTryAtTime(now, requestedAt);
            if (reset) {
                getJdbcTemplate(WMCPartition.nullPartition()).update(
                        INSERT_DNS_VERIFICATION_QUERY, info.getHostId(), info.getUserId(), nextTryAt.toDate(), requestedAt.toDate());
            } else {
                getJdbcTemplate(WMCPartition.nullPartition()).update(
                        UPDATE_DNS_VERIFICATION_QUERY, nextTryAt.toDate(), info.getHostId(), info.getUserId());
            }
            log.debug("Verification failed, will try again. Cause: " + info.getVerificationState().name());
            return UsersHostsInfo.createWaitingDNS(info);
        }
    }

    @Required
    public void setFrequentCheckPeriodMinutes(int minutes) {
        this.frequentCheckPeriod = Duration.standardMinutes(minutes);
    }

    @Required
    public void setFrequentCheckTimeMinutes(int minutes) {
        this.frequentCheckTime = Duration.standardMinutes(minutes);
    }

    @Required
    public void setDnsVerificationPeriodHours(int hours) {
        this.dnsVerificationPeriod = Duration.standardHours(hours);
    }

    @Required
    public void setRareCheckTimeMinutes(int minutes) {
        this.rareCheckTime = Duration.standardMinutes(minutes);
    }
}
