package ca.uhn.fhir.jpa.starter;

import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.api.CacheControlDirective;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.client.api.IGenericClient;
import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum;
import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
import jakarta.websocket.ContainerProvider;
import jakarta.websocket.Session;
import jakarta.websocket.WebSocketContainer;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r5.model.Bundle;
import org.hl7.fhir.r5.model.Enumerations;
import org.hl7.fhir.r5.model.Observation;
import org.hl7.fhir.r5.model.Patient;
import org.hl7.fhir.r5.model.Subscription;
import org.hl7.fhir.r5.model.SubscriptionTopic;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.test.context.junit.jupiter.SpringExtension;

import java.net.URI;

import static ca.uhn.fhir.util.TestUtil.waitForSize;
import static org.awaitility.Awaitility.await;
import static org.junit.jupiter.api.Assertions.assertEquals;

@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = {Application.class}, properties =
  {
     "spring.datasource.url=jdbc:h2:mem:dbr5",
     "hapi.fhir.fhir_version=r5",
	  "hapi.fhir.cr_enabled=false",
	  "hapi.fhir.subscription.websocket_enabled=true"
  })
public class ExampleServerR5IT {

  private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ExampleServerDstu2IT.class);
  private IGenericClient ourClient;
  private FhirContext ourCtx;

  public static final String SUBSCRIPTION_TOPIC_TEST_URL = "http://example.com/topic/test";


  @LocalServerPort
  private int port;


  @Test
  void testCreateAndRead() {

    String methodName = "testCreateResourceConditional";

    Patient pt = new Patient();
    pt.addName().setFamily(methodName);
    IIdType id = ourClient.create().resource(pt).execute().getId();

    Patient pt2 = ourClient.read().resource(Patient.class).withId(id).execute();
    assertEquals(methodName, pt2.getName().get(0).getFamily());
  }

  @Test
  void testWebsocketSubscription() throws Exception {
	  String endpoint = "ws://localhost:" + port + "/websocket";
    /*
     * Create topic
     */
    SubscriptionTopic topic = new SubscriptionTopic();

	 topic.setUrl(SUBSCRIPTION_TOPIC_TEST_URL);
	  topic.setStatus(Enumerations.PublicationStatus.ACTIVE);
	  SubscriptionTopic.SubscriptionTopicResourceTriggerComponent trigger = topic.addResourceTrigger();
	  trigger.setResource("Observation");
	  trigger.addSupportedInteraction(SubscriptionTopic.InteractionTrigger.CREATE);
	  trigger.addSupportedInteraction(SubscriptionTopic.InteractionTrigger.UPDATE);

	 ourClient.create().resource(topic).execute();

	  waitForSize(1, () -> ourClient
		  .search()
		  .forResource(SubscriptionTopic.class)
		  .where(Subscription.STATUS.exactly().code("active"))
		  .cacheControl(
			  new CacheControlDirective()
				  .setNoCache(true))
		  .returnBundle(Bundle.class)
		  .execute()
		  .getEntry()
		  .size());

	  /*
     * Create subscription
     */
    Subscription subscription = new Subscription();

    subscription.setTopic(SUBSCRIPTION_TOPIC_TEST_URL);
    subscription.setReason("Monitor new neonatal function (note, age will be determined by the monitor)");
    subscription.setStatus(Enumerations.SubscriptionStatusCodes.REQUESTED);
    subscription.getChannelType()
      .setSystem("http://terminology.hl7.org/CodeSystem/subscription-channel-type")
      .setCode("websocket");
    subscription.setContentType("application/fhir+json");
	 subscription.setEndpoint(endpoint);

    MethodOutcome methodOutcome = ourClient.create().resource(subscription).execute();
    IIdType mySubscriptionId = methodOutcome.getId();

    // Wait for the subscription to be activated
    waitForSize(1, () -> ourClient
		 .search()
		 .forResource(Subscription.class)
		 .where(Subscription.STATUS.exactly().code("active"))
		 .cacheControl(
			 new CacheControlDirective()
				 .setNoCache(true))
		 .returnBundle(Bundle.class)
		 .execute()
		 .getEntry()
		 .size());

    /*
     * Attach websocket
     */

	  SocketImplementation mySocketImplementation = new SocketImplementation(mySubscriptionId.getIdPart(),
		  EncodingEnum.JSON);

	  URI echoUri = new URI(endpoint);

	  WebSocketContainer container = ContainerProvider.getWebSocketContainer();

	  ourLog.info("Connecting to : {}", echoUri);
	  Session session = container.connectToServer(mySocketImplementation, echoUri);
	  ourLog.info("Connected to WS: {}", session.isOpen());

    /*
     * Create a matching resource
     */
    Observation obs = new Observation();
    obs.setStatus(Enumerations.ObservationStatus.FINAL);
    ourClient.create().resource(obs).execute();

    /*
     * Ensure that we receive a ping on the websocket
     */
    await().until(() -> mySocketImplementation.myPingCount > 0);

    /*
     * Clean up
     */
    ourClient.delete().resourceById(mySubscriptionId).execute();
  }

  @BeforeEach
  void beforeEach() {

    ourCtx = FhirContext.forR5();
    ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER);
    ourCtx.getRestfulClientFactory().setSocketTimeout(1200 * 1000);
    String ourServerBase = "http://localhost:" + port + "/fhir/";
    ourClient = ourCtx.newRestfulGenericClient(ourServerBase);
    ourClient.registerInterceptor(new LoggingInterceptor(true));
  }
}
