package ru.yandex.wmconsole.verification;

import java.io.IOException;
import java.net.IDN;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

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

import ru.yandex.common.util.collections.CollectionFactory;
import ru.yandex.wmconsole.data.VerificationStateEnum;
import ru.yandex.wmconsole.data.info.UsersHostsInfo;
import ru.yandex.wmconsole.service.ConsistentMainMirrorService;
import ru.yandex.wmconsole.util.WwwUtil;
import ru.yandex.wmconsole.verification.whois.WhoisRecordHandler;
import ru.yandex.wmconsole.verification.whois.WhoisRecordReader;
import ru.yandex.wmconsole.verification.whois.handlers.ContactInfoWhoisHandler;
import ru.yandex.wmconsole.verification.whois.handlers.SimpleWhoisHandler;
import ru.yandex.wmtools.common.SupportedProtocols;
import ru.yandex.wmtools.common.data.info.EmailInfo;
import ru.yandex.wmtools.common.error.InternalException;
import ru.yandex.wmtools.common.service.ValidatorService;

/**
 * @author avhaliullin
 */
public class WhoIsVerifier extends ConsistentVerifier {
    private static final Logger log = LoggerFactory.getLogger(WhoIsVerifier.class);

    public static final long WAITING_TIME = 2100;
    private static final String WWW_PREFIX = "www.";

    private final Set<String> UNSUPPORTED_DOMAINS = CollectionFactory.set("ru", IDN.toASCII("рф"), "by");

    private ValidatorService validatorService;
    private ConsistentMainMirrorService consistentMainMirrorService;

    private long lastVerificationTimeMillis = 0;

    private long waitTimeMillis = WAITING_TIME;

    private boolean handleEmail(String address, Map<String, EmailInfo> userEmails, List<EmailInfo> matchedEmails) {
        log.debug("Found in whois record: " + address);
        EmailInfo info = userEmails.get(address);
        if (info != null) {
            matchedEmails.add(info);
            if (info.isValid()) {
                return true;
            }
        }
        return false;
    }

    private List<WhoisRecordHandler> getHandlers() {
        List<WhoisRecordHandler> handlers = new ArrayList<WhoisRecordHandler>();
        handlers.add(new SimpleWhoisHandler());
        handlers.add(new ContactInfoWhoisHandler());
        return handlers;
    }

    private List<EmailInfo> checkWhois(WhoisRecordReader reader, Map<String, EmailInfo> userEmails) throws IOException {
        List<EmailInfo> matchedEmails = new ArrayList<EmailInfo>();
        log.info("Reading whois record");
        String line;

        List<WhoisRecordHandler> handlers = getHandlers();

        while ((line = reader.readLine()) != null) {
            for (WhoisRecordHandler handler : handlers) {
                String foundEmail = handler.nextLine(line);
                if (foundEmail != null && handleEmail(foundEmail.toLowerCase(), userEmails, matchedEmails)) {
                    reader.close();
                    return matchedEmails;
                }
            }
        }
        reader.close();

        log.info("Valid emails wasn't found in whois record");

        return matchedEmails;
    }

    private String getHostNameToAsk(String hostName) {
        int subDomainLevel = hostName.split("\\.").length;
        if (subDomainLevel > 3) {
            return null;
        }
        if (subDomainLevel == 3) {
            try {
                if (WwwUtil.isWWW(hostName) && consistentMainMirrorService.getMainMirror(WwwUtil.switchWWW(hostName)).equalsIgnoreCase(hostName)) {
                    return WwwUtil.switchWWW(hostName);
                } else {
                    return null;
                }
            } catch (InternalException e) {
                log.warn("Can't get main mirror for " + hostName, e);
                return null;
            }
        }
        return hostName;
    }

    public boolean applicable(String hostName) {
        String[] subdomains = hostName.split("\\.");
        if (subdomains.length > 0) {
            String domain = subdomains[subdomains.length - 1];
            if (UNSUPPORTED_DOMAINS.contains(domain)) {
                return false;
            }
        }
        return getHostNameToAsk(hostName) != null;
    }

    @Override
    public UsersHostsInfo verify(UsersHostsInfo info) {
        log.debug("Trying to verify whois");
        URL url;
        try {
            url = SupportedProtocols.getURL(info.getHostName());
        } catch (Exception e) {
            log.error("This should never happen", e);
            return info;
        }
        String host = url.getHost();

        host = getHostNameToAsk(host);
        if (host == null) {
            return UsersHostsInfo.createNotVerifiedNow(info, VerificationStateEnum.WHOIS_EMAIL_NOT_FOUND, "Whois verification is not applicable");
        }

        long time;
        while ((time = waitTimeMillis - (System.currentTimeMillis() - lastVerificationTimeMillis)) > 0) {
            log.info("Sleep for " + time + " millis");
            try {
                Thread.sleep(time);
            } catch (InterruptedException e) {
                log.info("Somebody woke me up");
            }
        }

        List<EmailInfo> matched;

        try {
            Map<String, EmailInfo> userEmails = new HashMap<String, EmailInfo>();
            List<EmailInfo> userEmailsList = null;
            try {
                userEmailsList = validatorService.getEmails(info.getUserId());
            } catch (InternalException e) {
                log.error("Failed to get emails", e);
                return UsersHostsInfo.createNotVerifiedNow(info, VerificationStateEnum.WHOIS_EMAIL_NOT_FOUND, "Failed to get emails");
            }
            for (EmailInfo emailInfo : userEmailsList) {
                userEmails.put(emailInfo.getAddress().toLowerCase(), emailInfo);
            }
            matched = checkWhois(new WhoisRecordReader(host), userEmails);
        } catch (IOException e) {
            String error = "Can't connect to whois server: " + e.getMessage();
            log.error(error);
            return UsersHostsInfo.createNotVerifiedNow(info, VerificationStateEnum.WHOIS_SERVER_CONNECTION_EXCEPTION, error);
        } finally {
            lastVerificationTimeMillis = System.currentTimeMillis();
        }

        //No matches found
        if (matched.size() == 0) {
            return UsersHostsInfo.createNotVerifiedNow(info, VerificationStateEnum.WHOIS_EMAIL_NOT_FOUND);
        }

        //Match found, everything is ok
        if (matched.get(matched.size() - 1).isValid()) {
            return UsersHostsInfo.createVerifiedNow(info, VerificationTypeEnum.WHOIS);
        }

        //Match found, but email isn't valid
        return UsersHostsInfo.createNotVerifiedNow(info, VerificationStateEnum.WHOIS_UNCONFIRMED_EMAIL);
    }


    @Required
    public void setValidatorService(ValidatorService validatorService) {
        this.validatorService = validatorService;
    }

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

    @Required
    public void setWaitTimeMillis(long waitTimeMillis) {
        this.waitTimeMillis = waitTimeMillis;
    }
}
