package ru.yandex.autotests.directapi.apiclient.version4;

import java.lang.reflect.Method;
import java.lang.reflect.UndeclaredThrowableException;
import java.net.URL;
import java.util.Map;

import javax.xml.soap.SOAPException;

import com.google.common.io.Resources;
import org.apache.axis.AxisFault;
import org.apache.axis.AxisProperties;
import org.apache.axis.EngineConfiguration;
import org.apache.axis.client.Stub;
import org.apache.axis.configuration.XMLStringProvider;
import org.apache.axis.message.SOAPHeaderElement;
import org.apache.commons.io.Charsets;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import ru.yandex.autotests.direct.utils.ReflectionUtils;
import ru.yandex.autotests.directapi.apiclient.RequestHeader;
import ru.yandex.autotests.directapi.apiclient.config.AuthType;
import ru.yandex.autotests.directapi.apiclient.config.ConnectionConfig;
import ru.yandex.autotests.directapi.apiclient.config.MyCustomSSLSocketFactory;
import ru.yandex.autotests.directapi.apiclient.config.ProtocolType;
import ru.yandex.autotests.directapi.apiclient.config.SSLUtilities;
import ru.yandex.autotests.directapi.apiclient.errors.AxisError;
import ru.yandex.autotests.directapi.exceptions.DirectAPIException;
import ru.yandex.autotests.directapi.model.User;
import ru.yandex.autotests.irt.testutils.json.JsonUtils;

/**
 * Created with IntelliJ IDEA.
 * User: mariabye
 * Date: 14.05.13
 * Time: 10:44
 * Soap implementation of Direct.API connectionConfig client
 * Usage:
 * new SoapClient() - set default client based on project properties
 * new SoapClient()
 * .version(4)
 * .wsdl(APIPort_PortType.class)
 * .auth(account)
 * custom properties for client
 */
public class SoapClient extends BaseApiV4Client {
    static {
        SSLUtilities.trustAllHostnames();
        SSLUtilities.trustAllHttpsCertificates();
    }
    public static Log log = LogFactory.getLog(SoapClient.class);

    private Stub stub;
    private final static EngineConfiguration axisConfig = readConfig();

    public <T> T getStub() {
        initStub();
        initHeader();
        return (T) stub;
    }

    public SoapClient clone() {
        return new SoapClient(connectionConfig.clone(), requestHeader.clone());
    }

    public SoapClient(ConnectionConfig connectionConfig) {
        super(connectionConfig);
    }

    public SoapClient(ConnectionConfig connectionConfig, RequestHeader requestHeader) {
        super(connectionConfig, requestHeader);
    }


    @Override
    protected ProtocolType getProtocolType() {
        return ProtocolType.SOAP;
    }

    private void initStub() {
        try {
            Object yandexServiceLocator;
            String apiURL = connectionConfig.getEndPoint(getProtocolType());
            URL url = new URL(apiURL);
            Class locatorClass = Class.forName(getPackageName() + ".YandexAPIServiceLocator");
            yandexServiceLocator = locatorClass.getConstructor(EngineConfiguration.class).newInstance(axisConfig);
            stub = (Stub) ReflectionUtils.invokeMethod(yandexServiceLocator, "getAPIPort", url);
        } catch (Exception e) {
            log.info(e.getMessage());
            throw new DirectAPIException("Ошибка инициализации SOAP клиента", e);
        }
    }

    private void initHeader() {
        stub.clearHeaders();
        for (Map.Entry<String, Object> parameter : requestHeader.getHeaderParameters().entrySet()) {
            addHeaderParameter(parameter.getKey(), parameter.getValue());
        }

        if (AUTH_TYPE.equals(AuthType.TOKEN)) {
            // тут был axis.socketSecureFactory -> org.apache.axis.components.net.SunFakeTrustSocketFactory
            // но он не работает на свежей java
            // заменено static-блоком с доверием любым сертификатам (подсмотрено в SOAPClientV5)
        } else {
            User account = User.get(requestHeader.getLogin());
            String certPath = String.format("jks/%s", account.getCertName());
            log.info("Certificate: " + certPath);
            MyCustomSSLSocketFactory.RESOURCE_PATH_TO_KEYSTORE = certPath;
            AxisProperties.setProperty(
                    "axis.socketSecureFactory",
                    "ru.yandex.autotests.directapi.apiclient.config.MyCustomSSLSocketFactory");
            removeHeaderParameter("token");
        }
    }

    private void addHeaderParameter(String parameterName, Object parameterValue) {
        SOAPHeaderElement element = new SOAPHeaderElement("http://psol.com/2003/tips/header", parameterName);
        try {
            element.setObjectValue(parameterValue);
            if (stub.getHeader("http://psol.com/2003/tips/header", parameterName) == null) {
                stub.setHeader(element);
            } else {
                stub.getHeader("http://psol.com/2003/tips/header", parameterName)
                        .setObjectValue(element.getObjectValue());
            }
        } catch (SOAPException e) {
            throw new DirectAPIException("Не удалось добавить в SOAP заголовок " + parameterName, e);
        }
    }

    public void removeHeaderParameter(String parameterName) {
        SOAPHeaderElement[] currentHeader = stub.getHeaders();
        SOAPHeaderElement element = stub.getHeader("http://psol.com/2003/tips/header", parameterName);
        if (element != null) {
            currentHeader = (SOAPHeaderElement[]) ArrayUtils.removeElement(currentHeader, element);
            stub.clearHeaders();
            for (SOAPHeaderElement headerElement : currentHeader) {
                stub.setHeader(headerElement);
            }
        }
    }

    public <T> T invokeMethod(String methodName, Object params) {
        initStub();
        initHeader();
        methodName = StringUtils.uncapitalize(methodName);
        try {
            Method method = ReflectionUtils.findMethodByName(
                    stub.getClass().getDeclaredMethods(), methodName);
            if (params != null && method.getParameterTypes() != null) {
                Class paramType = method.getParameterTypes()[0];
                params = JsonUtils.getObject(JsonUtils.toString(params), paramType);
            }
            try {
                return (T) ReflectionUtils.invokeMethod(stub, methodName, params);
            } catch (AxisFault axisFault) {
                throw new AxisError(axisFault);
            }
        } catch (NoSuchMethodException e) {
            throw new DirectAPIException("Ошибка исполнения SOAP метода", e);
        } catch (NumberFormatException parseException) {
            throw new DirectAPIException("Неверный ответ сервера (SOAP)", parseException);
        } catch (UndeclaredThrowableException e) {
            throw new DirectAPIException("Неверный ответ сервера (SOAP)", e);
        }
    }


    /*
Читает из ресурсов конфигурационный файл для клиента WS.
*/
    private static EngineConfiguration readConfig() {
        try {
            URL resource = Resources.getResource("jks/client-config.wsdd");
            String xmlconfig = Resources.toString(resource, Charsets.UTF_8);
            log.debug("client-config.wsdd resource" + resource.toString());
            log.trace("read config " + xmlconfig);
            return new XMLStringProvider(xmlconfig);
        } catch (Exception e) {
            log.info("Ошибка чтения конфига client-config.wsdd", e);
            log.info("Логирование SOAP запросов не подключено");
        }

        return null;
    }
}
