package controllers;

import java.io.BufferedInputStream;
import java.io.InputStream;
import java.io.StringReader;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Random;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.stream.FactoryConfigurationError;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;

import org.hsqldb.lib.ReaderInputStream;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.xml.sax.InputSource;

import blockchain.IncidentBlock;
import domain.Incident;

@Controller
@RequestMapping("rest")
public class IncidentController {

	private ArrayList<IncidentBlock> blockchain =
			new ArrayList<>();
	
	@SuppressWarnings("serial")
	@ResponseStatus(value = HttpStatus.NOT_FOUND)
	private class ResourceNotFoundException extends RuntimeException {
	}
	
	@SuppressWarnings("serial")
	@ResponseStatus(value = HttpStatus.BAD_REQUEST)
	private class BadRequestException extends RuntimeException {
	}

	@RequestMapping(value = "/incident/{incidentNumber}", method = RequestMethod.GET, produces = "application/json")
	public @ResponseBody Incident getSegment(@PathVariable("incidentNumber") int incidentNumber) {
		if (incidentNumber == 1234) {
			Incident i = new Incident();

			i.setIncidentNumber(1234);

			i.setDescription("Poor service response");
			i.setSeverity(4);
			i.setPayOut(50.00);
			return i;
		} else
			throw new ResourceNotFoundException();
	}

	@RequestMapping(value = "/incident/{incidentNumber}", method = RequestMethod.POST, consumes = "application/json", produces = "application/json")
	public ResponseEntity<?> putIncident(@RequestBody Incident incident) {
		System.out.println(incident);
		
		if(incident.getIncidentNumber() >= 0 &&
		   incident.getPayOut() >= 0 &&
		   incident.getSeverity() >= 0)
		{
			// blockchain stuff here
			String prevHash = null;
			
			if(blockchain.isEmpty()) 
			{
				// ceate genesis block
				Random r = new Random();
				prevHash = Integer.toString(r.nextInt(Integer.MAX_VALUE));
			}
			else
			{
				IncidentBlock prev = blockchain.get(blockchain.size() - 1);
				prevHash = prev.getHash();
			}
						
			IncidentBlock ib = new IncidentBlock(incident, prevHash);
			
			String rawData = ib.getTimestamp().toString() +
					ib.getPreviousHash() + incident.toString();
			
			String hash = generateHash(rawData);
			ib.setHash(hash);
			
			blockchain.add(ib);
			
			System.out.println("Blockchain: " + blockchain);
			
			return new ResponseEntity<>(incident, HttpStatus.CREATED);
		}
		else
		{
			throw new BadRequestException();
		}

	}

	@RequestMapping(value = "/xxeincident", method = RequestMethod.POST, consumes = "application/xml", produces = "application/xml")

	public ResponseEntity<?> xxeIncident(@RequestBody String xxeincident) {

		InputStream s = new BufferedInputStream(new ReaderInputStream(new StringReader(xxeincident)));

		try {

			DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
			factory.setFeature("http://xml.org/sax/features/external-general-entities", true);

			// Xerces 2 only -
			// http://xerces.apache.org/xerces-j/features.html#external-general-entities
			factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", false);
			factory.setIgnoringComments(true);
			factory.setIgnoringElementContentWhitespace(true);
			factory.setValidating(false);
			InputSource source = new InputSource(s);
			DocumentBuilder builder = factory.newDocumentBuilder();
			builder.parse(source);
		} catch (Exception e) {
			System.out.println("Error processing XML File:");
			System.out.println(e.getMessage());
			return new ResponseEntity<>("Hack Complete" + e.getMessage() + " you are at risk of a denial of service",
					HttpStatus.CREATED);
		}

		return new ResponseEntity<>("Hack Complete", HttpStatus.CREATED);

	}

	@RequestMapping(value = "/xxeincident2", method = RequestMethod.POST, consumes = "application/xml", produces = "application/xml")

	public ResponseEntity<?> xxeIncident2(@RequestBody String xxeincident) {
		InputStream s = new BufferedInputStream(new ReaderInputStream(new StringReader(xxeincident)));
		Incident incident = null;
		try {
			JAXBContext jc = JAXBContext.newInstance(Incident.class);

			XMLInputFactory xif = XMLInputFactory.newFactory();
			xif.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, true);
			xif.setProperty(XMLInputFactory.SUPPORT_DTD, true);
			XMLStreamReader xsr = xif.createXMLStreamReader(s);

			Unmarshaller unmarshaller = jc.createUnmarshaller();
			incident = (Incident) unmarshaller.unmarshal(xsr);
		} catch (IllegalArgumentException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (JAXBException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (FactoryConfigurationError e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (XMLStreamException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println(incident);

		{

			return new ResponseEntity<>(incident, HttpStatus.CREATED);
		}

	}

	public static String generateHash(String input) {
		try {
			MessageDigest digest = MessageDigest.getInstance("SHA-256");
			byte[] hash = digest.digest(input.getBytes("UTF-8"));
			StringBuffer hexString = new StringBuffer();
			for (int i = 0; i < hash.length; i++) {
				String hex = Integer.toHexString(0xff & hash[i]);
				if (hex.length() == 1)
					hexString.append('0');
				hexString.append(hex);
			}
			return hexString.toString();
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
}
