package oghamspringbootv2autoconfigure.it;

import static fr.sii.ogham.testing.assertion.OghamMatchers.isIdenticalHtml;
import static fr.sii.ogham.testing.util.ResourceUtils.resourceAsString;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.assertj.core.api.Assertions.assertThat;
import static org.awaitility.Awaitility.await;
import static org.hamcrest.Matchers.is;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;

import java.io.IOException;

import org.jsmpp.bean.SubmitSm;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.HttpEntity;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.stereotype.Service;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
import org.thymeleaf.exceptions.TemplateProcessingException;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.icegreen.greenmail.junit.GreenMailRule;
import com.icegreen.greenmail.util.ServerSetupTest;

import fr.sii.ogham.core.exception.MessageNotSentException;
import fr.sii.ogham.core.exception.MessagingException;
import fr.sii.ogham.core.message.content.TemplateContent;
import fr.sii.ogham.core.service.MessagingService;
import fr.sii.ogham.email.message.Email;
import fr.sii.ogham.sms.message.Sms;
import fr.sii.ogham.spring.v2.autoconfigure.OghamSpringBoot2AutoConfiguration;
import fr.sii.ogham.testing.assertion.OghamAssertions;
import fr.sii.ogham.testing.extension.junit.JsmppServerRule;
import fr.sii.ogham.testing.extension.junit.LoggingTestRule;
import fr.sii.ogham.testing.extension.junit.SmppServerRule;
import mock.context.SimpleBean;
import oghamspringbootv2autoconfigure.it.SpringWebBeanResolutionTest.TestApplication;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = {OghamSpringBoot2AutoConfiguration.class, TestApplication.class}, 
				webEnvironment=RANDOM_PORT,
				properties= {
						"spring.mail.host=127.0.0.1", 
						"spring.mail.port=3025",
						"[email protected]",
						"ogham.sms.smpp.host=localhost",
						"ogham.sms.smpp.port=2775"})
public class SpringWebBeanResolutionTest {
	@Rule public final LoggingTestRule loggingRule = new LoggingTestRule();
	@Rule public final GreenMailRule greenMail = new GreenMailRule(ServerSetupTest.SMTP);
	@Rule public final SmppServerRule<SubmitSm> smppServer = new JsmppServerRule(2775);


	@Value("${local.server.port}")
	int port;

	@Test
	public void emailUsingThymeleafTemplateShouldResolveBeansAndUrls() throws MessagingException, IOException {
		RestTemplate rt = new RestTemplate();
		// @formatter:off
		UriComponentsBuilder builder = UriComponentsBuilder.fromPath("email")
				.scheme("http")
				.host("localhost")
				.port(port);
		// @formatter:on
		rt.postForEntity(builder.toUriString(), new HttpEntity<>(""), Void.class);

		OghamAssertions.assertThat(greenMail)
			.receivedMessages()
				.count(is(1))
				.message(0)
					.body()
						.contentAsString(isIdenticalHtml(resourceAsString("/thymeleaf/expected/web-beans-and-urls-resolution.html")));
	}

	@Test
	public void smsUsingThymeleafTemplateShouldResolveBeansAndUrls() throws MessagingException, IOException {
		RestTemplate rt = new RestTemplate();
		// @formatter:off
		UriComponentsBuilder builder = UriComponentsBuilder.fromPath("sms")
				.scheme("http")
				.host("localhost")
				.port(port);
		// @formatter:on
		rt.postForEntity(builder.toUriString(), new HttpEntity<>(""), Void.class);

		OghamAssertions.assertThat(smppServer)
			.receivedMessages()
				.count(is(1))
				.message(0)
					.content(is(resourceAsString("/thymeleaf/expected/web-beans-and-urls-resolution.txt")));
	}

	
	@Test
	public void emailUsingThymeleafTemplateInAsyncMethodShouldResolveBeans() throws MessagingException, IOException {
		RestTemplate rt = new RestTemplate();
		// @formatter:off
		UriComponentsBuilder builder = UriComponentsBuilder.fromPath("async/email")
				.scheme("http")
				.host("localhost")
				.port(port)
				.queryParam("template", "web-beans-resolution");
		// @formatter:on
		rt.postForEntity(builder.toUriString(), new HttpEntity<>(""), Void.class);

		await().atMost(5, SECONDS).until(() -> greenMail.getReceivedMessages().length > 0);

		OghamAssertions.assertThat(greenMail)
			.receivedMessages()
				.count(is(1))
				.message(0)
					.body()
						.contentAsString(isIdenticalHtml(resourceAsString("/thymeleaf/expected/web-beans-resolution.html")));
	}

	@Test
	public void smsUsingThymeleafTemplateInAsyncMethodShouldResolveBeans() throws MessagingException, IOException {
		RestTemplate rt = new RestTemplate();
		// @formatter:off
		UriComponentsBuilder builder = UriComponentsBuilder.fromPath("async/sms")
				.scheme("http")
				.host("localhost")
				.port(port)
				.queryParam("template", "web-beans-resolution");
		// @formatter:on
		rt.postForEntity(builder.toUriString(), new HttpEntity<>(""), Void.class);
		
		await().atMost(5, SECONDS).until(() -> smppServer.getReceivedMessages().size() > 0);

		OghamAssertions.assertThat(smppServer)
			.receivedMessages()
				.count(is(1))
				.message(0)
					.content(is(resourceAsString("/thymeleaf/expected/web-beans-resolution.txt")));
	}
	
	@Test
	public void emailUsingThymeleafTemplateInAsyncMethodCantResolveUrls() throws MessagingException, IOException {
		RestTemplate rt = new RestTemplate();
		// @formatter:off
		UriComponentsBuilder builder = UriComponentsBuilder.fromPath("async/email")
				.scheme("http")
				.host("localhost")
				.port(port)
				.queryParam("template", "web-beans-and-urls-resolution");
		// @formatter:on
		rt.postForEntity(builder.toUriString(), new HttpEntity<>(""), Void.class);

		await().atMost(5, SECONDS).until(() -> hasError());

		OghamAssertions.assertThat(greenMail)
			.receivedMessages()
				.count(is(0));
		ErrorDto error = getError();
		assertThat(error.getType()).isEqualTo(MessageNotSentException.class.getSimpleName());
		ErrorDto cause = getRootCause(error.getCause());
		assertThat(cause.getType()).isEqualTo(TemplateProcessingException.class.getSimpleName());
		assertThat(cause.getMessage()).contains("Link base \"/fake/resources/foo.js\" cannot be context relative (/...) unless the context used for executing the engine implements the org.thymeleaf.context.IWebContext");
	}

	@Test
	public void smsUsingThymeleafTemplateInAsyncMethodCantResolveUrls() throws MessagingException, IOException {
		RestTemplate rt = new RestTemplate();
		// @formatter:off
		UriComponentsBuilder builder = UriComponentsBuilder.fromPath("async/sms")
				.scheme("http")
				.host("localhost")
				.port(port)
				.queryParam("template", "web-beans-and-urls-resolution");
		// @formatter:on
		rt.postForEntity(builder.toUriString(), new HttpEntity<>(""), Void.class);

		await().atMost(5, SECONDS).until(() -> hasError());

		OghamAssertions.assertThat(smppServer)
			.receivedMessages()
				.count(is(0));
		ErrorDto error = getError();
		assertThat(error.getType()).isEqualTo(MessageNotSentException.class.getSimpleName());
		ErrorDto cause = getRootCause(error.getCause());
		assertThat(cause.getType()).isEqualTo(TemplateProcessingException.class.getSimpleName());
		assertThat(cause.getMessage()).contains("Link base \"/fake/resources/foo.js\" cannot be context relative (/...) unless the context used for executing the engine implements the org.thymeleaf.context.IWebContext");
	}

	

	private ErrorDto getRootCause(ErrorDto cause) {
		while (cause != null) {
			if (cause.getCause() == null) {
				return cause;
			}
			cause = cause.getCause();
		}
		return null;
	}

	private boolean hasError() {
		return getError() != null;
	}

	private ErrorDto getError() {
		RestTemplate rt = new RestTemplate();
		// @formatter:off
		UriComponentsBuilder builder = UriComponentsBuilder.fromPath("async/error")
				.scheme("http")
				.host("localhost")
				.port(port);
		// @formatter:on
		return rt.postForEntity(builder.toUriString(), new HttpEntity<>(""), ErrorDto.class).getBody();		
	}
	
	@SpringBootApplication
	@EnableAsync
	public static class TestApplication {
		public static void main(String[] args) {
			SpringApplication.run(TestApplication.class, args);
		}

		@Service("fakeService")
		public static class FakeService {
			public String hello(String name) {
				return "hello " + name;
			}
		}
		
		@RestController("fakeController")
		public static class FakeController {
			@RequestMapping("fake/hello/{name}")
			public String hello(@PathVariable String name) {
				return "hello " + name;
			}
			@RequestMapping("fake/resources/{file}")
			public String resource(@PathVariable String file) {
				return "resource: "+file;
			}
		}
		
		@RestController
		public static class MessagingController {
			@Autowired MessagingService messagingService;
			private Exception ex;
			
			@RequestMapping("email")
			public void email() throws MessagingException {
				messagingService.send(new Email()
						.from("[email protected]")
						.to("[email protected]")
						.content(new TemplateContent("/thymeleaf/source/web-beans-and-urls-resolution.html", new SimpleBean("world", 0))));
			}
			
			@RequestMapping("sms")
			public void sms() throws MessagingException {
				messagingService.send(new Sms()
						.from("+33102030405")
						.to("+33123456789")
						.content(new TemplateContent("/thymeleaf/source/web-beans-and-urls-resolution.txt", new SimpleBean("world", 0))));
			}
			
			@RequestMapping("async/email")
			@Async
			public void emailAsync(@RequestParam("template") String templateName) throws MessagingException {
				try {
					messagingService.send(new Email()
							.from("[email protected]")
							.to("[email protected]")
							.content(new TemplateContent("/thymeleaf/source/" + templateName + ".html", new SimpleBean("world", 0))));
				} catch(Exception e) {
					ex = e;
				}
			}
			
			@RequestMapping("async/sms")
			@Async
			public void smsAsync(@RequestParam("template") String templateName) throws MessagingException {
				try {
					messagingService.send(new Sms()
							.from("+33102030405")
							.to("+33123456789")
							.content(new TemplateContent("/thymeleaf/source/" + templateName + ".txt", new SimpleBean("world", 0))));
				} catch(Exception e) {
					ex = e;
				}
			}
			
			@RequestMapping("async/error")
			public ErrorDto getError() {
				return ex == null ? null : new ErrorDto(ex);
			}
		}
	}

	public static class ErrorDto {
		private final String type;
		private final String message;
		private final ErrorDto cause;
		
		@JsonCreator
		public ErrorDto(@JsonProperty("type") String type, @JsonProperty("message") String message, @JsonProperty("cause") ErrorDto cause) {
			super();
			this.type = type;
			this.message = message;
			this.cause = cause;
		}
		public ErrorDto(Throwable e) {
			super();
			this.type = e.getClass().getSimpleName();
			this.message = e.getMessage();
			if (e.getCause() != null) {
				this.cause = new ErrorDto(e.getCause());
			} else {
				this.cause = null;
			}
		}
		public String getType() {
			return type;
		}
		public String getMessage() {
			return message;
		}
		public ErrorDto getCause() {
			return cause;
		}
	}

}