package be.iminds.ilabt.jfed.call_log_output;

import be.iminds.ilabt.jfed.fedmon.webapi.service.json.ServiceBuilder;
import be.iminds.ilabt.jfed.log.ApiCallDetails;
import be.iminds.ilabt.jfed.log.ApiCallIdGenerator;
import be.iminds.ilabt.jfed.lowlevel.ApiCallReply;
import be.iminds.ilabt.jfed.lowlevel.connection.JFedConnection;
import be.iminds.ilabt.jfed.util.*;
import be.iminds.ilabt.util.jsonld.JsonLdObjectsMetaData;
import com.fasterxml.jackson.annotation.*;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import org.slf4j.LoggerFactory;

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import java.util.*;

/**
 * This class is a serializable copy of ApiCallDetails, and is meant to be used for caching and/or storing to disk
 *
 * For BugReports, it is converted to/from XML using StaX. See SerializableApiCallDetailsReader and SerializableApiCallDetailsWriter
 *
 * For automated tester XML reports, it is converted to XML using JAXB
 *
 * User: twalcari
 * Date: 4/17/2014
 * Time: 09:34
 */
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown=true)
@JsonAutoDetect(fieldVisibility=JsonAutoDetect.Visibility.PUBLIC_ONLY)
@XmlRootElement(name = "call")
public class SerializableApiCallDetails {
    private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(SerializableApiCallDetails.class);

    private final int id;

    @XmlElement(name = "autority_name")
    private final String authorityName;

    @XmlElement(name = "authority_urn")
    private final String authorityUrn;

    private final String baseServerUrl;

    @XmlElement(name = "server_url")
    private final String callServerUrl;

    /**
     * proxy used for the connection. May be null.
     */
    @XmlElement(name = "proxy_info")
    private final JFedConnection.ProxyInfo proxyInfo;

    @XmlElement(name = "connection_sslauth_user_urn")
    private final String connectionSslAuthUserUrn;
    @XmlElement(name = "connection_sslauth_user_certs")
    private final String connectionSslAuthUserCertificates;

    @XmlElement(name = "connection_basicauth_username")
    private final String connectionBasicHttpAuthUsername;

    @XmlElement(name = "connection_auth")
    private final String connectionAuthenticationMethod; //NONE, HTTP_BASIC, SSL_CLIENT_AUTH
    @XmlElement(name = "connection_proto")
    private final String connectionProtocol; //HTTP, HTTPS
    @XmlElement(name = "connection_type")
    private final String connectionType; //HTTP, SFA

    @XmlElement(name = "connection_id")
    private final String connectionId;

    @XmlElement(name = "http_request_line")
    private final String httpRequestStatusLine;
    @XmlElement(name = "http_request_headers")
    private final String httpRequestHeaders;
    @XmlElement(name = "http_request")
    private final String httpRequest;
    @XmlElement(name = "http_status_line")
    private final String httpResponseStatusLine;
    @XmlElement(name = "http_response_headers")
    private final String httpResponseHeaders;
    @XmlElement(name = "http_reply")
    private final String httpResponse;


    @XmlElement(name = "api_name")
    private final String apiName;
    @XmlElement(name = "java_method_name")
    private final String javaMethodName;
    @XmlElement(name = "geni_method_name")
    private final String geniMethodName;
    @XmlElement(name = "method_parameters")
    private final String methodParameters;

    @XmlElement(name = "xmlrpc_request")
    private final String xmlRpcRequestJsonString;
    @XmlElement(name = "xmlrpc_reply")
    private final String xmlRpcResponseJsonString;

    @XmlElement(name = "geni_value")
    private final String xmlRpcGeniResponseValue;
    @XmlElement(name = "geni_output")
    private final String xmlRpcGeniResponseOutput;
    @XmlElement(name = "geni_code")
    private final String xmlRpcGeniResponseCode;

    @XmlElement(name = "geni_response_code")
    private final Integer geniResponseCode;
    @XmlElement(name = "geni_response_code_description")
    private final String geniResponseCodeDescription;
    @XmlElement(name = "geni_code_is_success")
    private final Boolean geniResponseCodeIsSuccess;
    @XmlElement(name = "geni_response_output")
    private final String geniResponseOutput;

    @XmlElement(name = "raw_response")
    private final String rawValue;

    @XmlElement(name = "start_time")
    private final Date startTime;
    @XmlElement(name = "stop_time")
    private final Date stopTime;

    @XmlElement(name = "protogeni_spew_log_url")
    private final String protogeniSpewLogUrl;

    @XmlElement(name = "exception")
    private final String exceptionString;


    private  SerializableApiCallDetails() {
        //JAXB requires this constructor, BUT we don't actually unmarshal using JAXB, so we never use it.
        throw new RuntimeException("Not supported. For XML unmarshalling, use StaX instead of JAXB.");
    }
    @JsonCreator
    public SerializableApiCallDetails(
            @JsonProperty("id") int id,
            @JsonProperty("authorityName") String authorityName,
            @JsonProperty("authorityUrn") String authorityUrn,
            @JsonProperty("baseServerUrl") String baseServerUrl,
            @JsonProperty("callServerUrl") String callServerUrl,
            @JsonProperty("proxyInfo") JFedConnection.ProxyInfo proxyInfo,
            @JsonProperty("connectionSslAuthUserUrn") String connectionSslAuthUserUrn,
            @JsonProperty("connectionBasicHttpAuthUsername") String connectionBasicHttpAuthUsername,
            @JsonProperty("connectionId") String connectionId,
            @JsonProperty("connectionSslAuthUserCertificates") String connectionSslAuthUserCertificates,
            @JsonProperty("connectionAuthenticationMethod") String connectionAuthenticationMethod,
            @JsonProperty("connectionProtocol") String connectionProtocol,
            @JsonProperty("connectionType") String connectionType,
            @JsonProperty("httpRequestStatusLine") String httpRequestStatusLine,
            @JsonProperty("httpRequestHeaders") String httpRequestHeaders,
            @JsonProperty("httpRequest") String httpRequest,
            @JsonProperty("httpResponseStatusLine") String httpResponseStatusLine,
            @JsonProperty("httpResponseHeaders") String httpResponseHeaders,
            @JsonProperty("httpResponse") String httpResponse,
            @JsonProperty("apiName") String apiName,
            @JsonProperty("javaMethodName") String javaMethodName,
            @JsonProperty("geniMethodName") String geniMethodName,
            @JsonProperty("methodParameters") String methodParameters,
            @JsonProperty("xmlRpcRequestJsonString") String xmlRpcRequestJsonString,
            @JsonProperty("xmlRpcResponseJsonString") String xmlRpcResponseJsonString,
            @JsonProperty("xmlRpcGeniResponseValue") String xmlRpcGeniResponseValue,
            @JsonProperty("xmlRpcGeniResponseOutput") String xmlRpcGeniResponseOutput,
            @JsonProperty("xmlRpcGeniResponseCode") String xmlRpcGeniResponseCode,
            @JsonProperty("geniResponseCode") Integer geniResponseCode,
            @JsonProperty("geniResponseCodeDescription") String geniResponseCodeDescription,
            @JsonProperty("geniResponseCodeIsSuccess") Boolean geniResponseCodeIsSuccess,
            @JsonProperty("geniResponseOutput") String geniResponseOutput,
            @JsonProperty("rawValue") String rawValue,
            @JsonProperty("startTime") Date startTime, 
            @JsonProperty("stopTime") Date stopTime,
            @JsonProperty("protogeniSpewLogUrl") String protogeniSpewLogUrl,
            @JsonProperty("exceptionString") String exceptionString) {
        this.id = id;
        this.authorityName = authorityName;
        this.authorityUrn = authorityUrn;
        this.baseServerUrl = baseServerUrl;
        this.callServerUrl = callServerUrl;
        this.proxyInfo = proxyInfo;
        this.connectionSslAuthUserUrn = connectionSslAuthUserUrn;
        this.connectionBasicHttpAuthUsername = connectionBasicHttpAuthUsername;
        this.connectionAuthenticationMethod = connectionAuthenticationMethod;
        this.connectionProtocol = connectionProtocol;
        this.connectionType = connectionType;
        this.connectionId = connectionId;
        this.connectionSslAuthUserCertificates = connectionSslAuthUserCertificates;
        this.httpRequestStatusLine = httpRequestStatusLine;
        this.httpRequestHeaders = httpRequestHeaders;
        this.httpRequest = httpRequest;
        this.httpResponseStatusLine = httpResponseStatusLine;
        this.httpResponseHeaders = httpResponseHeaders;
        this.httpResponse = httpResponse;
        this.apiName = apiName;
        this.javaMethodName = javaMethodName;
        this.geniMethodName = geniMethodName;
        this.methodParameters = methodParameters;
        this.xmlRpcRequestJsonString = xmlRpcRequestJsonString;
        this.xmlRpcResponseJsonString = xmlRpcResponseJsonString;
        this.xmlRpcGeniResponseValue = xmlRpcGeniResponseValue;
        this.xmlRpcGeniResponseOutput = xmlRpcGeniResponseOutput;
        this.xmlRpcGeniResponseCode = xmlRpcGeniResponseCode;
        this.geniResponseCodeIsSuccess = geniResponseCodeIsSuccess;
        this.geniResponseCode = geniResponseCode;
        this.geniResponseOutput = geniResponseOutput;
        this.geniResponseCodeDescription = geniResponseCodeDescription;

        this.rawValue = rawValue;
//        this.rawResponseValue= rawResponseValue;
//        this.rawResponseOutput = rawResponseOutput;
//        this.rawResponseCode = rawResponseCode;

        this.startTime = startTime;
        this.stopTime = stopTime;

        this.protogeniSpewLogUrl = protogeniSpewLogUrl;
        this.exceptionString = exceptionString;
    }

    /**
     * Copy constructor: will create a copy of the SerializableApiCallDetails, but with a new Id, which is unique in this
     * jFed instance.
     *
     * @param sacd
     */
    public SerializableApiCallDetails(SerializableApiCallDetails sacd) {
        this.id = ApiCallIdGenerator.getNextId();
        this.authorityName = sacd.authorityName;
        this.authorityUrn = sacd.authorityUrn;
        this.baseServerUrl = sacd.baseServerUrl;
        this.callServerUrl = sacd.callServerUrl;
        this.proxyInfo = sacd.proxyInfo;
        this.connectionSslAuthUserUrn = sacd.connectionSslAuthUserUrn;
        this.connectionBasicHttpAuthUsername = sacd.connectionBasicHttpAuthUsername;
        this.connectionAuthenticationMethod = sacd.connectionAuthenticationMethod;
        this.connectionProtocol = sacd.connectionProtocol;
        this.connectionType = sacd.connectionType;
//        assert connectionType != null;
//        assert !connectionType.trim().isEmpty();
        this.connectionId = sacd.connectionId;
        this.connectionSslAuthUserCertificates = sacd.connectionSslAuthUserCertificates;
        this.httpRequestStatusLine = sacd.httpRequestStatusLine;
        this.httpRequestHeaders = sacd.httpRequestHeaders;
        this.httpRequest = sacd.httpRequest;
        this.httpResponseStatusLine = sacd.httpResponseStatusLine;
        this.httpResponseHeaders = sacd.httpResponseHeaders;
        this.httpResponse = sacd.httpResponse;
        this.apiName = sacd.apiName;
        this.javaMethodName = sacd.javaMethodName;
        this.geniMethodName = sacd.geniMethodName;
        this.methodParameters = sacd.methodParameters;
        this.xmlRpcRequestJsonString = sacd.xmlRpcRequestJsonString;
        this.xmlRpcResponseJsonString = sacd.xmlRpcResponseJsonString;
        this.xmlRpcGeniResponseValue = sacd.xmlRpcGeniResponseValue;
        this.xmlRpcGeniResponseOutput = sacd.xmlRpcGeniResponseOutput;
        this.xmlRpcGeniResponseCode = sacd.xmlRpcGeniResponseCode;
        this.geniResponseCodeIsSuccess = sacd.geniResponseCodeIsSuccess;
        this.geniResponseCode = sacd.geniResponseCode;
        this.geniResponseOutput = sacd.geniResponseOutput;
        this.geniResponseCodeDescription = sacd.geniResponseCodeDescription;

        this.rawValue = sacd.rawValue;
//        this.rawResponseValue= rawResponseValue;
//        this.rawResponseOutput = rawResponseOutput;
//        this.rawResponseCode = rawResponseCode;

        this.startTime = sacd.startTime;
        this.stopTime = sacd.stopTime;

        this.protogeniSpewLogUrl = sacd.protogeniSpewLogUrl;
        this.exceptionString = sacd.exceptionString;
    }

    public int getId() {
        return id;
    }

    public String getAuthorityName() {
        return authorityName;
    }

    public String getAuthorityUrn() {
        return authorityUrn;
    }

    public String getBaseServerUrl() {
        return baseServerUrl;
    }

    public String getCallServerUrl() {
        return callServerUrl;
    }


    @JsonSerialize(as = JFedConnection.SshProxyInfo.class)
    @JsonDeserialize(as = JFedConnection.SshProxyInfo.class)
    public JFedConnection.ProxyInfo getProxyInfo() {
        return proxyInfo;
    }

    public String getConnectionSslAuthUserUrn() {
        return connectionSslAuthUserUrn;
    }
    public String getConnectionBasicHttpAuthUsername() {
        return connectionBasicHttpAuthUsername;
    }

    public String getConnectionAuthenticationMethod() {
        return connectionAuthenticationMethod;
    }

    public String getConnectionProtocol() {
        return connectionProtocol;
    }

    public String getConnectionType() {
        return connectionType;
    }

    public String getConnectionId() {
        return connectionId;
    }

    public String getConnectionSslAuthUserCertificates() {
        return connectionSslAuthUserCertificates;
    }

    public String getHttpRequestStatusLine() {
        return httpRequestStatusLine;
    }

    public String getHttpRequestHeaders() {
        return httpRequestHeaders;
    }

    public String getHttpRequest() {
        return httpRequest;
    }

    public String getHttpResponseStatusLine() {
        return httpResponseStatusLine;
    }

    public String getHttpResponseHeaders() {
        return httpResponseHeaders;
    }

    public String getHttpResponse() {
        return httpResponse;
    }

    public String getApiName() {
        return apiName;
    }

    public String getJavaMethodName() {
        return javaMethodName;
    }

    public String getGeniMethodName() {
        return geniMethodName;
    }

    public String getMethodParameters() {
        return methodParameters;
    }

    public String getXmlRpcRequestJsonString() {
        return xmlRpcRequestJsonString;
    }

    public String getXmlRpcResponseJsonString() {
        return xmlRpcResponseJsonString;
    }

    @JsonIgnore
    public Object getXmlRpcRequestJsonObject() {
        try {
            return JSonHelper.jsonStringToXmlRpcLikeObject(getXmlRpcRequestJsonString());
        } catch (Exception e) {
            LOG.warn("Exception while parsing JSON or JSON like XML-RPC request. Will return null.", e);
            return null;
        }
    }

    @JsonIgnore
    public Object getXmlRpcResponseJsonObject() {
        try {
            String jsonString = getXmlRpcResponseJsonString();
            if (jsonString == null)
                return null;
            return JSonHelper.jsonStringToXmlRpcLikeObject(jsonString);
        } catch (Exception e) {
            LOG.warn("Exception while parsing JSON or JSON like XML-RPC response. Will return null.", e);
            return null;
        }
    }

    /**
     * Same as getXmlRpcRequestJsonString, and falling back to that if something goes wrong.
     * However, this is a "human readable" version, which means that \n in the JSON strings is replaced by an actual newline
     */
    @JsonIgnore
    public String getXmlRpcRequestHumanReadableString() {
        //TODO improve generating human readable string from JSON
        // this is a much too simple method to get such a string. This will fail if the text contains for example \\n
        //     a better way is to create the JsonObject and convert it back into JSON, but with a "human readable" flag set that prevents newline from getting escaped
        if (xmlRpcRequestJsonString == null)
            return null;

        return xmlRpcRequestJsonString.replaceAll("\\\\n", "\n");
    }

    /**
     * Same as getXmlRpcResponseJSonString, and falling back to that if something goes wrong.
     * However, this is a "human readable" version, which means that \n in the JSON strings is replaced by an actual newline
     */
    @JsonIgnore
    public String getXmlRpcResponseHumanReadableString() {
        if (xmlRpcResponseJsonString == null) return null;
        return xmlRpcResponseJsonString.replaceAll("\\\\n", "\n");
    }

    public String getXmlRpcGeniResponseValue() {
        return xmlRpcGeniResponseValue;
    }

    public String getXmlRpcGeniResponseOutput() {
        return xmlRpcGeniResponseOutput;
    }

    public String getXmlRpcGeniResponseCode() {
        return xmlRpcGeniResponseCode;
    }

    public Integer getGeniResponseCode() {
        return geniResponseCode;
    }

    public String getGeniResponseCodeDescription() {
        return geniResponseCodeDescription;
    }

    public Boolean getGeniResponseCodeIsSuccess() {
        return geniResponseCodeIsSuccess;
    }

    public String getGeniResponseOutput() {
        return geniResponseOutput;
    }

    public Date getStartTime() {
        return startTime;
    }

    public Date getStopTime() {
        return stopTime;
    }

    public String getProtogeniSpewLogUrl() {
        return protogeniSpewLogUrl;
    }

    public String getExceptionString() {
        return exceptionString;
    }

    public String getRawValue() {
        return rawValue;
    }


    @JsonIgnore
    public String getAsJsonString() {
        ObjectMapper mapper = new ObjectMapper();
        try {
            return mapper
                    .writer()
//                    .writerWithDefaultPrettyPrinter()
                    .writeValueAsString(this);
        } catch (JsonProcessingException e) {
            LOG.error("Exception converting "+getClass().getSimpleName()+" "+ id +" to JSON", e);
            throw new RuntimeException("Exception converting "+getClass().getSimpleName()+" "+ id +" to JSON", e);
        }
    }
}
