/*
 * Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,  
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.switchyard.component.http;

import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.net.InetSocketAddress;
import java.util.HashSet;
import java.util.Set;

import javax.xml.namespace.QName;

import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.jboss.com.sun.net.httpserver.HttpContext;
import org.jboss.com.sun.net.httpserver.HttpExchange;
import org.jboss.com.sun.net.httpserver.HttpHandler;
import org.jboss.com.sun.net.httpserver.HttpServer;
import org.switchyard.Exchange;
import org.switchyard.Message;
import org.switchyard.ServiceDomain;
import org.switchyard.bus.camel.CamelExchange;
import org.switchyard.component.http.composer.HttpRequestBindingData;
import org.switchyard.component.http.composer.HttpComposition;
import org.switchyard.component.http.composer.HttpContextMapper;
import org.switchyard.component.http.config.model.HttpBindingModel;
import org.switchyard.component.test.mixins.http.HTTPMixIn;
import org.switchyard.config.model.ModelPuller;
import org.switchyard.config.model.composite.CompositeModel;
import org.switchyard.config.model.composite.CompositeReferenceModel;
import org.switchyard.config.model.composite.CompositeServiceModel;
import org.switchyard.metadata.BaseService;
import org.switchyard.metadata.InOnlyOperation;
import org.switchyard.metadata.InOnlyService;
import org.switchyard.metadata.InOutOperation;
import org.switchyard.metadata.InOutService;
import org.switchyard.metadata.ServiceOperation;
import org.switchyard.test.Invoker;
import org.switchyard.test.MockHandler;
import org.switchyard.test.SwitchYardRunner;
import org.switchyard.test.SwitchYardTestCaseConfig;

/**
 * Contains tests for HTTP Gateway.
 *
 * @author Magesh Kumar B <mageshbk@jboss.com> (C) 2012 Red Hat Inc.
 */
@RunWith(SwitchYardRunner.class)
@SwitchYardTestCaseConfig(mixins = HTTPMixIn.class)
public class HttpGatewayTest {

    private static final String METHOD_NAME = "sayHello";
    private static final String INPUT = "magesh";
    private static final String OUTPUT = "response to " + INPUT;
    private static final QName STRING_QNAME = new QName("java:java.lang.String");
    private static ModelPuller<CompositeModel> _puller;
    private ServiceDomain _domain;
    private HTTPMixIn httpMixIn;

    @org.switchyard.test.ServiceOperation("{urn:http:test:1.1}SampleHttpConsumerService")
    private Invoker _consumerService;

    @org.switchyard.test.ServiceOperation("{urn:http:test:1.1}InvalidHttpConsumerService")
    private Invoker _consumerService2;

    @org.switchyard.test.ServiceOperation("{urn:http:test:1.1}AuthHttpConsumerService")
    private Invoker _consumerService3;

    @org.switchyard.test.ServiceOperation("{urn:http:test:1.1}NtlmHttpConsumerService")
    private Invoker _consumerService4;

    @org.switchyard.test.ServiceOperation("{urn:http:test:1.1}TimeoutHttpConsumerService")
    private Invoker _consumerService5;

    @org.switchyard.test.ServiceOperation("{urn:http:test:1.1}OneWayHttpConsumerService")
    private Invoker _inOnlyConsumerService;

    private InboundHandler _httpInbound;
    private InboundHandler _httpInbound2;
    private OutboundHandler _httpOutbound;
    private OutboundHandler _httpOutbound2;
    private OutboundHandler _httpOutbound3;
    private OutboundHandler _httpOutbound4;
    private OutboundHandler _httpOutbound5;
    private final MockHandler mockService = new MockHandler().forwardInToOut();

    @Before
    public void setUp() throws Exception {
        _puller = new ModelPuller<CompositeModel>();
        CompositeModel composite = _puller.pull("/HelloSwitchYard.xml", getClass());
        composite.assertModelValid();

        CompositeServiceModel compositeService = composite.getServices().get(0);
        HttpBindingModel config = (HttpBindingModel)compositeService.getBindings().get(0);

        // Massive hack for Test Runner. Register both a service and a reference binding.
        _domain.registerService(config.getServiceName(), new InOutService(), mockService);
        _domain.registerServiceReference(config.getServiceName(), new HelloInterface());
        _httpInbound = new InboundHandler(config, _domain);
        _httpInbound.start();

        compositeService = composite.getServices().get(1);
        HttpBindingModel config2 = (HttpBindingModel)compositeService.getBindings().get(0);

        _domain.registerService(config2.getServiceName(), new InOnlyService(), new MockHandler());
        _domain.registerServiceReference(config2.getServiceName(), new HelloInterface());
        _httpInbound2 = new InboundHandler(config2, _domain);
        _httpInbound2.start();

        CompositeReferenceModel compositeReference = composite.getReferences().get(0);
        HttpBindingModel configRef = (HttpBindingModel)compositeReference.getBindings().get(0);

        _httpOutbound = new OutboundHandler(configRef, null);
        _domain.registerService(configRef.getServiceName(), new HelloInterface(), _httpOutbound);
        _httpOutbound.start();

        compositeReference = composite.getReferences().get(1);
        HttpBindingModel configRef2 = (HttpBindingModel)compositeReference.getBindings().get(0);
        _httpOutbound2 = new OutboundHandler(configRef2, null);
        _domain.registerService(configRef2.getServiceName(), new HelloInterface(), _httpOutbound2);
        _httpOutbound2.start();

        compositeReference = composite.getReferences().get(2);
        HttpBindingModel configRef3 = (HttpBindingModel)compositeReference.getBindings().get(0);
        _httpOutbound3 = new OutboundHandler(configRef3, null);
        _domain.registerService(configRef3.getServiceName(), new HelloInterface(), _httpOutbound3);
        _httpOutbound3.start();

        compositeReference = composite.getReferences().get(3);
        HttpBindingModel configRef4 = (HttpBindingModel)compositeReference.getBindings().get(0);
        _httpOutbound4 = new OutboundHandler(configRef4, null);
        _domain.registerService(configRef4.getServiceName(), new HelloInterface(), _httpOutbound4);
        _httpOutbound4.start();

        compositeReference = composite.getReferences().get(4);
        HttpBindingModel configRef5 = (HttpBindingModel)compositeReference.getBindings().get(0);
        _httpOutbound5 = new OutboundHandler(configRef5, null);
        _domain.registerService(configRef5.getServiceName(), new HelloInterface(), _httpOutbound5);
        _httpOutbound5.start();
    }

    @After
    public void tearDown() throws Exception {
        _httpInbound.stop();
        _httpInbound2.stop();
        _httpOutbound.stop();
        _httpOutbound2.stop();
        _httpOutbound3.stop();
        _httpOutbound4.stop();
        _httpOutbound5.stop();
    }

    @Test
    public void httpGatewayServiceTest() throws Exception {
        String response = httpMixIn.sendString("http://localhost:8080/http", INPUT, HTTPMixIn.HTTP_POST);
        //Test CSR - Composing a Service Request
        CamelExchange ex = (CamelExchange)mockService.getMessages().peek();
        Assert.assertEquals(String.class, ex.getMessage().getContent().getClass());
        Assert.assertEquals(1, mockService.getMessages().size());
        Assert.assertEquals(INPUT, response);
        Assert.assertEquals("UTF-8", ex.getExchange().getProperty(org.apache.camel.Exchange.CHARSET_NAME));
    }

    @Test
    public void httpOneWayStatusTest() throws Exception {
        int status = httpMixIn.sendStringAndGetStatus("http://localhost:8080/oneway", INPUT, HTTPMixIn.HTTP_POST);
        Assert.assertEquals(202, status);
    }

    @Test
    public void httpGatewayReferenceTest() throws Exception {
        Message responseMsg = _consumerService.operation(METHOD_NAME).sendInOut(INPUT);
        //Test CRR - Composing an Reference Response
        Assert.assertEquals(String.class, responseMsg.getContent().getClass());
        Assert.assertEquals(INPUT, responseMsg.getContent());
    }

    @Test
    public void httpStatus() throws Exception {
        MockHandler handler = new MockHandler();
        Exchange ex = _consumerService.operation(METHOD_NAME).createExchange(handler);
        Message requestMsg = ex.createMessage().setContent(INPUT);
        requestMsg.getContext().setProperty("SomeRequestHeader", "BAR");
        ex.send(requestMsg);
        handler.waitForOKMessage();
        Assert.assertEquals(200, ex.getContext().getProperty(HttpContextMapper.HTTP_RESPONSE_STATUS).getValue());
    }

    @Test
    public void httpFault() throws Exception {
        MockHandler handler = new MockHandler();
        Exchange ex = _consumerService2.operation(METHOD_NAME).createExchange(handler);
        Message requestMsg = ex.createMessage().setContent(INPUT);
        ex.send(requestMsg);
        handler.waitForFaultMessage();
        Assert.assertEquals(404, ex.getContext().getProperty(HttpContextMapper.HTTP_RESPONSE_STATUS).getValue());
    }

    @Test
    public void httpTimeout() throws Exception {
        HttpServer httpServer = HttpServer.create(new InetSocketAddress(8090), 10);
        httpServer.setExecutor(null); // creates a default executor
        httpServer.start();
        HttpContext httpContext = httpServer.createContext("/forever", new HttpHandler() {
            public void handle(HttpExchange exchange) {
                    try {
                        Thread.sleep(10000);
                    } catch (InterruptedException ie) {
                        //Ignore
            }}});
        try {
            Message responseMsg = _consumerService5.operation(METHOD_NAME).sendInOut(INPUT);
        } catch (Exception e) {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            e.printStackTrace(new PrintStream(baos));
            Assert.assertTrue(baos.toString().contains("SocketTimeoutException: Read timed out"));
        }
        httpServer.stop(0);
    }

    @Test
    public void authentication() throws Exception {
        HttpServer httpServer = HttpServer.create(new InetSocketAddress(8100), 10);
        httpServer.setExecutor(null); // creates a default executor
        httpServer.start();
        HttpContext context = httpServer.createContext("/basic-secured-endpoint", new StandaloneHandler());
        context.setAuthenticator(new HttpBasicAuthenticator());
        Message responseMsg = _consumerService3.operation(METHOD_NAME).sendInOut(INPUT);
        Assert.assertEquals(OUTPUT, responseMsg.getContent(String.class));
        httpServer.stop(0);
    }

    @Ignore // Exclusively for Magesh ;)
    @Test
    public void ntlmAuthentication() throws Exception {
        Message responseMsg = _consumerService4.operation(METHOD_NAME).sendInOut(INPUT);
        Assert.assertEquals(OUTPUT, responseMsg.getContent(String.class));
    }

    private static class HelloInterface extends BaseService {
        private static Set<ServiceOperation> _operations = new HashSet<ServiceOperation>(2);
        static {
            _operations.add(new InOutOperation(METHOD_NAME, STRING_QNAME, STRING_QNAME));
            _operations.add(new InOnlyOperation("oneWay", STRING_QNAME));
        }
        public HelloInterface() {
            super(_operations);
        }
    }

    private static class StandaloneHandler implements HttpHandler {

        public void handle(HttpExchange exchange) {
            try {
                HttpRequestBindingData httpRequest = new HttpRequestBindingData();
                httpRequest.setBodyFromStream(exchange.getRequestBody());
                Assert.assertEquals(INPUT, httpRequest.getBodyAsString());
                httpRequest.setBody(OUTPUT);
                exchange.sendResponseHeaders(200, httpRequest.getBodyBytes().available());
                httpRequest.writeBodyToStream(exchange.getResponseBody());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

