package ar.com.fernandospr.wns.client;

import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;

import javax.ws.rs.client.*;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;


import ar.com.fernandospr.wns.WnsProxyProperties;
import ar.com.fernandospr.wns.exceptions.WnsException;
import ar.com.fernandospr.wns.model.*;
import ar.com.fernandospr.wns.model.types.WnsNotificationType;
import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider;
import com.fasterxml.jackson.jaxrs.xml.JacksonJaxbXMLProvider;
import org.glassfish.jersey.apache.connector.ApacheConnectorProvider;
import org.glassfish.jersey.client.ClientConfig;
import org.glassfish.jersey.client.ClientProperties;
import org.glassfish.jersey.filter.LoggingFilter;
import org.glassfish.jersey.internal.util.collection.MultivaluedStringMap;


public class WnsClient {
    
	private static final String SCOPE = "notify.windows.com";
	private static final String GRANT_TYPE_CLIENT_CREDENTIALS = "client_credentials";
	private static final String AUTHENTICATION_URI = "https://login.live.com/accesstoken.srf";
	
	private final String sid;
	private final String clientSecret;
	private final Client client;

	private WnsOAuthToken token;

	public WnsClient(String sid, String clientSecret, Client client) {
		this.sid = sid;
		this.clientSecret = clientSecret;
		this.client = client;
	}

	public WnsClient(String sid, String clientSecret, boolean logging) {
		this(sid, clientSecret, null, logging);
	}
	
	public WnsClient(String sid, String clientSecret, WnsProxyProperties proxyProps, boolean logging) {
		this(sid, clientSecret, createClient(logging, proxyProps));
	}

	protected String getAuthenticationUri() {
		return AUTHENTICATION_URI;
	}

    private static Client createClient(boolean logging, WnsProxyProperties proxyProps) {
        ClientConfig clientConfig = new ClientConfig(JacksonJaxbXMLProvider.class, JacksonJsonProvider.class);

        setProxyCredentials(clientConfig, proxyProps);

        Client client = ClientBuilder.newClient(clientConfig);
        if (logging) {
            LoggingFilter loggingFilter = new LoggingFilter(
                    Logger.getLogger(WnsClient.class.getName()), true);

            client = client.register(loggingFilter);
        }
        return client;
    }

    private static void setProxyCredentials(ClientConfig clientConfig, WnsProxyProperties proxyProps) {
        if (proxyProps != null) {
            String proxyUri = proxyProps.getUri();
            String proxyUser = proxyProps.getUser();
            String proxyPass = proxyProps.getPass();

            if (proxyUri != null) {
                clientConfig.property(ClientProperties.PROXY_URI, proxyUri);
                if ( (proxyUser != null) && !proxyUser.trim().isEmpty()) {
                    clientConfig.property(ClientProperties.PROXY_USERNAME, proxyUser);
                    clientConfig.property(ClientProperties.PROXY_PASSWORD, proxyPass);
                }
            }
        }
    }

    /**
     * Based on <a href="http://msdn.microsoft.com/en-us/library/windows/apps/hh465407.aspx">http://msdn.microsoft.com/en-us/library/windows/apps/hh465407.aspx</a>
     *
     * @throws WnsException when authentication fails
     */
    public void refreshAccessToken() throws WnsException {
        WebTarget target = client.target(getAuthenticationUri());

        MultivaluedStringMap formData = new MultivaluedStringMap();
        formData.add("grant_type", GRANT_TYPE_CLIENT_CREDENTIALS);
        formData.add("client_id", this.sid);
        formData.add("client_secret", this.clientSecret);
        formData.add("scope", SCOPE);
        Response response = target.request(MediaType.APPLICATION_FORM_URLENCODED_TYPE).accept(MediaType.APPLICATION_JSON_TYPE).post(Entity.form(formData));

        if (response.getStatus() != 200) {
            throw new WnsException("Authentication failed. HTTP error code: " + response.getStatus());
        }

        this.token = response.readEntity(WnsOAuthToken.class);
    }
	
	/**
	 * @param channelUri
	 * @param resourceBuilder
	 * @param notification
	 * @param retriesLeft to push the notification if the token expires
	 * @return WnsNotificationResponse please see response headers from <a href="http://msdn.microsoft.com/en-us/library/windows/apps/hh465435.aspx#send_notification_response">http://msdn.microsoft.com/en-us/library/windows/apps/hh465435.aspx#send_notification_response</a>
	 * @throws WnsException when authentication fails
	 */
	public WnsNotificationResponse push(WnsResourceBuilder resourceBuilder, String channelUri, WnsAbstractNotification notification, int retriesLeft, WnsNotificationRequestOptional optional) throws WnsException {
        WebTarget target = client.target(channelUri);
        Invocation.Builder webResourceBuilder = resourceBuilder.build(target, notification, getToken().access_token, optional);
        String type = notification.getType().equals(WnsNotificationType.RAW) ? MediaType.APPLICATION_OCTET_STREAM : MediaType.TEXT_XML;

        Response response = webResourceBuilder.buildPost(Entity.entity(resourceBuilder.getEntityToSendWithNotification(notification), type)).invoke();

        WnsNotificationResponse notificationResponse = new WnsNotificationResponse(channelUri, response.getStatus(), response.getStringHeaders());
		if (notificationResponse.code == 200) {
			return notificationResponse;
		}
		
		if (notificationResponse.code == 401 && retriesLeft > 0) {
			retriesLeft--;
			// Access token may have expired
			refreshAccessToken();
			// Retry
			return this.push(resourceBuilder, channelUri, notification, retriesLeft, optional);
		}
		
		// Assuming push failed
		return notificationResponse;
	}
	
	private WnsOAuthToken getToken() throws WnsException {
		if (this.token == null) {
			refreshAccessToken();
		}
		return this.token;
	}

	/**
	 * @param channelUris
	 * @param resourceBuilder
	 * @param notification
	 * @param retriesLeft to push the notification if the token expires
	 * @return list of WnsNotificationResponse for each channelUri, please see response headers from <a href="http://msdn.microsoft.com/en-us/library/windows/apps/hh465435.aspx#send_notification_response">http://msdn.microsoft.com/en-us/library/windows/apps/hh465435.aspx#send_notification_response</a>
	 * @throws WnsException when authentication fails
	 */
	public List<WnsNotificationResponse> push(WnsResourceBuilder resourceBuilder, List<String> channelUris, WnsAbstractNotification notification, int retriesLeft, WnsNotificationRequestOptional optional) throws WnsException {
		List<WnsNotificationResponse> responses = new ArrayList<WnsNotificationResponse>();
		for (String channelUri : channelUris) {
			WnsNotificationResponse response = push(resourceBuilder, channelUri, notification, retriesLeft, optional);
			responses.add(response);
		}
		return responses;
	}
}