package io.retel.ariproxy.boundary.events;

import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;

import akka.NotUsed;
import akka.actor.ActorRef;
import akka.actor.ActorSystem;
import akka.http.javadsl.model.ws.Message;
import akka.http.scaladsl.model.ws.TextMessage.Strict;
import akka.stream.javadsl.Sink;
import akka.stream.javadsl.Source;
import akka.testkit.javadsl.TestKit;
import io.retel.ariproxy.boundary.callcontext.api.CallContextProvided;
import io.retel.ariproxy.boundary.callcontext.api.ProvideCallContext;
import io.retel.ariproxy.boundary.callcontext.api.ProviderPolicy;
import io.retel.ariproxy.boundary.commandsandresponses.auxiliary.AriMessageType;
import io.retel.ariproxy.metrics.IncreaseCounter;
import io.retel.ariproxy.metrics.StartCallSetupTimer;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.hamcrest.CoreMatchers;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

class WebsocketMessageToProducerRecordTranslatorITCase {

	private static final CallContextProvided CALL_CONTEXT_PROVIDED = new CallContextProvided("CALL_CONTEXT_PROVIDED");
	private final String TEST_SYSTEM = this.getClass().getSimpleName();
	private ActorSystem system;

	@AfterEach
	void teardown() {
		TestKit.shutdownActorSystem(system);
		system.terminate();
	}

	@BeforeEach
	void setup() {
		system = ActorSystem.create(TEST_SYSTEM);
	}

	@Test
	void verifyProcessingPipelineWorksAsExpectedForBogusMessages() {

		final TestKit catchAllProbe = new TestKit(system);

		final Source<Message, NotUsed> source = Source.single(new Strict("invalid message from ws"));
		final Sink<ProducerRecord<String, String>, NotUsed> sink = Sink.actorRef(catchAllProbe.getRef(), new ProducerRecord<String, String>("none", "completed"));

		WebsocketMessageToProducerRecordTranslator.eventProcessing()
				.on(system)
				.withHandler(() -> catchAllProbe.getRef().tell("Application replaced", catchAllProbe.getRef()))
				.withCallContextProvider(catchAllProbe.getRef())
				.withMetricsService(catchAllProbe.getRef())
				.from(source)
				.to(sink)
				.run();

		final ProducerRecord<String, String> completeMsg = catchAllProbe.expectMsgClass(ProducerRecord.class);
		assertThat(completeMsg.topic(), is("none"));
		assertThat(completeMsg.value(), is("completed"));
	}

	@Test
	@DisplayName("A websocket message shall be converted into a kafka producer record while also recording metrics")
	void verifyProsessingPipelineWorksAsExpected() {
		final TestKit callcontextProvider = new TestKit(system);
		final TestKit metricsService = new TestKit(system);
		final TestKit kafkaProducer = new TestKit(system);
		final TestKit applicationReplacedHandler = new TestKit(system);

		final Strict stasisStartEvent = new Strict(StasisEvents.stasisStartEvent);

		final String resourceId = "1532965104.0";

		final Source<Message, NotUsed> source = Source.single(stasisStartEvent);

		final Sink<ProducerRecord<String, String>, NotUsed> sink = Sink.actorRef(kafkaProducer.getRef(), new ProducerRecord<String, String>("none", "completed"));

		WebsocketMessageToProducerRecordTranslator.eventProcessing()
				.on(system)
				.withHandler(() -> applicationReplacedHandler.getRef().tell("Application replaced", ActorRef.noSender()))
				.withCallContextProvider(callcontextProvider.getRef())
				.withMetricsService(metricsService.getRef())
				.from(source)
				.to(sink)
				.run();

		final ProvideCallContext provideCallContextForMetrics = callcontextProvider.expectMsgClass(ProvideCallContext.class);
		assertThat(provideCallContextForMetrics.resourceId(), is(resourceId));
		assertThat(provideCallContextForMetrics.policy(), is(ProviderPolicy.CREATE_IF_MISSING));
		callcontextProvider.reply(CALL_CONTEXT_PROVIDED);

		kafkaProducer.expectMsgClass(ProducerRecord.class);

		final IncreaseCounter eventTypeCounter = metricsService.expectMsgClass(IncreaseCounter.class);
		assertThat(eventTypeCounter.getName(), CoreMatchers.is(AriMessageType.STASIS_START.name()));

		final IncreaseCounter callsStartedCounter = metricsService.expectMsgClass(IncreaseCounter.class);
		assertThat(callsStartedCounter.getName(), is("CallsStarted"));

		final ProvideCallContext provideCallContextForRouting = callcontextProvider.expectMsgClass(ProvideCallContext.class);
		assertThat(provideCallContextForRouting.resourceId(), is(resourceId));
		assertThat(provideCallContextForRouting.policy(), is(ProviderPolicy.CREATE_IF_MISSING));
		callcontextProvider.reply(CALL_CONTEXT_PROVIDED);

		final StartCallSetupTimer startCallSetupTimer = metricsService.expectMsgClass(StartCallSetupTimer.class);
		assertThat(startCallSetupTimer.getCallContext(), is(CALL_CONTEXT_PROVIDED.callContext()));

		final ProducerRecord completedRecord = kafkaProducer.expectMsgClass(ProducerRecord.class);
		assertThat(completedRecord.topic(), is("none"));
		assertThat(completedRecord.value(), is("completed"));
	}
}