package com.twilio.base;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.twilio.exception.ApiConnectionException;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class Page<T> {
    private final List<T> records;
    private final String firstPageUrl;
    private final String firstPageUri;
    private final String nextPageUrl;
    private final String nextPageUri;
    private final String previousPageUrl;
    private final String previousPageUri;
    private final String url;
    private final String uri;
    private final int pageSize;

    private Page(Builder<T> b) {
        this.records = b.records;
        this.firstPageUri = b.firstPageUri;
        this.firstPageUrl = b.firstPageUrl;
        this.nextPageUri = b.nextPageUri;
        this.nextPageUrl = b.nextPageUrl;
        this.previousPageUri = b.previousPageUri;
        this.previousPageUrl = b.previousPageUrl;
        this.uri = b.uri;
        this.url = b.url;
        this.pageSize = b.pageSize;
    }

    private String urlFromUri(String domain, String uri) {
        return "https://" + domain + ".twilio.com" + uri;
    }

    public List<T> getRecords() {
        return records;
    }

    /**
     * Generate first page url for a list result.
     *
     * @param domain domain to use
     * @return the first page url
     */
    public String getFirstPageUrl(String domain) {
        if (firstPageUrl != null) {
            return firstPageUrl;
        }

        return urlFromUri(domain, firstPageUri);
    }

    /**
     * Generate next page url for a list result.
     *
     * @param domain domain to use
     * @return the next page url
     */
    public String getNextPageUrl(String domain) {
        if (nextPageUrl != null) {
            return nextPageUrl;
        }

        return urlFromUri(domain, nextPageUri);
    }

    /**
     * Generate previous page url for a list result.
     *
     * @param domain domain to use
     * @return the previous page url
     */
    public String getPreviousPageUrl(String domain) {
        if (previousPageUrl != null) {
            return previousPageUrl;
        }

        return urlFromUri(domain, previousPageUri);
    }

    public int getPageSize() {
        return pageSize;
    }

    /**
     * Generate page url for a list result.
     *
     * @param domain domain to use
     * @return the page url
     */
    public String getUrl(String domain) {
        if (url != null) {
            return url;
        }

        return urlFromUri(domain, uri);
    }

    public boolean hasNextPage() {
        return (nextPageUri != null && !nextPageUri.isEmpty()) || (nextPageUrl != null && !nextPageUrl.isEmpty());
    }

    /**
     * Create a new page of data from a json blob.
     *
     * @param recordKey  key which holds the records
     * @param json       json blob
     * @param recordType resource type
     * @param mapper     json parser
     * @param <T>        record class type
     * @return a page of records of type T
     */
    public static <T> Page<T> fromJson(String recordKey, String json, Class<T> recordType, ObjectMapper mapper) {
        try {
            List<T> results = new ArrayList<>();
            JsonNode root = mapper.readTree(json);
            JsonNode records = root.get(recordKey);
            for (final JsonNode record : records) {
                results.add(mapper.readValue(record.toString(), recordType));
            }

            JsonNode uriNode = root.get("uri");
            if (uriNode != null) {
                return buildPage(root, results);
            } else {
                return buildNextGenPage(root, results);
            }

        } catch (final IOException e) {
            throw new ApiConnectionException(
                "Unable to deserialize response: " + e.getMessage() + "\nJSON: " + json, e
            );
        }
    }

    private static <T> Page<T> buildPage(JsonNode root, List<T> results) {
        Builder<T> builder = new Builder<T>()
            .uri(root.get("uri").asText());

        JsonNode nextPageNode = root.get("next_page_uri");
        if (nextPageNode != null && !nextPageNode.isNull()) {
            builder.nextPageUri(nextPageNode.asText());
        }

        JsonNode previousPageNode = root.get("previous_page_uri");
        if (previousPageNode != null && !previousPageNode.isNull()) {
            builder.previousPageUri(previousPageNode.asText());
        }

        JsonNode firstPageNode = root.get("first_page_uri");
        if (firstPageNode != null && !firstPageNode.isNull()) {
            builder.firstPageUri(firstPageNode.asText());
        }

        JsonNode pageSizeNode = root.get("page_size");
        if (pageSizeNode != null && !pageSizeNode.isNull()) {
            builder.pageSize(pageSizeNode.asInt());
        } else {
            builder.pageSize(results.size());
        }

        return builder.records(results).build();
    }

    private static <T> Page<T> buildNextGenPage(JsonNode root, List<T> results) {
        JsonNode meta = root.get("meta");
        Builder<T> builder = new Builder<T>().url(meta.get("url").asText());

        JsonNode nextPageNode = meta.get("next_page_url");
        if (!nextPageNode.isNull()) {
            builder.nextPageUrl(nextPageNode.asText());
        }

        JsonNode previousPageNode = meta.get("previous_page_url");
        if (!previousPageNode.isNull()) {
            builder.previousPageUrl(previousPageNode.asText());
        }

        JsonNode firstPageNode = meta.get("first_page_url");
        if (!firstPageNode.isNull()) {
            builder.firstPageUrl(firstPageNode.asText());
        }

        JsonNode pageSizeNode = meta.get("page_size");
        if (!pageSizeNode.isNull()) {
            builder.pageSize(pageSizeNode.asInt());
        } else {
            builder.pageSize(results.size());
        }

        return builder.records(results).build();
    }

    private static class Builder<T> {
        private List<T> records;
        private String firstPageUrl;
        private String firstPageUri;
        private String nextPageUrl;
        private String nextPageUri;
        private String previousPageUrl;
        private String previousPageUri;
        private String uri;
        private String url;
        private int pageSize;

        public Builder<T> records(List<T> records) {
            this.records = records;
            return this;
        }

        public Builder<T> firstPageUri(String firstPageUri) {
            this.firstPageUri = firstPageUri;
            return this;
        }

        public Builder<T> firstPageUrl(String firstPageUrl) {
            this.firstPageUrl = firstPageUrl;
            return this;
        }

        public Builder<T> nextPageUri(String nextPageUri) {
            this.nextPageUri = nextPageUri;
            return this;
        }

        public Builder<T> nextPageUrl(String nextPageUrl) {
            this.nextPageUrl = nextPageUrl;
            return this;
        }

        public Builder<T> previousPageUri(String previousPageUri) {
            this.previousPageUri = previousPageUri;
            return this;
        }

        public Builder<T> previousPageUrl(String previousPageUrl) {
            this.previousPageUrl = previousPageUrl;
            return this;
        }

        public Builder<T> uri(String uri) {
            this.uri = uri;
            return this;
        }

        public Builder<T> url(String url) {
            this.url = url;
            return this;
        }

        public Builder<T> pageSize(int pageSize) {
            this.pageSize = pageSize;
            return this;
        }

        public Page<T> build() {
            return new Page<>(this);
        }
    }
}
