package com.rubn.xsdvalidator.service;

import com.rubn.xsdvalidator.util.InMemoryXsdResourceResolver;
import com.rubn.xsdvalidator.util.XmlValidationErrorHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.util.function.Tuple2;

import javax.xml.XMLConstants;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;

/**
 * @author rubn
 */
@Slf4j
@Service
public class ValidationXsdSchemaService {

    public Flux<List<String>> validateXmlInputWithXsdSchema(final byte[] inputXml, final byte[] inputXsdSchema,
                                                      final Map<String, byte[]> mapPrefixFileNameAndContent) {
        return this.loadSchema(inputXsdSchema, mapPrefixFileNameAndContent)
                .map(Schema::newValidator)
                .flatMap(this::buildXmlValidatorErrorHandler)
                .flatMap(tuple -> this.buildValidator(tuple, inputXml))
                .flatMapMany(Mono::just);
    }

    private Mono<Schema> loadSchema(final byte[] inputXsdSchema, final Map<String, byte[]> mapPrefixFileNameAndContent) {
        final SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
        try (var bufferedInputStream = new BufferedInputStream(new ByteArrayInputStream(inputXsdSchema))) {
            schemaFactory.setResourceResolver(new InMemoryXsdResourceResolver(mapPrefixFileNameAndContent));
            return Mono.just(schemaFactory.newSchema(new StreamSource(bufferedInputStream)));
        } catch (Exception ex) {
            return Mono.error(ex);
        }
    }

    private Mono<Tuple2<Validator, XmlValidationErrorHandler>> buildXmlValidatorErrorHandler(Validator validator) {
        final XmlValidationErrorHandler xmlValidationErrorHandler = new XmlValidationErrorHandler();
        validator.setErrorHandler(xmlValidationErrorHandler);
        return Mono.zip(Mono.just(validator), Mono.just(xmlValidationErrorHandler));
    }

    private Mono<List<String>> buildValidator(Tuple2<Validator, XmlValidationErrorHandler> tuple, byte[] inputXml) {
        final Validator validator = tuple.getT1();
        final XmlValidationErrorHandler xmlValidationErrorHandler = tuple.getT2();
        try (var bufferedInputStream = new BufferedInputStream(new ByteArrayInputStream(inputXml))) {
            validator.validate(new StreamSource(bufferedInputStream));
            return Mono.just(xmlValidationErrorHandler.getExceptions());
        } catch (Exception e) {
            return Mono.error(e);
        }
    }

    public List<String> detectWords(List<String> exceptions) {
        return exceptions
                .stream()
                .flatMap(item -> Stream.of(item.split(StringUtils.SPACE)))
                .toList();
    }

}
