/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */

package de.christofreichardt.jaxws.securitypriceservice;

import de.christofreichardt.diagnosis.AbstractTracer;
import de.christofreichardt.diagnosis.Traceable;
import de.christofreichardt.diagnosis.TracerFactory;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.GeneralSecurityException;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.namespace.QName;
import javax.xml.transform.stream.StreamSource;
import javax.xml.ws.BindingProvider;
import javax.xml.ws.ProtocolException;
import javax.xml.ws.handler.Handler;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;

/**
 *
 * @author Christof Reichardt
 */
public class PriceRequestUnit implements Traceable {
  
  @Rule
  public ExpectedException thrown = ExpectedException.none();

  final private Properties properties;
  private SecurityPriceServicePortType securityPriceServicePort;

  public PriceRequestUnit(Properties properties) {
    this.properties = properties;
  }
  
  @Before
  public void init() throws MalformedURLException, GeneralSecurityException, IOException {
    AbstractTracer tracer = getCurrentTracer();
    tracer.entry("void", this, "init()");
    
    try {
      for (Map.Entry<Object, Object> entry : this.properties.entrySet()) {
        tracer.out().printfIndentln("%s = %s", entry.getKey(), entry.getValue());
      }
      
      String wsdlLocation = this.properties.getProperty("de.christofreichardt.jaxws.securitypriceservice.wsdl_location", 
          "http://localhost:8080/SecurityPriceService-server-0.0.1-SNAPSHOT/securitypriceservice?wsdl");
      String endpoint = this.properties.getProperty("de.christofreichardt.jaxws.securitypriceservice.endpoint", 
          "http://localhost:7070/SecurityPriceService-server-0.0.1-SNAPSHOT/securitypriceservice");
      
      URL url = new URL(wsdlLocation);
      QName qName = new QName("http://securitypriceservice.jaxws.christofreichardt.de/", "SecurityPriceService");
      SecurityPriceService securityPriceService = new SecurityPriceService(url, qName);
      this.securityPriceServicePort = securityPriceService.getPort(SecurityPriceServicePortType.class);
      
      BindingProvider bindingProvider = (BindingProvider) this.securityPriceServicePort;
      bindingProvider.getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, endpoint);
      List<Handler> handlerChain = bindingProvider.getBinding().getHandlerChain();
      
      tracer.out().printfIndentln("handlerChain.size() = %d", handlerChain.size());
      Assert.assertTrue("Expected an empty handler chain.", handlerChain.isEmpty());

      handlerChain.add(new SignatureHandler());
      bindingProvider.getBinding().setHandlerChain(handlerChain);
    }
    finally {
      tracer.wayout();
    }
  }
  
  @Test
  public void mixedPriceRequest() throws MalformedURLException, GeneralSecurityException, IOException, JAXBException, SecurityPriceServiceException, InterruptedException {
    AbstractTracer tracer = getCurrentTracer();
    tracer.entry("void", this, "mixedPriceRequest()");
    
    try {
      File requestFile = new File("." + File.separator + "xml" + File.separator + "mixed-price-request.xml");
      StreamSource streamSource = new StreamSource(requestFile);
      JAXBContext jaxbContext = JAXBContext.newInstance(SecurityPriceServiceRequest.class);
      Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
      JAXBElement<SecurityPriceServiceRequest> jaxbServiceRequest = unmarshaller.unmarshal(streamSource, SecurityPriceServiceRequest.class);
      SecurityPriceServiceRequest securityPriceServiceRequest = jaxbServiceRequest.getValue();
      securityPriceServiceRequest.setTransactionId(TransactionIdCounter.getInstance().getNextTransactionId());
      SecurityPriceServiceResponse securityPriceServiceResponse = this.securityPriceServicePort.findPrice(securityPriceServiceRequest);
      JAXBElement<SecurityPriceServiceResponse> jaxbServiceResponse = new JAXBElement<>(
          new QName("http://de.christofreichardt/SecurityPriceService", "SecurityPriceServiceResponse"), 
          SecurityPriceServiceResponse.class, securityPriceServiceResponse);
      
      tracer.out().printfIndentln("securityPriceServiceResponse.getTimestamp() = %s", securityPriceServiceResponse.getTimestamp());
      
      List<AbstractSecurityPrice> securityPrices = securityPriceServiceResponse.getSharePriceOrIndexPriceOrOptionPrice();
      for (AbstractSecurityPrice securityPrice : securityPrices) {
        MySecurityPrice mySecurityPrice = new MySecurityPrice(securityPrice);
        tracer.out().printfIndentln("%s", mySecurityPrice);
      }
      
      Assert.assertTrue("Expected 5 prices.", securityPrices.size() == 5);
      
      Assert.assertTrue("Expected 'UnknownPrice' but received '" + securityPrices.get(0).getClass().getSimpleName() + "'.", 
          "UnknownPrice".equals(securityPrices.get(0).getClass().getSimpleName()));
      
      Assert.assertTrue("Expected 'IndexPrice' but received '" + securityPrices.get(1).getClass().getSimpleName() + "'.", 
          "IndexPrice".equals(securityPrices.get(1).getClass().getSimpleName()));
      Assert.assertTrue("Expected an 'UNLIMITED INDEX ZERTIFIKAT AUF DAX PERFORMANCE-INDEX' but received '" + securityPrices.get(1).getSecurity().getName() + "'.", 
          "UNLIMITED INDEX ZERTIFIKAT AUF DAX PERFORMANCE-INDEX".equals(securityPrices.get(1).getSecurity().getName()));
      Assert.assertTrue("Expected the WKN 'CZ33FY' but received '" + securityPrices.get(1).getSecurity().getWKN() + "'.", 
          "CZ33FY".equals(securityPrices.get(1).getSecurity().getWKN()));
      
      Assert.assertTrue("Expected 'SharePrice' but received '" + securityPrices.get(2).getClass().getSimpleName() + "'.", 
          "SharePrice".equals(securityPrices.get(2).getClass().getSimpleName()));
      Assert.assertTrue("Expected a 'Volkswagen VZ Aktie' but received '" + securityPrices.get(2).getSecurity().getName() + "'.", 
          "Volkswagen VZ Aktie".equals(securityPrices.get(2).getSecurity().getName()));
      Assert.assertTrue("Expected the WKN '766403' but received '" + securityPrices.get(2).getSecurity().getWKN() + "'.", 
          "766403".equals(securityPrices.get(2).getSecurity().getWKN()));
      
      Assert.assertTrue("Expected 'OptionPrice' but received '" + securityPrices.get(3).getClass().getSimpleName() + "'.", 
          "OptionPrice".equals(securityPrices.get(3).getClass().getSimpleName()));
      Assert.assertTrue("Expected a 'CALL auf DAX' but received '" + securityPrices.get(3).getSecurity().getName() + "'.", 
          "CALL auf DAX".equals(securityPrices.get(3).getSecurity().getName()));
      Assert.assertTrue("Expected the WKN 'US39K5' but received '" + securityPrices.get(3).getSecurity().getWKN() + "'.", 
          "US39K5".equals(securityPrices.get(3).getSecurity().getWKN()));
      
      Marshaller marshaller = jaxbContext.createMarshaller();
      marshaller.setProperty("jaxb.formatted.output", Boolean.TRUE);
      marshaller.marshal(jaxbServiceResponse, new File("." + File.separator + "xml" + File.separator + "mixed-price-response.xml"));
    } 
    finally {
      tracer.wayout();
    }
  }
  
  @Test
  public void priceRequestByUnknown() throws JAXBException, SecurityPriceServiceException {
    AbstractTracer tracer = getCurrentTracer();
    tracer.entry("void", this, "priceRequestByUnknown()");
    
    try {
      this.thrown.expect(ProtocolException.class);
      this.thrown.expectMessage("Unknown user: 'unknown'.");
      
      File requestFile = new File("." + File.separator + "xml" + File.separator + "price-request-by-unknown.xml");
      StreamSource streamSource = new StreamSource(requestFile);
      JAXBContext jaxbContext = JAXBContext.newInstance(SecurityPriceServiceRequest.class);
      Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
      JAXBElement<SecurityPriceServiceRequest> jaxbServiceRequest = unmarshaller.unmarshal(streamSource, SecurityPriceServiceRequest.class);
      SecurityPriceServiceRequest securityPriceServiceRequest = jaxbServiceRequest.getValue();
      securityPriceServiceRequest.setTransactionId(TransactionIdCounter.getInstance().getNextTransactionId());
      SecurityPriceServiceResponse securityPriceServiceResponse = this.securityPriceServicePort.findPrice(securityPriceServiceRequest);
      
      tracer.out().printfIndentln("securityPriceServiceResponse.getTimestamp() = %s", securityPriceServiceResponse.getTimestamp());
    }
    finally {
      tracer.wayout();
    }
  }
  
  @Test
  public void priceRequestByCheater() throws JAXBException, SecurityPriceServiceException, IOException, GeneralSecurityException {
    AbstractTracer tracer = getCurrentTracer();
    tracer.entry("void", this, "priceRequestByCheater()");
    
    try {
      this.thrown.expect(ProtocolException.class);
      this.thrown.expectMessage("Invalid Signature.");
      
      File keystoreFile = new File(".." + File.separator + "cheater-keystore.jks");
      
      BindingProvider bindingProvider = (BindingProvider) this.securityPriceServicePort;
      List<Handler> handlerChain = bindingProvider.getBinding().getHandlerChain();
      handlerChain.clear();
      handlerChain.add(new SignatureHandler(keystoreFile));
      bindingProvider.getBinding().setHandlerChain(handlerChain);
      
      File requestFile = new File("." + File.separator + "xml" + File.separator + "mixed-price-request.xml");
      StreamSource streamSource = new StreamSource(requestFile);
      JAXBContext jaxbContext = JAXBContext.newInstance(SecurityPriceServiceRequest.class);
      Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
      JAXBElement<SecurityPriceServiceRequest> jaxbServiceRequest = unmarshaller.unmarshal(streamSource, SecurityPriceServiceRequest.class);
      SecurityPriceServiceRequest securityPriceServiceRequest = jaxbServiceRequest.getValue();
      securityPriceServiceRequest.setTransactionId(TransactionIdCounter.getInstance().getNextTransactionId());
      SecurityPriceServiceResponse securityPriceServiceResponse = this.securityPriceServicePort.findPrice(securityPriceServiceRequest);

      tracer.out().printfIndentln("securityPriceServiceResponse.getTimestamp() = %s", securityPriceServiceResponse.getTimestamp());
    }
    finally {
      tracer.wayout();
    }
  }
  
  @Test
  public void priceRequestForOfflineProvider() throws JAXBException, SecurityPriceServiceException {
    AbstractTracer tracer = getCurrentTracer();
    tracer.entry("void", this, "priceRequestForOfflineProvider()");
    
    try {
      File requestFile = new File("." + File.separator + "xml" + File.separator + "offline-price-request.xml");
      StreamSource streamSource = new StreamSource(requestFile);
      JAXBContext jaxbContext = JAXBContext.newInstance(SecurityPriceServiceRequest.class);
      Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
      JAXBElement<SecurityPriceServiceRequest> jaxbServiceRequest = unmarshaller.unmarshal(streamSource, SecurityPriceServiceRequest.class);
      SecurityPriceServiceRequest securityPriceServiceRequest = jaxbServiceRequest.getValue();
      securityPriceServiceRequest.setTransactionId(TransactionIdCounter.getInstance().getNextTransactionId());
      SecurityPriceServiceResponse securityPriceServiceResponse = this.securityPriceServicePort.findPrice(securityPriceServiceRequest);
      
      tracer.out().printfIndentln("securityPriceServiceResponse.getTimestamp() = %s", securityPriceServiceResponse.getTimestamp());
      
      List<AbstractSecurityPrice> securityPrices = securityPriceServiceResponse.getSharePriceOrIndexPriceOrOptionPrice();
      for (AbstractSecurityPrice securityPrice : securityPrices) {
        tracer.out().printfIndentln("%s", securityPrice);
      }
      
      Assert.assertTrue("Expected 1 price.", securityPrices.size() == 1);
      
      Assert.assertTrue("Expected 'UnknownPrice' but received '" + securityPrices.get(0).getClass().getSimpleName() + "'.", 
          "UnknownPrice".equals(securityPrices.get(0).getClass().getSimpleName()));
      Assert.assertTrue("Expected an 'Offline Test' but received '" + securityPrices.get(0).getSecurity().getName() + "'.", 
          "Offline Test".equals(securityPrices.get(0).getSecurity().getName()));
   }
    finally {
      tracer.wayout();
    }
  }
  
  @Test
  public void extraRequest() throws JAXBException, SecurityPriceServiceException {
    AbstractTracer tracer = getCurrentTracer();
    tracer.entry("void", this, "extraRequest()");
    
    try {
      File requestFile = new File(this.properties.getProperty("de.christofreichardt.jaxws.securitypriceservice.request_file", 
          "." + File.separator + "xml" + File.separator + "extra-request.xml"));
      StreamSource streamSource = new StreamSource(requestFile);
      JAXBContext jaxbContext = JAXBContext.newInstance(SecurityPriceServiceRequest.class);
      Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
      JAXBElement<SecurityPriceServiceRequest> jaxbServiceRequest = unmarshaller.unmarshal(streamSource, SecurityPriceServiceRequest.class);
      SecurityPriceServiceRequest securityPriceServiceRequest = jaxbServiceRequest.getValue();
      securityPriceServiceRequest.setTransactionId(TransactionIdCounter.getInstance().getNextTransactionId());
      SecurityPriceServiceResponse securityPriceServiceResponse = this.securityPriceServicePort.findPrice(securityPriceServiceRequest);
      JAXBElement<SecurityPriceServiceResponse> jaxbServiceResponse = new JAXBElement<>(
          new QName("http://de.christofreichardt/SecurityPriceService", "SecurityPriceServiceResponse"), 
          SecurityPriceServiceResponse.class, securityPriceServiceResponse);
      
      tracer.out().printfIndentln("securityPriceServiceResponse.getTimestamp() = %s", securityPriceServiceResponse.getTimestamp());
      
      List<AbstractSecurityPrice> securityPrices = securityPriceServiceResponse.getSharePriceOrIndexPriceOrOptionPrice();
      for (AbstractSecurityPrice securityPrice : securityPrices) {
        tracer.out().printfIndentln("%s", securityPrice);
      }
      
      Marshaller marshaller = jaxbContext.createMarshaller();
      marshaller.setProperty("jaxb.formatted.output", Boolean.TRUE);
      marshaller.marshal(jaxbServiceResponse, new File("." + File.separator + "xml" + File.separator + "extra-response.xml"));
   }
    finally {
      tracer.wayout();
    }
  }
  
  @Test
  /**
   * Must not be executed as first testcase.
   */
  public void replayAttack() throws JAXBException, SecurityPriceServiceException {
    AbstractTracer tracer = getCurrentTracer();
    tracer.entry("void", this, "replayAttack()");
    
    try {
      this.thrown.expect(SecurityPriceServiceException.class);
      this.thrown.expectMessage("Illegal transactionId:");

      File requestFile = new File(this.properties.getProperty("de.christofreichardt.jaxws.securitypriceservice.request_file", 
          "." + File.separator + "xml" + File.separator + "mixed-price-request.xml"));
      StreamSource streamSource = new StreamSource(requestFile);
      JAXBContext jaxbContext = JAXBContext.newInstance(SecurityPriceServiceRequest.class);
      Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
      JAXBElement<SecurityPriceServiceRequest> jaxbServiceRequest = unmarshaller.unmarshal(streamSource, SecurityPriceServiceRequest.class);
      SecurityPriceServiceRequest securityPriceServiceRequest = jaxbServiceRequest.getValue();
      securityPriceServiceRequest.setTransactionId(0); // 0 should be used already as transactionId(!) for user 'tester'
      SecurityPriceServiceResponse securityPriceServiceResponse = this.securityPriceServicePort.findPrice(securityPriceServiceRequest);
      
      tracer.out().printfIndentln("securityPriceServiceResponse.getTimestamp() = %s", securityPriceServiceResponse.getTimestamp());
      
      List<AbstractSecurityPrice> securityPrices = securityPriceServiceResponse.getSharePriceOrIndexPriceOrOptionPrice();
      for (AbstractSecurityPrice securityPrice : securityPrices) {
        tracer.out().printfIndentln("%s", securityPrice);
      }
   }
    finally {
      tracer.wayout();
    }
  }
  
  @Test
  public void missingSignature() throws JAXBException, SecurityPriceServiceException {
    AbstractTracer tracer = getCurrentTracer();
    tracer.entry("void", this, "missingSignature()");
    
    try {
      this.thrown.expect(ProtocolException.class);
      this.thrown.expectMessage("No Signature element found.");
      
      BindingProvider bindingProvider = (BindingProvider) this.securityPriceServicePort;
      List<Handler> handlerChain = bindingProvider.getBinding().getHandlerChain();
      handlerChain.clear();
      bindingProvider.getBinding().setHandlerChain(handlerChain);
      
      File requestFile = new File("." + File.separator + "xml" + File.separator + "mixed-price-request.xml");
      StreamSource streamSource = new StreamSource(requestFile);
      JAXBContext jaxbContext = JAXBContext.newInstance(SecurityPriceServiceRequest.class);
      Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
      JAXBElement<SecurityPriceServiceRequest> jaxbServiceRequest = unmarshaller.unmarshal(streamSource, SecurityPriceServiceRequest.class);
      SecurityPriceServiceRequest securityPriceServiceRequest = jaxbServiceRequest.getValue();
      securityPriceServiceRequest.setTransactionId(TransactionIdCounter.getInstance().getNextTransactionId());
      SecurityPriceServiceResponse securityPriceServiceResponse = this.securityPriceServicePort.findPrice(securityPriceServiceRequest);

      tracer.out().printfIndentln("securityPriceServiceResponse.getTimestamp() = %s", securityPriceServiceResponse.getTimestamp());
   }
    finally {
      tracer.wayout();
    }
  }

  @After
  public void exit() {
    AbstractTracer tracer = getCurrentTracer();
    tracer.entry("void", this, "exit()");
    
    try {
    }
    finally {
      tracer.wayout();
    }
  }

  @Override
  public AbstractTracer getCurrentTracer() {
    return TracerFactory.getInstance().getCurrentPoolTracer();
  }
}
