package ru.yandex.wmconsole.servantlet.internal;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Required;

import ru.yandex.common.framework.core.ServRequest;
import ru.yandex.common.framework.core.ServResponse;
import ru.yandex.common.util.collections.Pair;
import ru.yandex.common.util.collections.Triple;
import ru.yandex.wmconsole.service.ConsistentMainMirrorService;
import ru.yandex.wmconsole.service.VerifyViewerService;
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.servantlet.AbstractServantlet;
import ru.yandex.wmtools.common.util.XmlDataWrapper;

/**
 * Checks if specified host is verified by specified user.
 *
 * @author ailyin
 */
public class CheckVerificationServantlet extends AbstractServantlet {
    private static final Logger log = LoggerFactory.getLogger(CheckVerificationServantlet.class);

    private static final String PARAM_LOGIN = "uid";
    private static final String PARAM_HOSTNAME = "hostname";
    private static final String PARAM_MAIN_MIRROR = "use_main_mirror";

    private static final String PREFIX_UID = "uid_";

    private VerifyViewerService verifyService;
    private ConsistentMainMirrorService consistentMainMirrorService;

    private void checkSingleVerification(long userId, String hostname, boolean useMainMirror, ServResponse res) throws InternalException {
        if (useMainMirror) {
            hostname = consistentMainMirrorService.getMainMirror(hostname);
        }

        res.addData(new SimpleWrapper<Boolean>(verifyService.isHostVerifiedByUser(hostname, userId), "verified"));
    }

    private long getUid(String s) {
        return Long.parseLong(s.substring(PREFIX_UID.length()));
    }

    private void checkVerifications(ServRequest req, ServResponse res, boolean useMainMirror) throws InternalException {
        List<Pair<String, String>> params = req.getAllParams();

        List<Long> uids = new ArrayList<Long>();
        List<String> hostNames = new ArrayList<String>();

        for (Pair<String, String> param : params) {
            if (param.getFirst().startsWith(PREFIX_UID)) {
                long uid;
                try {
                    uid = getUid(param.getFirst());
                } catch (NumberFormatException e) {
                    log.warn("Bad param name: " + param.getFirst());
                    continue;
                }
                hostNames.add(param.getSecond());
                uids.add(uid);
            }
        }

        final List<String> hostNamesForCheck;
        if (useMainMirror) {
            hostNamesForCheck = new ArrayList<>(hostNames.size());
            for (String hostName : hostNames) {
                hostNamesForCheck.add(consistentMainMirrorService.getMainMirror(hostName));
            }
        } else {
            hostNamesForCheck = hostNames;
        }


        List<Pair<Long, String>> verifications = new ArrayList<Pair<Long, String>>();
        for (int i = 0; i < hostNamesForCheck.size(); i++) {
            verifications.add(new Pair<Long, String>(uids.get(i), hostNamesForCheck.get(i)));
        }

        Collection<Pair<Long, String>> verifiedList = verifyService.checkVerificationForUsersAndHosts(verifications);
        Set<UserHostPair> verifiedCaseInsensitiveSet = new HashSet<UserHostPair>();
        for (Pair<Long, String> pair : verifiedList) {
            verifiedCaseInsensitiveSet.add(new UserHostPair(pair));
        }
        log.debug("To verify list size: " + verifications.size());
        log.debug("Verified: " + verifiedList.size());

        for (int i = 0; i < verifications.size(); i++) {
            if (verifiedCaseInsensitiveSet.contains(new UserHostPair(verifications.get(i)))) {
                res.addData(new VerificationWrapper(new Triple<Long, String, Boolean>(uids.get(i), hostNames.get(i), true)));
            } else {
                res.addData(new VerificationWrapper(new Triple<Long, String, Boolean>(uids.get(i), hostNames.get(i), false)));
            }
        }
    }

    @Override
    protected void doProcess(ServRequest req, ServResponse res) throws InternalException, UserException {
        log.debug("CheckVerification: Started");
        checkService(verifyService, VerifyViewerService.class);

        Long userId = getLongParam(req, PARAM_LOGIN);
        String hostname = getStringParam(req, PARAM_HOSTNAME);
        Boolean useMainMirror = getBooleanParam(req, PARAM_MAIN_MIRROR);

        if (userId != null && hostname != null) {
            checkSingleVerification(userId, hostname, (useMainMirror != null) && (useMainMirror), res);
        } else {
            checkVerifications(req, res, (useMainMirror != null) && (useMainMirror));
        }

        log.debug("CheckVerification: Finished");
    }

    private static class VerificationWrapper extends XmlDataWrapper<Triple<Long, String, Boolean>> {
        public VerificationWrapper(Triple<Long, String, Boolean> data) {
            super(data);
        }

        @Override
        protected void doToXml(StringBuilder result) {
            result.append("<verification uid='")
                    .append(data.first)
                    .append("' host-name='")
                    .append(data.second)
                    .append("' >")
                    .append(data.third.toString())
                    .append("</verification>");
        }
    }

    private static class UserHostPair {
        private Long userId;
        private String hostName;

        public UserHostPair(Long userId, String hostName) {
            this.userId = userId;
            this.hostName = hostName;
        }

        public UserHostPair(Pair<Long, String> pair) {
            this(pair.first, pair.second);
        }

        public Long getUserId() {
            return userId;
        }

        public String getHostName() {
            return hostName;
        }

        public int hashCode() {
            return (int) (userId % Integer.MAX_VALUE + hostName.toLowerCase().hashCode());
        }

        public boolean equals(Object o) {
            return o instanceof UserHostPair && hostName.equalsIgnoreCase(((UserHostPair) o).getHostName()) && userId.equals(((UserHostPair) o).getUserId());
        }
    }

    @Required
    public void setVerifyService(VerifyViewerService verifyService) {
        this.verifyService = verifyService;
    }

    @Required
    public void setConsistentMainMirrorService(ConsistentMainMirrorService consistentMainMirrorService) {
        this.consistentMainMirrorService = consistentMainMirrorService;
    }
}
