/*
 * TeleStax, Open Source Cloud Communications
 * Copyright 2011-2015, Telestax Inc and individual contributors
 * by the @authors tag.
 *
 * This program is free software: you can redistribute it and/or modify
 * under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation; either version 3 of
 * the License, or (at your option) any later version.
 *
 * This program 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 *
 */
package org.restcomm.connect.provisioning.number.voxbone;

import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.regex.Pattern;

import org.apache.commons.configuration.Configuration;
import org.apache.log4j.Logger;
import org.restcomm.connect.provisioning.number.api.ContainerConfiguration;
import org.restcomm.connect.provisioning.number.api.PhoneNumber;
import org.restcomm.connect.provisioning.number.api.PhoneNumberParameters;
import org.restcomm.connect.provisioning.number.api.PhoneNumberProvisioningManager;
import org.restcomm.connect.provisioning.number.api.PhoneNumberSearchFilters;

import com.google.gson.JsonArray;
import com.google.gson.JsonNull;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.ClientResponse.Status;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.api.client.filter.HTTPBasicAuthFilter;

/**
 * @author jean.deruelle@telestax.com
 */
public class VoxbonePhoneNumberProvisioningManager implements PhoneNumberProvisioningManager {
    private static final Logger logger = Logger.getLogger(VoxbonePhoneNumberProvisioningManager.class);
    private static final String COUNTRY_CODE_PARAM = "countryCodeA3";
    private static final String PAGE_SIZE = "pageSize";
    private static final String PAGE_NUMBER = "pageNumber";
    private static final String CONTENT_TYPE = "application/json";

    protected Boolean telestaxProxyEnabled;
    protected String uri, username, password;
    protected String searchURI, createCartURI, voiceURI, updateURI, cancelURI, countriesURI, listDidsURI;
    protected String voiceUriId;
    protected Configuration activeConfiguration;
    protected ContainerConfiguration containerConfiguration;

    public VoxbonePhoneNumberProvisioningManager() {}

    /*
     * (non-Javadoc)
     *
     * @see
     * PhoneNumberProvisioningManager#init(org.apache.commons.configuration
     * .Configuration, boolean)
     */
    @Override
    public void init(Configuration phoneNumberProvisioningConfiguration, Configuration telestaxProxyConfiguration, ContainerConfiguration containerConfiguration) {
        this.containerConfiguration = containerConfiguration;
        telestaxProxyEnabled = telestaxProxyConfiguration.getBoolean("enabled", false);
        if (telestaxProxyEnabled) {
            uri = telestaxProxyConfiguration.getString("uri");
            username = telestaxProxyConfiguration.getString("username");
            password = telestaxProxyConfiguration.getString("password");
            activeConfiguration = telestaxProxyConfiguration;
        } else {
            Configuration voxboneConfiguration = phoneNumberProvisioningConfiguration.subset("voxbone");
            uri = voxboneConfiguration.getString("uri");
            username = voxboneConfiguration.getString("username");
            password = voxboneConfiguration.getString("password");
            activeConfiguration = voxboneConfiguration;
        }
        searchURI = uri + "/inventory/didgroup";
        createCartURI = uri + "/ordering/cart";
        voiceURI = uri + "/configuration/voiceuri";
        updateURI = uri + "/configuration/configuration";
        cancelURI = uri + "/ordering/cancel";
        countriesURI = uri + "/inventory/country";
        listDidsURI = uri + "/inventory/did";

        Configuration callbackUrlsConfiguration = phoneNumberProvisioningConfiguration.subset("callback-urls");
        Client jerseyClient = Client.create();
        jerseyClient.addFilter(new HTTPBasicAuthFilter(username, password));

        WebResource webResource = jerseyClient.resource(voiceURI);
        String body = "{\"voiceUri\":{\"voiceUriProtocol\":\"SIP\",\"uri\":\"" + callbackUrlsConfiguration.getString("voice[@url]") + "\"}}";
        ClientResponse clientResponse = webResource.accept(CONTENT_TYPE).type(CONTENT_TYPE).put(ClientResponse.class,body);

        String voiceURIResponse = clientResponse.getEntity(String.class);
        if(logger.isDebugEnabled())
            logger.debug("response " + voiceURIResponse);

        JsonParser parser = new JsonParser();
        JsonObject jsonVoiceURIResponse = parser.parse(voiceURIResponse).getAsJsonObject();

        if (clientResponse.getClientResponseStatus() == Status.OK) {
            JsonObject voxVoiceURI = jsonVoiceURIResponse.get("voiceUri").getAsJsonObject();
            voiceUriId = voxVoiceURI.get("voiceUriId").getAsString();
        } else if (clientResponse.getClientResponseStatus() == Status.UNAUTHORIZED) {
            JsonObject error = jsonVoiceURIResponse.get("errors").getAsJsonArray().get(0).getAsJsonObject();
            throw new IllegalArgumentException(error.get("apiErrorMessage").getAsString());
        } else {
            webResource = jerseyClient.resource(voiceURI);
            clientResponse = webResource.queryParam(PAGE_NUMBER,"0").queryParam(PAGE_SIZE,"300").accept(CONTENT_TYPE).type(CONTENT_TYPE).get(ClientResponse.class);

            String listVoiceURIResponse = clientResponse.getEntity(String.class);
            if(logger.isDebugEnabled())
                logger.debug("response " + listVoiceURIResponse );

            JsonObject jsonListVoiceURIResponse = parser.parse(listVoiceURIResponse).getAsJsonObject();
            // TODO go through the list of voiceURI id and check which one is matching
            JsonObject voxVoiceURI = jsonListVoiceURIResponse.get("voiceUris").getAsJsonArray().get(0).getAsJsonObject();
            voiceUriId = voxVoiceURI.get("voiceUriId").getAsString();
        }
    }

    private List<PhoneNumber> toAvailablePhoneNumbers(final JsonArray phoneNumbers, PhoneNumberSearchFilters listFilters) {
        Pattern searchPattern = listFilters.getFilterPattern();
        final List<PhoneNumber> numbers = new ArrayList<PhoneNumber>();
        for (int i = 0; i < phoneNumbers.size(); i++) {
            JsonObject number = phoneNumbers.get(i).getAsJsonObject();
            if(Integer.parseInt(number.get("stock").toString()) > 0) {
                //we only return the number if there is any stock for it
                String countryCode = number.get(COUNTRY_CODE_PARAM).getAsString();
                String features = null;
                if(number.get("features") != null) {
                    features = number.get("features").toString();
                }
                boolean isVoiceCapable = true;
                boolean isFaxCapable = false;
                boolean isSmsCapable = false;
                if(features.contains("VoxSMS")) {
                    isSmsCapable = true;
                }
                if(features.contains("VoxFax")) {
                    isFaxCapable = true;
                }
                String friendlyName = number.get("countryCodeA3").getAsString();
                if(number.get("cityName") != null && !(number.get("cityName") instanceof JsonNull)) {
                    friendlyName = friendlyName + "-" + number.get("cityName").getAsString();
                }
                if(number.get("areaCode") != null && !(number.get("areaCode") instanceof JsonNull)) {
                    friendlyName = friendlyName + "-" + number.get("areaCode").getAsString();
                }
                final PhoneNumber phoneNumber = new PhoneNumber(
                        friendlyName,
                        number.get("didGroupId").getAsString(),
                        null, null, null, null, null, null,
                        countryCode, null, isVoiceCapable, isSmsCapable, null, isFaxCapable, null);
                numbers.add(phoneNumber);
            }
        }
        return numbers;
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * PhoneNumberProvisioningManager#searchForNumbers(java.lang.String,
     * java.lang.String, java.util.regex.Pattern, boolean, boolean, boolean, boolean, int, int)
     */
    @Override
    public List<PhoneNumber> searchForNumbers(String country, PhoneNumberSearchFilters listFilters) {
        Locale locale = new Locale("en",country);
        String iso3Country = locale.getISO3Country();
        if(logger.isDebugEnabled()) {
            logger.debug("searchPattern " + listFilters.getFilterPattern());
        }

        Client jerseyClient = Client.create();
        jerseyClient.addFilter(new HTTPBasicAuthFilter(username, password));

        WebResource webResource = jerseyClient.resource(searchURI);

        // https://developers.voxbone.com/docs/v3/inventory#path__didgroup.html
        webResource = webResource.queryParam(COUNTRY_CODE_PARAM,iso3Country);
//        Pattern filterPattern = listFilters.getFilterPattern();
//        if(filterPattern != null) {
//            webResource = webResource.queryParam("cityNamePattern" , filterPattern.toString());
//        }
        if(listFilters.getAreaCode() != null) {
            webResource = webResource.queryParam("areaCode", listFilters.getAreaCode());
        }
        if(listFilters.getInRateCenter() != null) {
            webResource = webResource.queryParam("rateCenter", listFilters.getInRateCenter());
        }
        if(listFilters.getSmsEnabled() != null) {
            webResource = webResource.queryParam("featureIds", "6");
        }
        if(listFilters.getFaxEnabled() != null) {
            webResource = webResource.queryParam("featureIds", "25");
        }
        if(listFilters.getRangeIndex() != -1) {
            webResource = webResource.queryParam(PAGE_NUMBER, "" + listFilters.getRangeIndex());
        } else {
            webResource = webResource.queryParam(PAGE_NUMBER, "0");
        }
        if(listFilters.getRangeSize() != -1) {
            webResource = webResource.queryParam(PAGE_SIZE, "" + listFilters.getRangeSize());
        } else {
            webResource = webResource.queryParam(PAGE_SIZE, "50");
        }
        ClientResponse clientResponse = webResource.accept(CONTENT_TYPE).type(CONTENT_TYPE)
                .get(ClientResponse.class);

        String response = clientResponse.getEntity(String.class);
        if(logger.isDebugEnabled())
            logger.debug("response " + response);

        JsonParser parser = new JsonParser();
        JsonObject jsonResponse = parser.parse(response).getAsJsonObject();

//        long count = jsonResponse.getAsJsonPrimitive("count").getAsLong();
//        if(logger.isDebugEnabled()) {
//            logger.debug("Number of numbers found : "+count);
//        }
        JsonArray voxboneNumbers = jsonResponse.getAsJsonArray("didGroups");
        final List<PhoneNumber> numbers = toAvailablePhoneNumbers(voxboneNumbers, listFilters);
        return numbers;
//        } else {
//            logger.warn("Couldn't reach uri for getting Phone Numbers. Response status was: "+response.getStatusLine().getStatusCode());
//        }
//
//        return new ArrayList<PhoneNumber>();
    }

    @Override
    public boolean buyNumber(PhoneNumber phoneNumberObject, PhoneNumberParameters phoneNumberParameters) {
        String phoneNumber = phoneNumberObject.getPhoneNumber();
        Client jerseyClient = Client.create();
        jerseyClient.addFilter(new HTTPBasicAuthFilter(username, password));

        WebResource webResource = jerseyClient.resource(createCartURI);
        ClientResponse clientResponse = webResource.accept(CONTENT_TYPE).type(CONTENT_TYPE).put(ClientResponse.class,"{}");

        String createCartResponse = clientResponse.getEntity(String.class);
        if(logger.isDebugEnabled())
            logger.debug("createCartResponse " + createCartResponse);

        JsonParser parser = new JsonParser();
        JsonObject jsonCreateCartResponse = parser.parse(createCartResponse).getAsJsonObject();
        JsonObject voxCart = jsonCreateCartResponse.get("cart").getAsJsonObject();
        String cartIdentifier = voxCart.get("cartIdentifier").getAsString();

        try {
            Client addToCartJerseyClient = Client.create();
            addToCartJerseyClient.addFilter(new HTTPBasicAuthFilter(username, password));

            WebResource addToCartWebResource = addToCartJerseyClient.resource(createCartURI + "/" + cartIdentifier + "/product");
            String addToCartBody = "{\"didCartItem\":{\"didGroupId\":\"" + phoneNumber + "\",\"quantity\":\"1\"}}";
            ClientResponse addToCartResponse = addToCartWebResource.accept(CONTENT_TYPE).type(CONTENT_TYPE).post(ClientResponse.class,addToCartBody);

            if (addToCartResponse.getClientResponseStatus() == Status.OK) {
                String addToCartResponseString = addToCartResponse.getEntity(String.class);
                if(logger.isDebugEnabled())
                    logger.debug("addToCartResponse " + addToCartResponseString);

                JsonObject jsonAddToCartResponse = parser.parse(addToCartResponseString).getAsJsonObject();

                if(jsonAddToCartResponse.get("status").getAsString().equalsIgnoreCase("SUCCESS")) {
                    Client checkoutCartJerseyClient = Client.create();
                    checkoutCartJerseyClient.addFilter(new HTTPBasicAuthFilter(username, password));

                    WebResource checkoutCartWebResource = checkoutCartJerseyClient.resource(createCartURI + "/" + cartIdentifier + "/checkout");
                    ClientResponse checkoutCartResponse = checkoutCartWebResource.queryParam("cartIdentifier", cartIdentifier).accept(CONTENT_TYPE).type(CONTENT_TYPE).get(ClientResponse.class);

                    if (checkoutCartResponse.getClientResponseStatus() == Status.OK) {
                        String checkoutCartResponseString = checkoutCartResponse.getEntity(String.class);
                        if(logger.isDebugEnabled())
                            logger.debug("checkoutCartResponse " + checkoutCartResponseString);

                        JsonObject jsonCheckoutCartResponse = parser.parse(checkoutCartResponseString).getAsJsonObject();
                        if(jsonCheckoutCartResponse.get("status").getAsString().equalsIgnoreCase("SUCCESS")) {
                            String orderReference = jsonCheckoutCartResponse.get("productCheckoutList").getAsJsonArray().get(0).getAsJsonObject().get("orderReference").getAsString();
                            Client listDidsJerseyClient = Client.create();
                            listDidsJerseyClient.addFilter(new HTTPBasicAuthFilter(username, password));

                            WebResource listDidsWebResource = listDidsJerseyClient.resource(listDidsURI);
                            ClientResponse listDidsResponse = listDidsWebResource.
                                    queryParam("orderReference", orderReference).
                                    queryParam(PAGE_NUMBER, "0").
                                    queryParam(PAGE_SIZE, "50").
                                    accept(CONTENT_TYPE).
                                    type(CONTENT_TYPE).
                                    get(ClientResponse.class);

                            if (listDidsResponse.getClientResponseStatus() == Status.OK) {
                                String listDidsResponseString = listDidsResponse.getEntity(String.class);
                                if(logger.isDebugEnabled())
                                    logger.debug("listDidsResponse " + listDidsResponseString);

                                JsonObject jsonListDidsResponse = parser.parse(listDidsResponseString).getAsJsonObject();
                                JsonObject dids = jsonListDidsResponse.get("dids").getAsJsonArray().get(0).getAsJsonObject();
                                String didId = dids.get("didId").getAsString();
                                String e164 = dids.get("e164").getAsString();
                                phoneNumberObject.setFriendlyName(didId);
                                phoneNumberObject.setPhoneNumber(e164);
                                updateNumber(phoneNumberObject, phoneNumberParameters);
                            } else {
                                return false;
                            }
                        } else {
                            // Handle not enough credit on the account
                            return false;
                        }
                    } else {
                        return false;
                    }
                    // we always return true as the phone number was bought
                    return true;
                } else {
                    return false;
                }
            } else {
                if(logger.isDebugEnabled())
                    logger.debug("Couldn't buy Phone Number " + phoneNumber + ". Response status was: "+ addToCartResponse.getClientResponseStatus());
            }
        } catch (final Exception e) {
            logger.warn("Couldn't reach uri for buying Phone Numbers" + uri, e);
        }

        return false;
    }

    @Override
    public boolean updateNumber(PhoneNumber phoneNumberObj, PhoneNumberParameters phoneNumberParameters) {
        String phoneNumber = phoneNumberObj.getFriendlyName();
        Client jerseyClient = Client.create();
        jerseyClient.addFilter(new HTTPBasicAuthFilter(username, password));

        WebResource webResource = jerseyClient.resource(updateURI);
        String body = "{\"didIds\":[\"" + phoneNumber + "\"],\"voiceUriId\":\"" + voiceUriId + "\"}";
        ClientResponse clientResponse = webResource.accept(CONTENT_TYPE).type(CONTENT_TYPE).post(ClientResponse.class,body);

        String voiceURIResponse = clientResponse.getEntity(String.class);
        if(logger.isDebugEnabled())
            logger.debug("response " + voiceURIResponse);

//        JsonParser parser = new JsonParser();
//        JsonObject jsonCreateCartResponse = parser.parse(voiceURIResponse).getAsJsonObject();

        if (clientResponse.getClientResponseStatus() == Status.OK) {
            return true;
        } else {
            return false;
        }
    }

    @Override
    public boolean cancelNumber(PhoneNumber phoneNumberObj) {
        String phoneNumber = phoneNumberObj.getFriendlyName();
        Client jerseyClient = Client.create();
        jerseyClient.addFilter(new HTTPBasicAuthFilter(username, password));

        WebResource webResource = jerseyClient.resource(cancelURI);

        String body = "{\"didIds\":[\"" + phoneNumber + "\"]}";
        ClientResponse clientResponse = webResource.accept(CONTENT_TYPE).type(CONTENT_TYPE).post(ClientResponse.class,body);

        String response = clientResponse.getEntity(String.class);
        if(logger.isDebugEnabled())
            logger.debug("response " + response);

//        JsonParser parser = new JsonParser();
//        JsonObject jsonResponse = parser.parse(response).getAsJsonObject();

        if (clientResponse.getClientResponseStatus() == Status.OK) {
            return true;
        } else {
            return false;
        }
    }

    @Override
    public List<String> getAvailableCountries() {
        Client jerseyClient = Client.create();
        jerseyClient.addFilter(new HTTPBasicAuthFilter(username, password));

        WebResource webResource = jerseyClient.resource(countriesURI);

        // http://www.voxbone.com/apidoc/resource_InventoryServiceRest.html#path__country.html
        ClientResponse clientResponse = webResource.queryParam(PAGE_NUMBER,"0").queryParam(PAGE_SIZE,"300").accept(CONTENT_TYPE).type(CONTENT_TYPE)
                .get(ClientResponse.class);

        String response = clientResponse.getEntity(String.class);
        if(logger.isDebugEnabled())
            logger.debug("response " + response);

        JsonParser parser = new JsonParser();
        JsonObject jsonResponse = parser.parse(response).getAsJsonObject();
        JsonArray voxCountries = jsonResponse.get("countries").getAsJsonArray();
        List<String> countries = new ArrayList<String>();
        for (int i = 0; i < voxCountries.size(); i++) {
            JsonObject country = voxCountries.get(i).getAsJsonObject();
            String countryCode = country.get(COUNTRY_CODE_PARAM).getAsString();
            countries.add(countryCode);
        }

        return countries;
    }
}
