package marketing.heyday.hartmann.fotodocumentation.rest.jackson;

import java.io.*;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.networknt.schema.JsonSchema;
import com.networknt.schema.JsonSchemaFactory;
import com.networknt.schema.ValidationMessage;

import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.WebApplicationException;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.MultivaluedMap;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.Response.Status;
import jakarta.ws.rs.ext.MessageBodyReader;
import jakarta.ws.rs.ext.Provider;

/**
 * 
 * 
 * <p>Copyright: Copyright (c) 2024</p>
 * <p>Company: heyday Marketing GmbH</p>
 * @author <a href="mailto:p.verboom@heyday.marketing">Patrick Verboom</a>
 * @version 1.0
 * 
 * created: 6 Dec 2024
 */
@Provider
@Consumes(value = {
		MediaType.APPLICATION_JSON, "application/json",
		MediaType.APPLICATION_JSON, "application/json; charset=utf-8",
})
public class ValidatedMessageBodyReader implements MessageBodyReader<SchemaValidated> {
	private static final Log LOG = LogFactory.getLog(ValidatedMessageBodyReader.class);

	/* (non-Javadoc)
	 * @see jakarta.ws.rs.ext.MessageBodyReader#isReadable(java.lang.Class, java.lang.reflect.Type, java.lang.annotation.Annotation[], jakarta.ws.rs.core.MediaType)
	 */
	@Override
	public boolean isReadable(Class<?> classType, Type type, Annotation[] annotations, MediaType mediaType) {
		if (mediaType.getType().contains("application/json")) {
			return false;
		}
		return Arrays.stream(annotations).anyMatch(a -> a.annotationType() == JsonSchemaValidate.class);
	}

	/* (non-Javadoc)
	 * @see jakarta.ws.rs.ext.MessageBodyReader#readFrom(java.lang.Class, java.lang.reflect.Type, java.lang.annotation.Annotation[], jakarta.ws.rs.core.MediaType, jakarta.ws.rs.core.MultivaluedMap, java.io.InputStream)
	 */
	@Override
	public SchemaValidated readFrom(Class<SchemaValidated> classType, Type type, Annotation[] annotations, MediaType mediaType,
			MultivaluedMap<String, String> httpHeaders, InputStream input) throws IOException {
		final String jsonData = read(input);

		Optional<JsonSchemaValidate> annotation = Arrays.stream(annotations).filter(a -> a.annotationType() == JsonSchemaValidate.class).map(a -> (JsonSchemaValidate) a).findAny();
		if (annotation.isPresent()) {
			ValidationReply reply = validate(annotation.get(), jsonData);
			if (!reply.success()) {
				var response = Response.status(Status.BAD_REQUEST).entity(reply.errors()).build();
				throw new WebApplicationException(response);
			}
		}

		ObjectMapper objectMapper = new ObjectMapper();
		return objectMapper.readValue(new StringReader(jsonData), classType);
	}

	/**
	 * @param jsonSchema
	 * @param jsonData
	 * @return
	 */
	private ValidationReply validate(JsonSchemaValidate jsonSchema, String jsonData) {
		String schemaPath = jsonSchema.value();
		try {
			JsonSchema schema = getJsonSchema(schemaPath);
			JsonNode node = getJsonNode(jsonData);
			Set<ValidationMessage> errors = schema.validate(node);
			if (!errors.isEmpty()) {
				LOG.error("Failed to validate json to schema " + schemaPath);
				errors.forEach(LOG::error);
			}
			return new ValidationReply(errors.isEmpty(), errors);
		} catch (IOException e) {
			LOG.error(e.getMessage(), e);
			return new ValidationReply(false, new HashSet<>());
		}
	}

	protected JsonSchema getJsonSchema(String name) throws IOException {
		JsonSchemaFactory factory = new JsonSchemaFactory();
		try (InputStream input = Thread.currentThread().getContextClassLoader().getResourceAsStream(name);) {
			return factory.getSchema(input);
		}
	}

	protected JsonNode getJsonNode(String content) throws IOException {
		return new ObjectMapper().readTree(content);
	}

	private String read(InputStream input) throws IOException {
		try (BufferedReader buffer = new BufferedReader(new InputStreamReader(input, Charset.forName("UTF-8")))) {
			return buffer.lines().collect(Collectors.joining("\n"));
		}
	}
}
