package be.iminds.ilabt.jfed.highlevel.stitcher;

import be.iminds.ilabt.jfed.fedmon.webapi.service.json.Server;
import be.iminds.ilabt.jfed.lowlevel.connection.MockReplyLibrary;
import be.iminds.ilabt.jfed.lowlevel.connection.MockSfaConnection;
import be.iminds.ilabt.jfed.lowlevel.stitching.VlanRange;
import be.iminds.ilabt.jfed.rspec.model.ModelRspecType;
import be.iminds.ilabt.jfed.rspec.rspec_source.RequestRspecSource;
import be.iminds.ilabt.jfed.util.DataConversionUtils;
import be.iminds.ilabt.jfed.util.TextUtil;
import org.apache.xmlrpc.XmlRpcRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.Map;
import java.util.List;
import java.util.Objects;

/**
 * TestStitchingAMv2
 */
public class TestStitchingAMv2Server extends TestStitchingAMServer {
    private final static Logger LOG = LoggerFactory.getLogger(TestStitchingAMv2Server.class);

    private static enum State { NO, CREATED, READY, FAILED }
    private int statusRequestsReceived = 0;
    private int totalStatusRequestsReceived = 0;
    private final int statusRequestsBeforeReady;
    private State state= State.NO;

    private String manifest;

    private boolean answeredPermanentFailure = false;
    private boolean sawAnyRequest = false;
    private List<Integer> seenRequests = new ArrayList<>();

    public TestStitchingAMv2Server(String serverUrl, String cmUrn, String cmHostname, String stitchIface,
                                   VlanRange availableVlans, StitchingTestSetupConfig.StitchingTestSetupAmConfig amConfig,
                                   TestStitchingAMServer dependency) {
        super(serverUrl, cmUrn, cmHostname, stitchIface, availableVlans, amConfig, dependency);
        assert amConfig.hasV2;
        this.statusRequestsBeforeReady = 2;
    }



    public MockSfaConnection.MockXmlRpcAnswer matches(XmlRpcRequest request) {
        if (!Objects.equals(MockReplyLibrary.getServerUrl(request), serverUrl))
            return null;
        int callCount;
        if (request.getMethodName() != null)
            callCount = registerCall(request.getMethodName());
        else
            callCount = -1;

        if (amConfig.hasSpecialReply(StitchingTestSetupConfig.SpecialReply.ERROR_ALWAYS_TIMEOUT))
            return new MockSfaConnection.MockXmlRpcAnswer(new org.apache.http.conn.ConnectTimeoutException("Connect to server timed out"));

        if (Objects.equals(request.getMethodName(), "CreateSliver") && request.getParameterCount() >= 4) {
            if (callCount == 1 && amConfig.hasSpecialReply(StitchingTestSetupConfig.SpecialReply.ERROR_ONETIME_GENERIC_ALLOCATE))
                return new MockSfaConnection.MockXmlRpcAnswer(TestStitchingXmlRpcHelper.wrapErrorReply(2, "Error reserving vlan tag for link0"));
            if (callCount == 1 && amConfig.hasSpecialReply(StitchingTestSetupConfig.SpecialReply.ERROR_ALLOCATE_ONE_TIMEOUT))
                return new MockSfaConnection.MockXmlRpcAnswer(new org.apache.http.conn.ConnectTimeoutException("Connect to server timed out"));
            if (callCount == 2 && amConfig.hasSpecialReply(StitchingTestSetupConfig.SpecialReply.ERROR_ALLOCATE_TWO_TIMEOUT))
                return new MockSfaConnection.MockXmlRpcAnswer(new org.apache.http.conn.ConnectTimeoutException("Connect to server timed out"));

            if (!Objects.equals(state, State.NO)) throw new RuntimeException("TEST BUG DETECTED: am is in wrong state for "+request.getMethodName()+": "+state);

            if (answeredPermanentFailure)
                return new MockSfaConnection.MockXmlRpcAnswer(TestStitchingXmlRpcHelper.wrapErrorReply(5, "TEST BUG DETECTED: already replied with permanent failure. This retry shouldn't have been done."));

            //struct CreateSliver(string slice_urn, string credentials[], string rspec, struct users[], struct options)
            String requestRspec = (String) request.getParameter(2);

            String suggestedVlan = getSuggestedVlan(requestRspec);
            assert suggestedVlan != null;

            if (dependency != null && suggestedVlan.trim().equalsIgnoreCase("any")) {
                String availableRangeStr = getAvailableVlans(new RequestRspecSource(requestRspec, ModelRspecType.BASIC).getModelRspec());
                if (availableRangeStr == null)
                    return new MockSfaConnection.MockXmlRpcAnswer(TestStitchingXmlRpcHelper.wrapErrorReply(5, "TEST BUG DETECTED: no available range"));
                assert availableRangeStr != null;
                VlanRange availableRange = new VlanRange(availableRangeStr);
                if (availableRange.size() != 1 || !availableRange.contains(dependency.getActiveVlan())) //if available is just dependency.getActiveVlan(), it is allowed. otherwise not.
                    return new MockSfaConnection.MockXmlRpcAnswer(TestStitchingXmlRpcHelper.wrapErrorReply(5,
                            "TEST BUG DETECTED: a n y used while this server depends on another server. " +
                                    "availableRange=\""+availableRangeStr+"\" other server vlan: "+dependency.getActiveVlan()));
            }

            if (dependency != null && Integer.parseInt(suggestedVlan) != dependency.getActiveVlan())
                return new MockSfaConnection.MockXmlRpcAnswer(TestStitchingXmlRpcHelper.wrapErrorReply(5,
                        "TEST BUG DETECTED: vlan "+suggestedVlan+" was requested while dependency has "+dependency.getActiveVlan()+" as active vlan"));

            if (!supportsAny && suggestedVlan.trim().equalsIgnoreCase("any")) {
                if (sawAnyRequest)
                    return new MockSfaConnection.MockXmlRpcAnswer(TestStitchingXmlRpcHelper.wrapErrorReply(5, "TEST BUG DETECTED: not first request with vlan a n y"));
                sawAnyRequest = true;

                if (!authority.hasFlag(Server.Flag.featureStitchingAny))
                    return new MockSfaConnection.MockXmlRpcAnswer(TestStitchingXmlRpcHelper.wrapErrorReply(5, "TEST BUG DETECTED: request with vlan a n y for authority which is known not to support it"));

                return new MockSfaConnection.MockXmlRpcAnswer(TestStitchingXmlRpcHelper.wrapErrorReply(2, "ERROR: vlan range any is invalid"));
            }

            //if any is supported or not, if suggested is not any, we need to check if it is available
            if (!suggestedVlan.equalsIgnoreCase("any")) {
                int suggestedVlanInt = 0;
                try {
                    suggestedVlanInt = Integer.parseInt(suggestedVlan);
                } catch (NumberFormatException e) {
                    return new MockSfaConnection.MockXmlRpcAnswer(TestStitchingXmlRpcHelper.wrapErrorReply(5, "TEST BUG DETECTED: invalid vlan in suggestedVlan: \""+suggestedVlan+"\""));
                }
                if (!availableVlans.contains(suggestedVlanInt) || amConfig.hasSpecialReply(StitchingTestSetupConfig.SpecialReply.ERROR_NO_VLANS)) {
                    if (seenRequests.contains(suggestedVlanInt))
                        return new MockSfaConnection.MockXmlRpcAnswer(TestStitchingXmlRpcHelper.wrapErrorReply(5, "TEST BUG DETECTED: not first request with vlan \""+suggestedVlan+"\""));
                    seenRequests.add(suggestedVlanInt);

                    return new MockSfaConnection.MockXmlRpcAnswer(TestStitchingXmlRpcHelper.wrapErrorReply(2, "vlan tag "+suggestedVlanInt+" for "+cmHostname+" not available"));
                }
            } else
            if (amConfig.hasSpecialReply(StitchingTestSetupConfig.SpecialReply.ERROR_NO_VLANS)) {
                answeredPermanentFailure = true;
                return new MockSfaConnection.MockXmlRpcAnswer(TestStitchingXmlRpcHelper.wrapErrorReply(2, "Could not reserve vlan tags for " + cmHostname + ""));
            }


            assert !amConfig.hasSpecialReply(StitchingTestSetupConfig.SpecialReply.ERROR_NO_VLANS);
            state = State.CREATED;

            try {
                manifest = createManifest(requestRspec);
            } catch (NoVlansAvailableException e) {
                if (e.getVlan() != null) {
                    return new MockSfaConnection.MockXmlRpcAnswer(TestStitchingXmlRpcHelper.wrapErrorReply(5, "TEST BUG DETECTED: BUG in tester itself: This vlan not available error should have been handled above: vlan \"" + e.getVlan() + "\""));
                } else
                    //occurs if suggested "any" and no vlans available from the received set of available vlans
                    return new MockSfaConnection.MockXmlRpcAnswer(TestStitchingXmlRpcHelper.wrapErrorReply(2, "Could not reserve vlan tags for " + cmHostname + ""));
            }

            return new MockSfaConnection.MockXmlRpcAnswer(TestStitchingXmlRpcHelper.wrapSuccessStringReply(manifest));
        }
        if (Objects.equals(request.getMethodName(), "ListResources") && request.getParameterCount() >= 2 &&
                ((Map)request.getParameter(1)).containsKey("geni_slice_urn")) {
            //struct ListResources(string credentials[], struct options) with option including "geni_slice_urn"
            if (Objects.equals(state, State.NO)) throw new RuntimeException("am is in wrong state for "+request.getMethodName()+": "+state);

            Map options = (Map) request.getParameter(1);
            boolean compressed = false;
            if (options.containsKey("geni_compressed")) {
                compressed = TextUtil.objectToBoolean(options.get("geni_compressed"));
            }
            assert compressed : "normally jFed sets compressed";

            assert manifest != null;

            String resManifest;
            if (compressed)
                resManifest = DataConversionUtils.compressToBase64(manifest);
            else
                resManifest = manifest;

            return new MockSfaConnection.MockXmlRpcAnswer(TestStitchingXmlRpcHelper.wrapSuccessStringReply(resManifest));
        }
        if (Objects.equals(request.getMethodName(), "ListResources") && request.getParameterCount() >= 2 &&
                (!((Map)request.getParameter(1)).containsKey("geni_slice_urn"))) {
            /*
             * struct ListResources(string credentials[], struct options)
             * Typical options, which we assume here:  (so no "geni_slice_urn")
             *   "geni_available": true, "geni_rspec_version": { "version": "3", "type": "geni" }, "geni_compressed": true
             * */
            if (!Objects.equals(state, State.NO)) throw new RuntimeException("am is in wrong state for "+request.getMethodName()+": "+state);

            Map options = (Map) request.getParameter(1);
            boolean compressed = false;
            if (options.containsKey("geni_compressed")) {
                compressed = TextUtil.objectToBoolean(options.get("geni_compressed"));
            }
            assert compressed : "normally jFed sets compressed";

            assert advertisement != null;

            String resAdvertisement;
            if (compressed)
                resAdvertisement = DataConversionUtils.compressToBase64(advertisement);
            else
                resAdvertisement = advertisement;

            return new MockSfaConnection.MockXmlRpcAnswer(TestStitchingXmlRpcHelper.wrapSuccessStringReply(resAdvertisement));
        }
        if (Objects.equals(request.getMethodName(), "SliverStatus") && request.getParameterCount() >= 2) {
            if (Objects.equals(state, State.NO)) throw new RuntimeException("am is in wrong state for "+request.getMethodName()+": "+state);

            String error = null;
            if (totalStatusRequestsReceived == 1 && amConfig.hasSpecialReply(StitchingTestSetupConfig.SpecialReply.FAIL_ONCE_SECOND_STATUS_BANDWIDTH)) {
                state = State.FAILED;
                error = "Bandwidth PCE(PCE_CREATE_FAILED): 'Unable to find path because the maximum bandwidth of a link has been exceeded.";
            }
            if (totalStatusRequestsReceived == 1 && amConfig.hasSpecialReply(StitchingTestSetupConfig.SpecialReply.FAIL_ONCE_SECOND_STATUS_GENERICCAUSE)) {
                state = State.FAILED;
                error = "unspecified error";
            }
            if (totalStatusRequestsReceived == 1 && amConfig.hasSpecialReply(StitchingTestSetupConfig.SpecialReply.FAIL_ONCE_SECOND_STATUS_VLAN_UNAVAILABLE)) {
                state = State.FAILED;
                error = "vlan "+activeVlan+" is unavailable";
            }
            if (statusRequestsReceived == 1 && amConfig.hasSpecialReply(StitchingTestSetupConfig.SpecialReply.FAIL_ALLWAYS_SECOND_STATUS_VLAN_UNAVAILABLE)) {
                state = State.FAILED;
                error = "vlan "+activeVlan+" is unavailable";
            }

            // struct SliverStatus(string slice_urn, string credentials[], struct options)
            String sliceUrn = (String) request.getParameter(0);
            if (state != State.FAILED && statusRequestsReceived >= statusRequestsBeforeReady)
                state = State.READY;

            statusRequestsReceived += 1;
            totalStatusRequestsReceived += 1;

            List<ComponentInfo> nodes = lookupByType("node");
            ComponentInfo node1 = nodes.get(0);
            String sliverUrn = node1.sliverId;

            String stateStr = (state == State.READY ? "ready" : (state == State.FAILED ? "fail" : "changing"));

            String urn = TestStitchingXmlRpcHelper.wrapStructMember("geni_urn",
                    "<string>" + TestStitchingXmlRpcHelper.encodeXmlRpcString(sliceUrn) + "</string>");
            String status = TestStitchingXmlRpcHelper.wrapStructMember("geni_status",
                    "<string>"+stateStr+"</string>");

            String resUrn = TestStitchingXmlRpcHelper.wrapStructMember("geni_urn", sliverUrn);
            String resStatus = TestStitchingXmlRpcHelper.wrapStructMember("geni_status", "<string>"+stateStr+"</string>");
            String resError = error == null ? "" : TestStitchingXmlRpcHelper.wrapStructMember("geni_error", "<string>"+error+"</string>");
            String resource = "<struct>"+resUrn+resStatus+resError+"</struct>";
            String resources_array = "<array><data><value>"+resource+"</value></data></array>";
            String res = TestStitchingXmlRpcHelper.wrapStructMember("geni_resources", resources_array);

//            LOG.debug("geni_resources:\n"+res+"\n\n");
//            LOG.debug("geni_urn:\n"+status+"\n\n");
//            LOG.debug("geni_status:\n"+urn+"\n\n");

            return new MockSfaConnection.MockXmlRpcAnswer(TestStitchingXmlRpcHelper.wrapSuccessXmlRpcAMReply("<struct>" + urn + res + status + "</struct>"));
        }
        if (Objects.equals(request.getMethodName(), "DeleteSliver") && request.getParameterCount() > 0) {
            state = State.NO;

            activeVlan = null;
            seenRequests.clear();
            sawAnyRequest = false;
            statusRequestsReceived = 0;

            return new MockSfaConnection.MockXmlRpcAnswer(TestStitchingXmlRpcHelper.wrapSuccessXmlRpcAMReply("<boolean>1</boolean>"));
        }
        return null;
    }

}
