package eu.dnetlib.repo.manager.service;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import eu.dnetlib.domain.data.Repository;
import eu.dnetlib.repo.manager.shared.BrokerException;
import eu.dnetlib.repo.manager.shared.Term;
import eu.dnetlib.repo.manager.shared.Tuple;
import eu.dnetlib.repo.manager.shared.broker.*;
import org.json.JSONException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.*;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;

import javax.annotation.PostConstruct;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.*;

@Service("brokerService")
public class BrokerServiceImpl implements BrokerService {

    @Autowired
    private RepositoryServiceImpl repoAPI;
    @Value("${services.broker.url}:${services.broker.port}/${services.broker.api}${services.broker.openaire}")
    private String openairePath;
    @Value("${services.broker.url}:${services.broker.port}/${services.broker.api}")
    private String apiPath;
    @Value("${topic_types.url}")
    private String topicsURL;

    private static final org.apache.log4j.Logger LOGGER = org.apache.log4j.Logger
            .getLogger(BrokerServiceImpl.class);

    @Autowired
    RestTemplate restTemplate ;

    private HttpHeaders httpHeaders;

    private HashMap<String,Term> topics = new HashMap<String, Term>();

    @Autowired
    private EmailUtils emailUtils;

    @PostConstruct
    private void initDnetTopicsMap() {

        httpHeaders = new HttpHeaders();
        httpHeaders.set("Content-Type", "application/json");

        LOGGER.debug("Init dnet topics!");
        InputStream is = null;
        try {
            is = new URL(topicsURL).openStream();
            ObjectMapper mapper = new ObjectMapper();
            JsonNode root = mapper.readTree(is);
            for (JsonNode term : root.path("terms") )
                topics.put(term.path("code").textValue(), parseTerm(term));
        } catch (IOException e) {
            LOGGER.debug("Exception on initDnetTopicsMap" , e);
            emailUtils.reportException(e);
        }
    }

    private Term parseTerm(JsonNode term) {
        return new Term(term.path("englishName").textValue(),term.path("nativeName").textValue(),
                term.path("encoding").textValue(),term.path("code").textValue());
    }


    @Override
    public DatasourcesBroker getDatasourcesOfUser(String user,String includeShared,String includeByOthers) throws JSONException {

        DatasourcesBroker ret = new DatasourcesBroker();
        try {
            ret.setDatasourcesOfUser(getDatasourcesOfUserType(getRepositoriesOfUser(user)));
            //TODO fix bug when values are true
            if (Boolean.parseBoolean(includeShared)) {
                List<String> sharedDatasourceIds = new ArrayList<String>();
                ret.setSharedDatasources(getDatasourcesOfUserType(getRepositoriesByIds(sharedDatasourceIds)));
            }

            if (Boolean.parseBoolean(includeByOthers)) {
                ret.setDatasourcesOfOthers(getDatasourcesOfUserType(getRepositoriesOfUser(user)));
            }
        } catch (BrokerException e) {
            LOGGER.debug("Exception on getDatasourcesOfUser" , e);
            emailUtils.reportException(e);
        }

        return ret;
    }

    @Override
    public List<BrowseEntry> getTopicsForDatasource(String datasourceName) throws BrokerException {
        final String service = "/topicsForDatasource";

        UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(openairePath + service)
                .queryParam("ds", datasourceName);

        ResponseEntity<List<BrowseEntry>> resp;
        try {
            resp = restTemplate.exchange(
                    builder.build().encode().toUri(),
                    HttpMethod.GET,
                    null,
                    new ParameterizedTypeReference<List<BrowseEntry>>() {
                    });
        } catch (RestClientException e) {
            LOGGER.debug("Exception on getTopicsForDatasource" , e);
            emailUtils.reportException(e);
            throw new BrokerException(e);
        }

        return resp.getBody();
    }

    @Override
    public EventsPage advancedShowEvents(String page,
                                         String size,
                                         AdvQueryObject advQueryObject) throws BrokerException, JSONException ,IOException {

        final String service = "/events/{page}/{pageSize}";

        Map<String, Long> uriParams = new HashMap<>();
        uriParams.put("page", Long.parseLong(page));
        uriParams.put("pageSize", Long.parseLong(size));

        UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(openairePath + service);

        MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
        advQueryObject.setPage(Long.parseLong(page));
        HttpEntity<AdvQueryObject> entity = new HttpEntity<>(advQueryObject, httpHeaders);
        ResponseEntity<EventsPage> resp;
        try {
            resp = restTemplate.exchange(
                    builder.buildAndExpand(uriParams).encode().toUri(),
                    HttpMethod.POST,
                    entity,
                    new ParameterizedTypeReference<EventsPage>() {
                    }
            );
        } catch (RestClientException e) {
            LOGGER.debug("Exception on advancedShowEvents" , e);
            emailUtils.reportException(e);
            throw new BrokerException(e);
        }
        return resp.getBody();


    }


    private List<Tuple<BrowseEntry, String>> getDatasourcesOfUserType(List<Repository> repositories) throws BrokerException {

        List<Tuple<BrowseEntry, String>> entries = new ArrayList<>();
        for (Repository repo : repositories) {
            BrowseEntry temp = new BrowseEntry();
            temp.setValue(repo.getOfficialName());
            temp.setSize(new Long(0));
            for (BrowseEntry e : getTopicsForDatasource(repo.getOfficialName())) {
                temp.setSize(temp.getSize() + e.getSize());
            }
            Tuple<BrowseEntry, String> tup = new Tuple<>(temp, repo.getLogoUrl());
            entries.add(tup);
        }

        // sort the collection by the second field of the tuple which is size
        Collections.sort(entries, new Comparator<Tuple<BrowseEntry, String>>() {
            @Override
            public int compare(Tuple<BrowseEntry, String> e1, Tuple<BrowseEntry, String> e2) {
                return (int) (e2.getFirst().getSize().longValue() - e1.getFirst().getSize().longValue());
            }
        });

        return entries;
    }

    private List<Repository> getRepositoriesOfUser(String userEmail) throws JSONException {

        int page = 0;
        int size = 50;
        List<Repository> rs ;
        List<Repository> resultSet = new ArrayList<>();

        while (true){
            rs = repoAPI.getRepositoriesOfUser(userEmail, String.valueOf(page), String.valueOf(size));
            resultSet.addAll(rs);
            page+=1;
            if(rs.size() == 0) break;
        }
        return resultSet;
    }

    private List<Repository> getRepositoriesByIds(List<String> sharedDatasourceIds) {
        return null;
    }

    @Override
    public EventsPage showEvents(String datasourceName,
                                 String topic,
                                 String page,
                                 String size) throws BrokerException, JSONException {

        final String service = "/events";

        UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(openairePath + service)
                .queryParam("ds", datasourceName)
                .queryParam("topic", topic)
                .path("/{page}/{size}/");

        ResponseEntity<EventsPage> resp;
        try {
            resp = restTemplate.exchange(
                    builder.build().expand(page, size).encode().toUri(),
                    HttpMethod.GET,
                    null,
                    new ParameterizedTypeReference<EventsPage>() {
                    });
        } catch (RestClientException e) {
            LOGGER.debug("Exception on showEvents" , e);
            emailUtils.reportException(e);
            throw new BrokerException(e);
        }
        return resp.getBody();
    }

    @Override
    public Map<String, List<SimpleSubscriptionDesc>> getSimpleSubscriptionsOfUser(String userEmail)
            throws BrokerException {

        final String service = "/subscriptions";

        UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(openairePath + service)
                .queryParam("email", userEmail);

        LOGGER.debug(builder.build().encode().toUri());
        ResponseEntity<Map<String, List<SimpleSubscriptionDesc>>> resp;
        try {
            resp = restTemplate.exchange(
                    builder.build().encode().toUri(),
                    HttpMethod.GET,
                    null,
                    new ParameterizedTypeReference<Map<String, List<SimpleSubscriptionDesc>>>() {
                    });
        } catch (RestClientException e) {
            LOGGER.debug("Exception on getSimpleSubscriptionsOfUser" , e);
            emailUtils.reportException(e);
            throw new BrokerException(e);
        }
        return resp.getBody();
    }

    @Override
    public Subscription subscribe(OpenaireSubscription obj) throws BrokerException {
        final String service = "/subscribe";

        //build the uri params
        UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(openairePath + service);

        HttpEntity<OpenaireSubscription> entity = new HttpEntity<>(obj, httpHeaders);

        //create new template engine
        RestTemplate template = new RestTemplate();
        template.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
        ResponseEntity<Subscription> resp;
        try {
            //communicate with endpoint
            resp = restTemplate.exchange(
                    builder.build().encode().toUri(),
                    HttpMethod.POST,
                    entity,
                    new ParameterizedTypeReference<Subscription>() {
                    });
        } catch (RestClientException e) {
            LOGGER.debug("Exception on OpenaireSubscription" , e);
            emailUtils.reportException(e);
            throw new BrokerException(e);
        }

        return resp.getBody();
    }

    @Override
    public ResponseEntity<Object> unsubscribe(String subscriptionId) throws BrokerException {
        final String service = "/subscriptions/" + subscriptionId;

        //build the uri params
        UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(apiPath + service);

        try {
            //communicate with endpoint
            restTemplate.exchange(
                    builder.build().encode().toUri(),
                    HttpMethod.DELETE,
                    null,
                    new ParameterizedTypeReference<Void>() {
                    });
        } catch (RestClientException e) {
            LOGGER.debug("Exception on unsubscribe" , e);
            emailUtils.reportException(e);
            throw new BrokerException(e);
        }
        return new ResponseEntity<>("OK",HttpStatus.OK);
    }

    @Override
    public Subscription getSubscription( String subscriptionId) throws BrokerException {
        final String service = "/subscriptions/" + subscriptionId;

        //build the uri params
        UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(apiPath + service);

        ResponseEntity<Subscription> resp;
        try {
            //communicate with endpoint
            resp = restTemplate.exchange(
                    builder.build().encode().toUri(),
                    HttpMethod.GET,
                    null,
                    new ParameterizedTypeReference<Subscription>() {
                    });
        } catch (RestClientException e) {
            LOGGER.debug("Exception on getSubscription" , e);
            emailUtils.reportException(e);
            throw new BrokerException(e);
        }
        return resp.getBody();
    }

    @Override
    public Map<String, Term> getDnetTopics() throws BrokerException {
        return topics;
    }

    @Override
    public EventsPage getNotificationsBySubscriptionId(String subscriptionId,
                                                       String page,
                                                       String size) throws BrokerException {

        UriComponents uriComponents = UriComponentsBuilder
                .fromHttpUrl(openairePath + "/notifications/")
                .path("/{id}/{page}/{size}/")
                .build().expand(subscriptionId,page, size).encode();

        ResponseEntity<EventsPage> resp;
        try {
            resp = restTemplate.exchange(
                    uriComponents.toUri(),
                    HttpMethod.GET,
                    null,
                    new ParameterizedTypeReference<EventsPage>() {
                    });
        } catch (RestClientException e) {
            LOGGER.debug("Exception on getNotificationsBySubscriptionId" , e);
            emailUtils.reportException(e);
            throw new BrokerException(e);
        }
        return resp.getBody();
    }

    //@Override
    public Map<String, List<Subscription>> getSubscriptionsOfUser(String userEmail)
            throws BrokerException {

        Map<String, List<SimpleSubscriptionDesc>> simpleSubs = getSimpleSubscriptionsOfUser(userEmail);
        Map<String,List<Subscription>> subs = new HashMap<>();
        List<Subscription> subscriptions = null;

        for(String s:simpleSubs.keySet()){
            List<SimpleSubscriptionDesc> simpleSubscriptionDescs = simpleSubs.get(s);
            for(SimpleSubscriptionDesc simpleSubscriptionDesc : simpleSubscriptionDescs) {
                subscriptions = new ArrayList<>();
                subscriptions.add(getSubscription(simpleSubscriptionDesc.getId()));
            }
            subs.put(s,subscriptions);
        }
        return subs;
    }


}
