package org.wikiwebserver.util;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.math.BigDecimal;
import java.net.URL;
import java.net.URLConnection;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;

import org.wikiwebserver.core.WareHouse;
import org.wikiwebserver.handler.http.FormData;
import org.wikiwebserver.handler.http.HTTPException;
import org.wikiwebserver.handler.http.HTTPHandler;
import org.wikiwebserver.handler.http.HTTPRequest;
import org.wikiwebserver.handler.http.interfaces.HTTPResponder;

import page.config.SiteMonitor;
import page.tools.entity.Order;
import page.tools.entity.Payment;
import page.tools.entity.User;

public class NochexHelper implements HTTPResponder {
    
    private static final String nochexEntryPoint = "https://secure.nochex.com/";    
    private static final String nochexAPC = "https://www.nochex.com/nochex.dll/apc/apc";
    private static final String responderAPC = "http://www.wikiwebserver.org" + 
        WareHouse.getUrlPathForClass(NochexHelper.class);      

    /*
     * Receive Nochex Automatic payment confirmation (APC)
     * 
     * @param conn The connection made from Nochex
     * @return a String with the value of "Complete"
     * @throws IOException if there is a failure confirming the payment
     */
    public Object respond(HTTPHandler conn) throws IOException {
        
        HTTPRequest req = conn.getRequest();          
        
        if (!"POST".equalsIgnoreCase(req.getMethod())) {
            throw new HTTPException(400, "POST containing payment information expected");
        }
        
        // Read the payment information from nochex
        FormData formData = req.getFormData();
        
        if (formData == null || formData.size() == 0) {
            throw new HTTPException(400, "No payment information recieved in POST");
        }        
        

        // Create new payment object
        Payment payment = new Payment();

        
        // Store payment data from Nochex for confirmation
        Object recievedObject = req.getData();
        if (recievedObject instanceof String) {
            payment.setAPCResponse((String) recievedObject);
        }
        
        // Set the payment amount
        String amountString = formData.getFirst("amount");
        BigDecimal amount = new BigDecimal(amountString);
        int amountInPennies = amount.multiply(new BigDecimal(100)).intValue();
        payment.setAmountInPennies(amountInPennies);        
        
        payment.setPaymentTo(formData.getFirst("to_email"));
        payment.setPaymentFrom(formData.getFirst("from_email"));       
        

        String statusString = formData.getFirst("status"); // live or test
        payment.setLive("live".equalsIgnoreCase(statusString));
        
        
        
        // Find the original order
        String orderID = formData.getFirst("order_id");
        
        // Link order to payment
        Order order = Order.getOrderById(orderID);
        if (order == null) {
            // Sometimes I may accept other random payments that do not have orders
            return "Order not found";
        }
        
        order.getPayer().addPayment(payment, conn);
        order.setPayment(payment);
        
        // Also link payment to order
        payment.setOrder(order); // Link to order        
        
        try {
            SiteMonitor.logPayment(payment);
            
            // Confirm the payment information
            confirmPurchase(order);
            
        } catch (Exception ex) {
            // Failed (we can try later)
            ex.printStackTrace();
        }
        
        return "Complete";
    }
    
    
    
    public void confirmPurchase(Order order) throws Exception {
        
        if (order == null) {
            throw new NullPointerException("Order is null");
        }        
        
        Payment payment = order.getPayment();
        if (payment == null) {
            throw new Exception("No payment found for order " + order.getId());
        }
        else if (order.getAmountInPennies() != payment.getAmountInPennies()) {
            throw new Exception("Payment amount does not match order amount");
        }
        else if (!order.getPaymentTo().equals(payment.getPaymentTo())) {
            throw new Exception("Payment made to wrong merchant");
        }      
        else if (order.isTest() == payment.isLive()) {
            throw new Exception("Test order generated a live payment");
        }   
        else if (!order.isTest() == !payment.isLive()) {
            throw new Exception("Test payment made for a real order");
        }          
        
        OutputStream nochexOut = null;
        BufferedReader nochexReader = null;
        try {
            URLConnection nochex = new URL(nochexAPC).openConnection();
            nochex.setDoOutput(true);
            
            nochexOut = nochex.getOutputStream();
            
            String nochexAPCResponse = payment.getAPCResponse();
            if (nochexAPCResponse == null) {
                throw new IOException("No APC Response has been recieved from Nochex");
            }
            
            byte[] data = ((String) nochexAPCResponse).getBytes();
            nochexOut.write(data);
            nochexOut.flush();
        
            // Get the response
            nochexReader = new BufferedReader(new InputStreamReader(nochex.getInputStream()));
            
            String response = WareHouse.readerToString(nochexReader);
            
            payment.setAPCConfirmation(response);
            
            payment.setConfirmed(response.startsWith("AUTHORISED"));
            
            
        } catch (IOException ex) {     
            throw ex;
        } finally {
            if (nochexOut != null) nochexOut.close();
            if (nochexReader != null) nochexReader.close();
        }
    }
    
    public Order createOrder(int amountInPennies, 
                             String description,
                             String paymentResponder,
                             User paymentMaker) {
        
        if (paymentMaker == null) {
            throw new SecurityException("Unknown user creating an order");
        }
        
        Order order = new Order();
        order.setDescription(description);
        order.setPaymentTo("nochex@wikiwebserver.org");
        order.setAmountInPennies(amountInPennies);
        
        order.setPayer(paymentMaker);
        order.setPaymentResponder(paymentResponder);
        order.setAPCResponder(responderAPC);
        
        //order.setTest(true);
        
        return order;
    }
    
    public void redirectToNochexProcessor(Order order) throws HTTPException {
        
        Map<String, String> params = new TreeMap<String, String>();
        
        // Where the user will end up after payment
        String callbackPage = order.getPaymentResponder();            
        
        params.put("merchant_id",  order.getPaymentTo());
        params.put("amount",       order.getAmount());
        params.put("description",  order.getDescription());
        params.put("order_id",     order.getId());
        
        params.put("callback_url", order.getAPCResponder());        
        params.put("success_url",  getCallbackUrl(callbackPage, "success", order.getId()));
        params.put("declined_url", getCallbackUrl(callbackPage, "declined", order.getId()));
        params.put("cancel_url",   getCallbackUrl(callbackPage, "cancel", order.getId()));
                
        
        if (order.isTest()) {
            String testSuccessUrl = getCallbackUrl(callbackPage, "test_success", order.getId());
            params.put("test_transaction", "100");
            params.put("test_success_url", testSuccessUrl);         
        }  
        
        if (order.getPayer() != null) {
            params.put("email_address", order.getPayer().getEmail());
            String fullName = (String) order.getPayer().get("fullName");
            if (fullName != null) {
                params.put("billing_fullname", fullName);
            }
        }        
        
        StringBuilder nochexBuilder = new StringBuilder();
        nochexBuilder.append(nochexEntryPoint + "?");
        
        Iterator<Map.Entry<String, String>> i = params.entrySet().iterator();
        while (i.hasNext()) {
            Map.Entry<String, String> param = i.next();
            nochexBuilder.append(param.getKey() + "=" + WareHouse.formDataEncode(param.getValue()));
            if (i.hasNext()) nochexBuilder.append("&");
        }    
        
        String nochexRequest = nochexBuilder.toString();
        order.setPaymentRequest(nochexRequest);
        
        throw new HTTPException(303, "Continue to nochex", nochexRequest);
        
    }
    
    private static String getCallbackUrl(String callbackUrl, String response, String orderID) {
        return callbackUrl + "?processor=nochex&response=" + response + "&order_id=" + orderID;
    }  
}

