package tdl.client;

import cucumber.api.java.en.*;
import tdl.client.abstractions.UserImplementation;
import tdl.client.actions.ClientAction;
import tdl.client.actions.ClientActions;
import tdl.client.audit.StdoutAuditStream;
import utils.jmx.broker.RemoteJmxQueue;
import utils.logging.LogAuditStream;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static org.hamcrest.Matchers.*;
import static org.junit.Assert.assertThat;

public class ClientSteps {
    private final SingletonTestBroker broker;

    // Test broker location
    private static final String HOSTNAME = "localhost";
    private static final int PORT = 21616;

    // Variables set by the background tasks
    private RemoteJmxQueue requestQueue;
    private RemoteJmxQueue responseQueue;
    private Client client;

    //Testing utils
    private int initialRequestCount;
    private LogAuditStream logAuditStream;

    public ClientSteps(SingletonTestBroker broker) {
        this.broker = broker;
        this.logAuditStream = new LogAuditStream(new StdoutAuditStream());
        this.initialRequestCount = 0;
    }

    //~~~~~ Setup

    @Given("^I start with a clean broker and a client for user \"([^\"]*)\"$")
    public void create_the_queues(String username) throws Throwable {
        requestQueue = broker.addQueue(username +".req");
        requestQueue.purge();

        responseQueue = broker.addQueue(username +".resp");
        responseQueue.purge();

        logAuditStream.clearLog();
        client = new Client.Builder()
                .setHostname(HOSTNAME)
                .setPort(PORT)
                .setUniqueId(username)
                .setAuditStream(logAuditStream)
                .create();
    }

    @Given("^the broker is not available$")
    public void client_with_wrong_broker() throws Throwable {
        logAuditStream.clearLog();
        client = new Client.Builder()
                .setHostname(HOSTNAME + "1")
                .setPort(PORT)
                .setUniqueId("X")
                .setAuditStream(logAuditStream)
                .create();
    }

    @Then("^the time to wait for requests is (\\d+)ms$")
    public void check_time(int expectedTimeout) throws Throwable {
        assertThat("The client request timeout has a different value.",
                client.getRequestTimeoutMillis(), equalTo(expectedTimeout));
    }

    @Then("^the request queue is \"([^\"]*)\"$")
    public void check_request_queue(String expectedValue) throws Throwable {
        assertThat("Request queue has a different value.",
                requestQueue.getName(), equalTo(expectedValue));
    }

    @Then("^the response queue is \"([^\"]*)\"$")
    public void check_response_queue(String expectedValue) throws Throwable {
        assertThat("Response queue has a different value.",
                responseQueue.getName(), equalTo(expectedValue));
    }

    class RequestRepresentation {
        String payload;
    }

    @Given("^I receive the following requests:$")
    public void initialize_request_queue(List<RequestRepresentation> requests) throws Throwable {
        for (RequestRepresentation request : requests) {
            requestQueue.sendTextMessage(request.payload);
        }
        initialRequestCount = requests.size();
    }


    //~~~~~ Implementations

    private static final Map<String, UserImplementation> USER_IMPLEMENTATIONS = new HashMap<String, UserImplementation >() {{
        put("add two numbers", params -> {
            Integer x = Integer.parseInt(params[0]);
            Integer y = Integer.parseInt(params[1]);
            return x + y;
        });
        put("increment number", params -> {
            Integer x = Integer.parseInt(params[0]);
            return x + 1;
        });
        put("return null", params -> null);
        put("throw exception", param -> {
            throw new IllegalStateException("faulty user code");
        });
        put("echo the request", params -> params[0]);
        put("some logic", params -> "ok");
        put("work for 500ms", params -> {
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "OK";
        });
    }};


    private static UserImplementation asImplementation(String call) {
        if (USER_IMPLEMENTATIONS.containsKey(call)) {
            return USER_IMPLEMENTATIONS.get(call);
        } else {
            throw new IllegalArgumentException("Not a valid implementation reference: \"" + call+"\"");
        }
    }

    private static final Map<String, ClientAction> CLIENT_ACTIONS = new HashMap<String, ClientAction >() {{
        put("publish", ClientActions.publish());
        put("stop", ClientActions.stop());
        put("publish and stop", ClientActions.publishAndStop());
    }};

    private static ClientAction asAction(String actionName) {
        if (CLIENT_ACTIONS.containsKey(actionName)) {
            return CLIENT_ACTIONS.get(actionName);
        } else {
            throw new IllegalArgumentException("Not a valid action reference: \"" + actionName+"\"");
        }
    }

    public class ProcessingRuleRepresentation {
        String method;
        String call;
        String action;
    }

    @When("^I go live with the following processing rules:$")
    public void go_live(List<ProcessingRuleRepresentation> listOfRules) throws Throwable {
        ProcessingRules processingRules = new ProcessingRules();
        listOfRules.forEach((ruleLine) ->
                        processingRules.on(ruleLine.method).call(asImplementation(ruleLine.call)).then(asAction(ruleLine.action))
        );

        client.goLiveWith(processingRules);
    }

    //~~~~~ Assertions

    @Then("^the client should consume all requests$")
    public void request_queue_empty() throws Throwable {
        assertThat("Requests have not been consumed",requestQueue.getSize(), equalTo(asLong(0)));
    }

    @Then("^the client should consume first request$")
    public void request_queue_less_than_one() throws Throwable {
        assertThat("Wrong number of requests have been consumed",requestQueue.getSize(), equalTo(asLong(initialRequestCount-1)));
    }

    class ResponseRepresentation {
        String payload;
    }

    @And("^the client should publish the following responses:$")
    public void response_queue_contains_expected(List<ResponseRepresentation> expectedResponses) throws Throwable {
        List<String> expectedContents = expectedResponses.stream()
                .map(responseRepresentation -> responseRepresentation.payload)
                .collect(Collectors.toList());
        assertThat("The responses are not correct",responseQueue.getMessageContents(), equalTo(expectedContents));
    }

    class OutputRepresentation {
        String output;
    }

    @Then("^the client should display to console:$")
    public void the_client_should_display_to_console(List<OutputRepresentation> expectedOutputs) throws Throwable {
        String output = logAuditStream.getLog();
        for (OutputRepresentation expectedLine : expectedOutputs) {
            assertThat(output, containsString(expectedLine.output));
        }
        System.out.println(output);
    }

    @But("^the client should not display to console:$")
    public void the_client_should_not_display_to_console(List<String> rejectedOutputs) throws Throwable {
        String output = logAuditStream.getLog();
        for (String expectedLine : rejectedOutputs) {
            assertThat(output, not(containsString(expectedLine)));
        }
    }

    @Then("^the client should not consume any request$")
    public void request_queue_unchanged() throws Throwable {
        assertThat("The request queue has different size. The message has been consumed.",
                requestQueue.getSize(), equalTo(asLong(initialRequestCount)));
    }

    @And("^the client should not publish any response$")
    public void response_queue_unchanged() throws Throwable {
        assertThat("The response queue has different size. Messages have been published.",
                responseQueue.getSize(), equalTo(asLong(0)));
    }

    @Then("^I should get no exception$")
    public void I_should_get_no_exception() throws Throwable {
        //No exception
    }

    //~~~ Utils

    private static Long asLong(Integer value) {
        return (long) value;
    }
}

@@ -1976,20 +1976,11 @@
 ame(
-HOSTNAME + %22
+%2211
 1%22)%0A

Make the bad connection test fail faster