package ru.yandex.wmconsole.servantlet.api;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.List;

import org.apache.commons.lang.StringUtils;
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.webmaster.common.util.xml.SimpleXmlBuilder;
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;

/**
 * @author senin
 */
public class VerificationCodeServantlet extends AbstractServantlet {
    private static final Logger log = LoggerFactory.getLogger(VerificationCodeServantlet.class);

    public static final String PARAM_CODE = "code";
    public static final String PARAM_ERROR = "error";
    public static final String PARAM_STATE = "state";
    public static final String TAG_STATUS = "status";
    public static final String TAG_CODE = "code";
    public static final String TAG_ERROR = "error";

    private List<String> acceptedReferers;

    enum GetTokenErrorEnum  {
        WRONG_PROTOCOL,
        WRONG_REFERER,
        INCORRECT_URL,
        PROCESSING_ERROR,
        QUERY_NOT_EMPTY,
        ACCESS_DENIED_BY_USER
    }

    /**
     * If state param missed or empty just show ok status and code param
     * If state param contains correct URL, append code param and make http request. in case of 200 show ok status
     * In other cases show error message and code param
     * XML:
     * <data>
     *     <status>OK|ERROR</status>
     *     [<code>code</code>]
     *     [<error>WRONG_PROTOCOL|INCORRECT_URL|PROCESSING_ERROR|QUERY_NOT_EMPTY</error>]
     * </data>
     * @param req request
     * @param res response
     * @throws UserException
     * @throws InternalException
     */
    @Override
    protected void doProcess(ServRequest req, ServResponse res) throws UserException, InternalException {
        String state = getStringParam(req, PARAM_STATE, "");
        String error = getStringParam(req, PARAM_ERROR, "");

        GetTokenErrorEnum errorState = null;
        String code = null;
        String locationUrl = null;

        String referer = req.getReferer();

        if (!"".equals(error)) {
            errorState = GetTokenErrorEnum.ACCESS_DENIED_BY_USER;
        } else if (StringUtils.isEmpty(referer)) {
            errorState = GetTokenErrorEnum.WRONG_REFERER;
        } else {
            try {
                URL refererUrl = new URL(referer);
                String refererHost = refererUrl.getHost();
                if (!acceptedReferers.contains(refererHost)) {
                    log.error("Wrong referer: " + referer);
                    errorState = GetTokenErrorEnum.WRONG_REFERER;
                }
            } catch (MalformedURLException e) {
                log.error("Wrong referer: " + referer);
                errorState = GetTokenErrorEnum.WRONG_REFERER;
            }
        }

        if (errorState != null) {
            res.addData(new VerificationResponse(errorState, null, null));
            return;
        }

        code = getRequiredStringParam(req, PARAM_CODE);
        if (!StringUtils.isEmpty(state)) {
            try {
                URL stateUrl = new URL(state);
                if (!"https".equals(stateUrl.getProtocol())) {
                    errorState = GetTokenErrorEnum.WRONG_PROTOCOL;
                } else if (stateUrl.getQuery() != null) {
                    errorState = GetTokenErrorEnum.QUERY_NOT_EMPTY;
                } else {
                    URL url = new URL(state + "?code=" + code);
                    log.debug("URL: " + url.toString());
                    locationUrl = url.toString();
                }
            } catch (MalformedURLException e) {
                log.debug(e.getMessage());
                errorState = GetTokenErrorEnum.INCORRECT_URL;
            }
        }
        res.addData(new VerificationResponse(errorState, code, locationUrl));
    }

    private static class VerificationResponse extends XmlDataWrapper<Void> {
        private final GetTokenErrorEnum errorState;
        private final String code;
        private final String url;

        public VerificationResponse(GetTokenErrorEnum errorState, String code, String url) {
            super(null);
            this.errorState = errorState;
            this.code = code;
            this.url = url;
        }

        @Override
        protected void doToXml(StringBuilder result) {
            SimpleXmlBuilder xml = new SimpleXmlBuilder(result);
            xml.open("verification-result");
            if (errorState == null) {
                xml.element(TAG_STATUS, "OK");
                xml.element(TAG_CODE, code);
                if (url != null) {
                    xml.element("location", url);
                }
            } else {
                xml.element(TAG_STATUS, "ERROR");
                xml.element(TAG_ERROR, errorState);
            }
            xml.close();
        }
    }

    @Required
    public void setAcceptedReferers(List<String> acceptedReferers) {
        this.acceptedReferers = acceptedReferers;
    }
}
