package org.switchyard.component.sca;

import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.TimeUnit;

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.namespace.QName;

import junit.framework.Assert;

import org.junit.Before;
import org.junit.Test;
import org.switchyard.Exchange;
import org.switchyard.MockDomain;
import org.switchyard.ServiceDomain;
import org.switchyard.deploy.internal.Deployment;
import org.switchyard.extensions.java.JavaService;
import org.switchyard.metadata.InOnlyService;
import org.switchyard.remote.RemoteMessage;
import org.switchyard.remote.http.HttpInvoker;
import org.switchyard.serial.FormatType;
import org.switchyard.serial.Serializer;
import org.switchyard.serial.SerializerFactory;
import org.switchyard.test.MockHandler;

public class SwitchYardRemotingServletTest {
    
    private final QName TEST_SERVICE = new QName("RemotingTest");
    
    private Serializer serializer = SerializerFactory.create(FormatType.JSON, null, true);
    private SwitchYardRemotingServlet servlet;
    private HttpServletRequest request;
    private HttpServletResponse response;
    private ServiceDomain domain;
    private InputStream input;
    
    @Before
    public void setUp() throws Exception {
        domain = new MockDomain();
        domain.setProperty(Deployment.CLASSLOADER_PROPERTY, this.getClass().getClassLoader());
        RemoteEndpointPublisher rep = mock(RemoteEndpointPublisher.class);
        when(rep.getDomain(TEST_SERVICE)).thenReturn(domain);
        servlet = new SwitchYardRemotingServlet();
        servlet.setEndpointPublisher(rep);
        request = mock(HttpServletRequest.class);
        ServletInputStream sis = new ServletInputStream() {
            public int read() throws IOException {
                return input.read();
            }
            public int read(byte[] b, int off, int len) throws IOException {
                return input.read(b, off, len);
            }

            public void setReadListener(ReadListener readListener) {
            }

            public boolean isReady() {
                return true;
            }

            public boolean isFinished() {
                return true;
            }

        };
        when(request.getInputStream()).thenReturn(sis);
        when(request.getHeader(HttpInvoker.SERVICE_HEADER)).thenReturn(TEST_SERVICE.toString());
        response = mock(HttpServletResponse.class);
    }

    @Test
    public void missingServiceName() {
        try {
            when(request.getHeader(HttpInvoker.SERVICE_HEADER)).thenReturn(null);
            servlet.doPost(request, response);
            Assert.fail("Expected failure due to missing service name on message");
        } catch (Exception ex) {
            // success
        }
    }
    
    @Test
    public void noOperationName() throws Exception {
        domain.registerServiceReference(TEST_SERVICE, new InOnlyService());
        domain.registerService(TEST_SERVICE, new InOnlyService(), new MockHandler());
        RemoteMessage msg = new RemoteMessage()
            .setService(TEST_SERVICE);
        setRequestMessage(msg);
        servlet.doPost(request, response);
    }
    
    @Test
    public void noOperationNameMultipleOperations() throws Exception {
        MockHandler handler = new MockHandler();
        domain.registerServiceReference(TEST_SERVICE, JavaService.fromClass(MyInterface.class));
        domain.registerService(TEST_SERVICE, JavaService.fromClass(MyInterface.class), handler);
        
        RemoteMessage msg = new RemoteMessage()
            .setService(TEST_SERVICE);
        setRequestMessage(msg);
        
        try {
            servlet.doPost(request, response);
            Assert.fail("No operation was supplied with multiple service operations - this should fail!");
        } catch (Exception ex) {
            // expected
        }
    }
    
    @Test
    public void operationNameMultipleOperations() throws Exception {
        MockHandler handler = new MockHandler();
        domain.registerServiceReference(TEST_SERVICE, JavaService.fromClass(MyInterface.class));
        domain.registerService(TEST_SERVICE, JavaService.fromClass(MyInterface.class), handler);
        
        RemoteMessage msg = new RemoteMessage()
            .setService(TEST_SERVICE)
            .setOperation("bar");
        setRequestMessage(msg);
        servlet.doPost(request, response);
        
        Exchange ex = handler.getMessages().poll(300, TimeUnit.MILLISECONDS);
        Assert.assertNotNull(ex);
        Assert.assertEquals("bar", ex.getContract().getConsumerOperation().getName());
    }
    
    private void setRequestMessage(RemoteMessage message) throws Exception {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        serializer.serialize(message, RemoteMessage.class, bos);
        bos.flush();
        input = new ByteArrayInputStream(bos.toByteArray());
    }
    
}

interface MyInterface {
    void foo(String arg);
    void bar(String arg);
}
