/*
 * JBoss, Home of Professional Open Source.
 * See the COPYRIGHT.txt file distributed with this work for information
 * regarding copyright ownership.  Some portions may be licensed
 * to Red Hat, Inc. under one or more contributor license agreements.
 * 
 * This 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 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 library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301 USA.
 */
package org.teiid.translator.odata4;

import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.StringReader;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.activation.DataSource;
import javax.xml.ws.Dispatch;
import javax.xml.ws.Service.Mode;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.http.HTTPBinding;

import org.apache.olingo.commons.api.edm.provider.CsdlComplexType;
import org.apache.olingo.commons.api.edm.provider.CsdlReturnType;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import org.teiid.cdk.api.TranslationUtility;
import org.teiid.core.types.InputStreamFactory.ClobInputStreamFactory;
import org.teiid.core.util.ObjectConverterUtil;
import org.teiid.core.util.UnitTestUtil;
import org.teiid.language.Call;
import org.teiid.language.Command;
import org.teiid.language.QueryExpression;
import org.teiid.metadata.MetadataFactory;
import org.teiid.translator.ExecutionContext;
import org.teiid.translator.ProcedureExecution;
import org.teiid.translator.ResultSetExecution;
import org.teiid.translator.TranslatorException;
import org.teiid.translator.UpdateExecution;
import org.teiid.translator.WSConnection;

@SuppressWarnings({"nls", "unused"})
public class TestODataUpdateExecution {

    private UpdateExecution helpExecute(MetadataFactory mf, String query, String expectedPayload,
            final String resultJson, String expectedURL, String expectedMethod, int responseCode)
            throws Exception {

        ODataExecutionFactory translator = new ODataExecutionFactory();
        translator.start();
        
        TranslationUtility utility = new TranslationUtility(
                TestODataMetadataProcessor.getTransformationMetadata(mf,translator));
        
		Command cmd = utility.parseCommand(query);
		ExecutionContext context = Mockito.mock(ExecutionContext.class);
		WSConnection connection = Mockito.mock(WSConnection.class);
		
		Map<String, Object> headers = new HashMap<String, Object>();
		headers.put(MessageContext.HTTP_REQUEST_HEADERS, new HashMap<String, List<String>>());
		headers.put(WSConnection.STATUS_CODE, new Integer(responseCode));
		
		Dispatch<DataSource> dispatch = Mockito.mock(Dispatch.class);
		Mockito.stub(dispatch.getRequestContext()).toReturn(headers);
		Mockito.stub(dispatch.getResponseContext()).toReturn(headers);
		
		Mockito.stub(connection.createDispatch(Mockito.eq(HTTPBinding.HTTP_BINDING), Mockito.anyString(), 
		        Mockito.eq(DataSource.class), Mockito.eq(Mode.MESSAGE))).toReturn(dispatch);
		
		DataSource ds = new DataSource() {
			@Override
			public OutputStream getOutputStream() throws IOException {
				return new ByteArrayOutputStream();
			}
			@Override
			public String getName() {
				return "result";
			}
			@Override
			public InputStream getInputStream() throws IOException {
				ByteArrayInputStream in = new ByteArrayInputStream(resultJson.getBytes());
				return in;
			}
			@Override
			public String getContentType() {
				return "application/json";
			}
		};
		ArgumentCaptor<DataSource> data = ArgumentCaptor.forClass(DataSource.class);
		Mockito.stub(dispatch.invoke(data.capture())).toReturn(ds);
		
		UpdateExecution execution = translator
                .createUpdateExecution(cmd, context,
                        utility.createRuntimeMetadata(), connection);
		execution.execute();
		
		ArgumentCaptor<String> endpoint = ArgumentCaptor.forClass(String.class);
		ArgumentCaptor<String> binding = ArgumentCaptor.forClass(String.class);
		
		
        Mockito.verify(connection).createDispatch(binding.capture(),
                endpoint.capture(), Mockito.eq(DataSource.class),
                Mockito.eq(Mode.MESSAGE));
		assertEquals(expectedURL, URLDecoder.decode(endpoint.getValue(), "utf-8"));
		String payload = new String(ObjectConverterUtil.convertToByteArray(
		        ((ClobInputStreamFactory)data.getValue()).getInputStream()));
		assertEquals(expectedPayload, payload);
		assertEquals(expectedMethod, dispatch.getRequestContext().get(MessageContext.HTTP_REQUEST_METHOD));
		return execution;
	}

    
	@Test
	public void testInsertEntitySet() throws Exception {
		String query = "INSERT INTO People(UserName,FirstName,LastName, EMails, Gender, Concurrency) "
		        + "values ('jdoe', 'John', 'Doe', ('jdoe@cantfind.ws',), 'Male', 1234)";
		String expectedURL = "People";
		String returnResponse = "{\n" + 
		        "   \"UserName\":\"russellwhyte\",\n" + 
		        "   \"FirstName\":\"Russell\",\n" + 
		        "   \"LastName\":\"Whyte\"\n" + 
		        "}";
		String expectedPayload = "{\"@odata.type\":\"#Microsoft.OData.SampleService.Models.TripPin.Person\","
		        + "\"UserName@odata.type\":\"String\",\"UserName\":\"jdoe\","
		        + "\"FirstName@odata.type\":\"String\",\"FirstName\":\"John\","
		        + "\"LastName@odata.type\":\"String\",\"LastName\":\"Doe\","
		        + "\"Emails@odata.type\":\"#Collection(String)\",\"Emails\":[\"jdoe@cantfind.ws\"],"
		        + "\"Gender@odata.type\":\"#Microsoft.OData.SampleService.Models.TripPin.PersonGender\","
		        + "\"Gender\":\"Male\","
		        + "\"Concurrency@odata.type\":\"Int64\",\"Concurrency\":1234}";
		UpdateExecution excution = helpExecute(TestODataMetadataProcessor.tripPinMetadata(),
		        query, expectedPayload, returnResponse, expectedURL, "POST", 201);
		
	}
	
	@Test
	public void testInsertComplexType() throws Exception {
		String query = "INSERT INTO Persons_address(street, city, state, ssn) "
		        + "VALUES('sesame street', 'Newyork', 'NY', 1234)";
		String expectedURL = "Persons(1234)/address";
        
		String returnResponse = "{\n" + 
                "   \"UserName\":\"russellwhyte\",\n" + 
                "   \"FirstName\":\"Russell\",\n" + 
                "   \"LastName\":\"Whyte\"\n" + 
                "}";
        String expectedPayload = "{\"@odata.type\":\"#Edm.Address\","
                + "\"street@odata.type\":\"String\",\"street\":\"sesame street\","
                + "\"city@odata.type\":\"String\",\"city\":\"Newyork\","
                + "\"state@odata.type\":\"String\",\"state\":\"NY\"}";
		
        // single complex requires PATCH
		UpdateExecution excution = helpExecute(TestODataMetadataProcessor.getEntityWithComplexProperty(),
		        query, expectedPayload, returnResponse, expectedURL, "PATCH", 201);

	}
	
    @Test
    public void testInsertComplexTypeTripPin() throws Exception {
        String query = "INSERT INTO People_AddressInfo(Address, People_UserName) "
                + "VALUES('sesame street', 'russel')";
        String expectedURL = "People('russel')/AddressInfo";
        
        String returnResponse = "{\n" + 
                "   \"Address\":\"russellwhyte\",\n" + 
                "   \"FirstName\":\"Russell\",\n" + 
                "   \"LastName\":\"Whyte\"\n" + 
                "}";
        String expectedPayload = "{\"@odata.type\":\"#Microsoft.OData.SampleService.Models.TripPin.Location\","
                + "\"value\":[{\"@odata.type\":\"#Microsoft.OData.SampleService.Models.TripPin.Location\","
                + "\"Address@odata.type\":\"String\","
                + "\"Address\":\"sesame street\"}]}";
        
        //collection needs PUT
        UpdateExecution excution = helpExecute(TestODataMetadataProcessor.tripPinMetadata(),
                query, expectedPayload, returnResponse, expectedURL, "PUT", 201);
    }	

    @Test
    public void testInsertNavigation() throws Exception {
        String query = "INSERT INTO People_Friends(UserName, FirstName, LastName, People_UserName) "
                + "VALUES('jdoe', 'John', 'Doe', 'russel')";
        String expectedURL = "People('russel')/Friends";
        
        String returnResponse = "{\n" + 
                "   \"UserName\":\"jdoe\",\n" + 
                "   \"FirstName\":\"John\",\n" + 
                "   \"LastName\":\"Doe\"\n" + 
                "}";
        String expectedPayload = "{\"@odata.type\":\"#Microsoft.OData.SampleService.Models.TripPin.Person\","
                + "\"UserName@odata.type\":\"String\",\"UserName\":\"jdoe\","
                + "\"FirstName@odata.type\":\"String\",\"FirstName\":\"John\","
                + "\"LastName@odata.type\":\"String\",\"LastName\":\"Doe\"}";
        
        UpdateExecution excution = helpExecute(TestODataMetadataProcessor.tripPinMetadata(),
                query, expectedPayload, returnResponse, expectedURL, "POST", 201);
    }
}
