/**
 * Copyright (c) 2017-2024 Linagora
 * 
 * This program/library is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 2.1 of the License, or (at your
 * option) any later version.
 * 
 * This program/library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
 * for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program/library; If not, see http://www.gnu.org/licenses/
 * for the GNU Lesser General Public License version 2.1.
 */
package org.ow2.petals.binding.rest.exchange;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;

import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.text.ParseException;

import javax.ws.rs.core.MediaType;
import javax.xml.stream.FactoryConfigurationError;
import javax.xml.stream.XMLStreamException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.stream.StreamSource;

import org.apache.commons.io.IOUtils;
import org.junit.Test;

import com.ebmwebsourcing.easycommons.stream.EasyByteArrayOutputStream;

import de.odysseus.staxon.json.JsonXMLConfigBuilder;
import de.odysseus.staxon.json.JsonXMLOutputFactory;

/**
 * Unit tests about utility class {@link HTTPHelper}
 * 
 * @author Christophe DENEUX - Linagora
 *
 */
public class HTTPHelperTest {

    /**
     * Unit test about method {@link HTTPHelper#isXMLMediaType(javax.ws.rs.core.MediaType)}.
     */
    @Test
    public void isXMLMediaType_fromMediaType() {

        assertTrue(HTTPHelper.isXMLMediaType(MediaType.APPLICATION_XML_TYPE));
        assertTrue(HTTPHelper.isXMLMediaType(MediaType.APPLICATION_ATOM_XML_TYPE));
        assertTrue(HTTPHelper.isXMLMediaType(MediaType.APPLICATION_SVG_XML_TYPE));
        assertTrue(HTTPHelper.isXMLMediaType(MediaType.APPLICATION_XHTML_XML_TYPE));
        assertTrue(HTTPHelper.isXMLMediaType(MediaType.TEXT_XML_TYPE));

        assertFalse(HTTPHelper.isXMLMediaType(MediaType.APPLICATION_JSON_TYPE));
        assertFalse(HTTPHelper.isXMLMediaType(MediaType.APPLICATION_OCTET_STREAM_TYPE));
        assertFalse(HTTPHelper.isXMLMediaType(MediaType.MULTIPART_FORM_DATA_TYPE));

    }

    /**
     * Unit test about method {@link HTTPHelper#findAcceptedMediaType(String)}.
     */
    @Test
    public void findAcceptedMediaType() throws ParseException {

        // Header 'Accept' set to explicit expressions without wild-card
        {
            final MediaType acceptedMediaType = HTTPHelper.findAcceptedMediaType(MediaType.APPLICATION_XML);
            assertEquals(MediaType.APPLICATION_XML_TYPE,
                    new MediaType(acceptedMediaType.getType(), acceptedMediaType.getSubtype()));
        }
        {
            final MediaType acceptedMediaType = HTTPHelper.findAcceptedMediaType("application/xml,application/json");
            assertEquals(MediaType.APPLICATION_XML_TYPE,
                    new MediaType(acceptedMediaType.getType(), acceptedMediaType.getSubtype()));
        }
        {
            final MediaType acceptedMediaType = HTTPHelper
                    .findAcceptedMediaType("application/xml,application/json;q=0.9");
            assertEquals(MediaType.APPLICATION_XML_TYPE,
                    new MediaType(acceptedMediaType.getType(), acceptedMediaType.getSubtype()));
        }
        {
            final MediaType acceptedMediaType = HTTPHelper.findAcceptedMediaType("image/jpeg,application/json;q=0.9");
            assertEquals(MediaType.APPLICATION_JSON_TYPE,
                    new MediaType(acceptedMediaType.getType(), acceptedMediaType.getSubtype()));
        }
        {
            final MediaType acceptedMediaType = HTTPHelper
                    .findAcceptedMediaType("application/xml;q=0.8,application/json;q=0.9");
            assertEquals(MediaType.APPLICATION_JSON_TYPE,
                    new MediaType(acceptedMediaType.getType(), acceptedMediaType.getSubtype()));
        }
        {
            assertNull(HTTPHelper.findAcceptedMediaType("image/jpeg"));
        }

        // Header 'Accept' set to sub-type wild-carded expressions
        {
            final MediaType acceptedMediaType = HTTPHelper.findAcceptedMediaType("application/*");
            assertEquals(MediaType.APPLICATION_JSON_TYPE,
                    new MediaType(acceptedMediaType.getType(), acceptedMediaType.getSubtype()));
        }
        {
            final MediaType acceptedMediaType = HTTPHelper.findAcceptedMediaType("text/*");
            assertEquals(MediaType.TEXT_XML_TYPE,
                    new MediaType(acceptedMediaType.getType(), acceptedMediaType.getSubtype()));
        }
        {
            assertNull(HTTPHelper.findAcceptedMediaType("image/*"));
        }
        {
            final MediaType acceptedMediaType = HTTPHelper.findAcceptedMediaType("image/*, text/*");
            assertEquals(MediaType.TEXT_XML_TYPE,
                    new MediaType(acceptedMediaType.getType(), acceptedMediaType.getSubtype()));
        }

        // Header 'Accept' set to full wild-carded expressions
        {
            final MediaType acceptedMediaType = HTTPHelper.findAcceptedMediaType("");
            assertEquals(MediaType.APPLICATION_JSON_TYPE,
                    new MediaType(acceptedMediaType.getType(), acceptedMediaType.getSubtype()));
        }
        {
            final MediaType acceptedMediaType = HTTPHelper.findAcceptedMediaType(null);
            assertEquals(MediaType.APPLICATION_JSON_TYPE,
                    new MediaType(acceptedMediaType.getType(), acceptedMediaType.getSubtype()));
        }
        {
            final MediaType acceptedMediaType = HTTPHelper.findAcceptedMediaType(MediaType.WILDCARD);
            assertEquals(MediaType.APPLICATION_JSON_TYPE,
                    new MediaType(acceptedMediaType.getType(), acceptedMediaType.getSubtype()));
        }
    }

    /**
     * Unit test about method {@link HTTPHelper#isLoggable(MediaType)}.
     */
    @Test
    public void isLoggable() {
        assertTrue(HTTPHelper.isLoggable(MediaType.APPLICATION_XML_TYPE));
        assertTrue(HTTPHelper.isLoggable(MediaType.APPLICATION_ATOM_XML_TYPE));
        assertTrue(HTTPHelper.isLoggable(MediaType.APPLICATION_JSON_TYPE));
        assertTrue(HTTPHelper.isLoggable(MediaType.APPLICATION_XHTML_XML_TYPE));
        assertFalse(HTTPHelper.isLoggable(MediaType.APPLICATION_OCTET_STREAM_TYPE));

        assertTrue(HTTPHelper.isLoggable(MediaType.TEXT_XML_TYPE));
        assertTrue(HTTPHelper.isLoggable(MediaType.TEXT_HTML_TYPE));
        assertTrue(HTTPHelper.isLoggable(MediaType.TEXT_PLAIN_TYPE));

        assertFalse(HTTPHelper.isLoggable(new MediaType("image", "jpeg")));

        assertFalse(HTTPHelper.isLoggable(new MediaType("", "")));
        assertFalse(HTTPHelper.isLoggable((MediaType) null));
    }

    /**
     * <p>
     * Verify the conversion of an XML array into JSON.
     * </p>
     * <p>
     * For arrays, we recommend to not use a 'virtual-root' otherwise problem can occurs when array is empty. The result
     * is 'null' instead of something like '{"elements":null}'.
     * </p>
     */
    @Test
    public void convertXMLArray2JSON()
            throws IOException, TransformerException, XMLStreamException, FactoryConfigurationError {

        final JsonXMLConfigBuilder xml2JsonConverterCfgBuilder = new JsonXMLConfigBuilder();
        xml2JsonConverterCfgBuilder.autoArray(true);

        // -----------------
        // Not empty array
        // -----------------
        {
            final StringBuilder nonEmptyXMLArray = new StringBuilder();
            nonEmptyXMLArray.append("<elements>");
            nonEmptyXMLArray.append("<element>");
            nonEmptyXMLArray.append("<sub-element>");
            nonEmptyXMLArray.append("value 1");
            nonEmptyXMLArray.append("</sub-element>");
            nonEmptyXMLArray.append("</element>");
            nonEmptyXMLArray.append("<element>");
            nonEmptyXMLArray.append("<sub-element>");
            nonEmptyXMLArray.append("value 2");
            nonEmptyXMLArray.append("</sub-element>");
            nonEmptyXMLArray.append("</element>");
            nonEmptyXMLArray.append("</elements>");

            final EasyByteArrayOutputStream ebaos = new EasyByteArrayOutputStream();
            try (final InputStream is = IOUtils.toInputStream(nonEmptyXMLArray, Charset.defaultCharset())) {
                HTTPHelper.streamHTTPBody(new StreamSource(is),
                        new JsonXMLOutputFactory(xml2JsonConverterCfgBuilder.build()), ebaos,
                        MediaType.APPLICATION_JSON_TYPE);
            }

            assertEquals("{\"elements\":{\"element\":[{\"sub-element\":\"value 1\"},{\"sub-element\":\"value 2\"}]}}",
                    ebaos.toString());
        }

        // -------------
        // Empty array
        // -------------
        {
            final String emptyXMLArray = "<elements />";

            final EasyByteArrayOutputStream ebaos = new EasyByteArrayOutputStream();
            try (final InputStream is = IOUtils.toInputStream(emptyXMLArray, Charset.defaultCharset())) {
                HTTPHelper.streamHTTPBody(new StreamSource(is),
                        new JsonXMLOutputFactory(xml2JsonConverterCfgBuilder.build()), ebaos,
                        MediaType.APPLICATION_JSON_TYPE);
            }

            assertEquals("{\"elements\":null}", ebaos.toString());
        }
    }

    /**
     * <p>
     * Verify the conversion of an XML payload containing namespaces into JSON where the JSON conversion excludes
     * namespaces.
     * </p>
     * <p>
     * Expected results:
     * </p>
     * <ul>
     * <li>no error,</li>
     * <li>if JSON conversion configuration excludes namespaces declarations, no namespace declaration occurs into the
     * generated JSON,</li>
     * <li>if JSON conversion configuration includes namespaces declarations, namespace declarations occur into the
     * generated JSON,</li>
     * </ul>
     */
    @Test
    public void convertXML2JSON_namespaces()
            throws IOException, TransformerException, XMLStreamException, FactoryConfigurationError {

        final StringBuilder xmlPayload = new StringBuilder();
        xmlPayload.append("<tns1:elements xmlns:tns1=\"http://namespace-1\" xmlns:tns2=\"http://namespace-2\">");
        xmlPayload.append("<tns1:element>");
        xmlPayload.append("<tns2:sub-element>");
        xmlPayload.append("value 1");
        xmlPayload.append("</tns2:sub-element>");
        xmlPayload.append("</tns1:element>");
        xmlPayload.append("<tns1:element>");
        xmlPayload.append("<tns2:sub-element>");
        xmlPayload.append("value 2");
        xmlPayload.append("</tns2:sub-element>");
        xmlPayload.append("</tns1:element>");
        xmlPayload.append("</tns1:elements>");

        {
            final JsonXMLConfigBuilder xml2JsonConverterCfgBuilder = new JsonXMLConfigBuilder();
            xml2JsonConverterCfgBuilder.namespaceDeclarations(false);

            final EasyByteArrayOutputStream ebaos = new EasyByteArrayOutputStream();
            try (final InputStream is = IOUtils.toInputStream(xmlPayload, Charset.defaultCharset())) {
                HTTPHelper.streamHTTPBody(new StreamSource(is),
                        new JsonXMLOutputFactory(xml2JsonConverterCfgBuilder.build()), ebaos,
                        MediaType.APPLICATION_JSON_TYPE);
            }

            assertEquals(
                    "{\"tns1:elements\":{\"tns1:element\":{\"tns2:sub-element\":\"value 1\"},\"tns1:element\":{\"tns2:sub-element\":\"value 2\"}}}",
                    ebaos.toString());
        }

        {
            final JsonXMLConfigBuilder xml2JsonConverterCfgBuilder = new JsonXMLConfigBuilder();
            xml2JsonConverterCfgBuilder.namespaceDeclarations(true);

            final EasyByteArrayOutputStream ebaos = new EasyByteArrayOutputStream();
            try (final InputStream is = IOUtils.toInputStream(xmlPayload, Charset.defaultCharset())) {
                HTTPHelper.streamHTTPBody(new StreamSource(is),
                        new JsonXMLOutputFactory(xml2JsonConverterCfgBuilder.build()), ebaos,
                        MediaType.APPLICATION_JSON_TYPE);
            }

            assertEquals(
                    "{\"tns1:elements\":{\"@xmlns:tns1\":\"http://namespace-1\",\"@xmlns:tns2\":\"http://namespace-2\",\"tns1:element\":{\"tns2:sub-element\":\"value 1\"},\"tns1:element\":{\"tns2:sub-element\":\"value 2\"}}}",
                    ebaos.toString());
        }
    }

}
