package com.atlassian.bitbucket.rest.pull;

import com.atlassian.bitbucket.property.PropertyMap;
import com.atlassian.bitbucket.pull.*;
import com.atlassian.bitbucket.rest.annotation.JsonSurrogate;
import com.atlassian.bitbucket.rest.RestLinkedMapEntity;
import com.atlassian.bitbucket.rest.util.RestNamedLink;
import com.atlassian.bitbucket.rest.util.RestPage;
import com.atlassian.bitbucket.rest.util.RestRelatedLinks;
import com.google.common.collect.Sets;
import org.apache.commons.lang3.StringUtils;
import org.codehaus.jackson.map.annotate.JsonSerialize;

import javax.annotation.Nullable;
import java.util.*;
import java.util.function.Function;

import static com.atlassian.bitbucket.rest.property.RestPropertySupport.PROPERTIES;
import static com.atlassian.bitbucket.rest.util.RestDocUtils.pageOf;
import static com.atlassian.bitbucket.rest.util.RestDocUtils.selfLink;
import static com.google.common.collect.Sets.newHashSet;

@JsonSerialize
@JsonSurrogate(PullRequest.class)
public class RestPullRequest extends RestLinkedMapEntity {

    public static final String AUTHOR = "author";
    public static final String CLOSED = "closed";
    public static final String CREATED_DATE = "createdDate";
    public static final String DESCRIPTION = "description";
    public static final String DESCRIPTION_AS_HTML = "descriptionAsHtml";
    public static final String FROM_REF = "fromRef";
    public static final String ID = "id";
    public static final String LOCKED = "locked";
    public static final String OPEN = "open";
    public static final String PARTICIPANTS = "participants";
    public static final String REVIEWERS = "reviewers";
    public static final String STATE = "state";
    public static final String TITLE = "title";
    public static final String TO_REF = "toRef";
    public static final String UPDATED_DATE = "updatedDate";
    public static final String VERSION = "version";

    public static final Function<PullRequest, RestPullRequest> REST_TRANSFORM = RestPullRequest::new;

    @SuppressWarnings("unused") //Required for Jersey
    public RestPullRequest() {
    }

    public RestPullRequest(PullRequest pullRequest) {
        this(pullRequest.getId(), pullRequest.getVersion(), pullRequest.getTitle(), pullRequest.getDescription(),
                pullRequest.getState(), pullRequest.getCreatedDate(), pullRequest.getUpdatedDate(),
                new RestPullRequestRef(pullRequest.getFromRef()),
                new RestPullRequestRef(pullRequest.getToRef()),
                pullRequest.isLocked(),
                new RestPullRequestParticipant(pullRequest.getAuthor()),
                getRestParticipants(pullRequest.getReviewers()),
                getRestParticipants(pullRequest.getParticipants()),
                pullRequest.getProperties(), null);
    }

    private RestPullRequest(Map<String, Object> values) {
        super(values);
    }

    // Required for REST documentation
    private RestPullRequest(long id, int version, String title, String description, PullRequestState state,
                            Date created, Date updated, RestPullRequestRef from, RestPullRequestRef to,
                            Boolean locked, RestPullRequestParticipant author, Set<RestPullRequestParticipant> reviewers,
                            Set<RestPullRequestParticipant> participants, PropertyMap properties,
                            RestNamedLink selfLink) {
        if (id > 0L) {
            put(ID, id);
        }
        if (version != -1) {
            put(VERSION, version);
        }
        putIfNotNull(TITLE, title);
        putIfNotNull(DESCRIPTION, description);
        if (state != null) {
            put(STATE, state);
            put(OPEN, state == PullRequestState.OPEN);
            put(CLOSED, state != PullRequestState.OPEN);
        }
        putIfNotNull(CREATED_DATE, created);
        putIfNotNull(UPDATED_DATE, updated);
        putIfNotNull(FROM_REF, from);
        putIfNotNull(TO_REF, to);
        putIfNotNull(LOCKED, locked);
        putIfNotNull(AUTHOR, author);
        putIfNotNull(REVIEWERS, reviewers);
        putIfNotNull(PARTICIPANTS, participants);
        putIfNotEmpty(PROPERTIES, properties);

        setLinks(new RestRelatedLinks.Builder()
                .addRelatedLink(RestRelatedLinks.RELATIONSHIP_SELF, selfLink)
                .build());
    }

    public String getDescription() {
        return StringUtils.trimToNull(getStringProperty(DESCRIPTION));
    }

    public long getId() {
        return getLongProperty(ID);
    }

    public RestPullRequestRef getFromRef() {
        return RestPullRequestRef.valueOf(get(FROM_REF));
    }

    public Set<RestPullRequestParticipant> getReviewers() {
        return getParticipants(REVIEWERS);
    }

    public String getTitle() {
        return StringUtils.trimToNull(getStringProperty(TITLE));
    }

    public RestPullRequestRef getToRef() {
        return RestPullRequestRef.valueOf(get(TO_REF));
    }

    public int getVersion() {
        return getIntProperty(VERSION);
    }

    public boolean hasAuthor() {
        return containsKey(AUTHOR);
    }

    public boolean hasDescription() {
        return getStringProperty(DESCRIPTION) != null;
    }

    public boolean hasParticipants() {
        return containsKey(PARTICIPANTS);
    }

    public boolean hasReviewers() {
        return containsKey(REVIEWERS);
    }

    public boolean hasTitle() {
        return getTitle() != null;
    }

    public boolean hasVersion() {
        return containsKey(VERSION);
    }

    public void setHtmlDescription(String html) {
        put(DESCRIPTION_AS_HTML, html);
    }

    @Nullable
    @SuppressWarnings("unchecked")
    public static RestPullRequest valueOf(@Nullable Object value) {
        if (value instanceof RestPullRequest) {
            return (RestPullRequest) value;
        } else if (value instanceof Map) {
            return new RestPullRequest((Map) value);
        }
        return null;
    }

    private Set<RestPullRequestParticipant> getParticipants(String key) {
        Object value = get(key);

        Collection<?> rawReviewers = value instanceof Collection ? (Collection) value : Collections.emptySet();
        if (!rawReviewers.isEmpty()) {
            Set<RestPullRequestParticipant> reviewers = new HashSet<>(rawReviewers.size());
            for (Object rawReviewer : rawReviewers) {
                RestPullRequestParticipant reviewer = RestPullRequestParticipant.valueOf(rawReviewer);
                if (reviewer != null) {
                    reviewers.add(reviewer);
                }
            }
            return reviewers;
        }
        return Collections.emptySet();
    }

    private static Set<RestPullRequestParticipant> getRestParticipants(Set<PullRequestParticipant> participants) {
        return transform(participants, RestPullRequestParticipant.REST_TRANSFORM);
    }

    // REST Documentation Examples

    public static final RestPullRequest REQUEST_EXAMPLE = new RestPullRequest(-1L, -1, "Talking Nerdy",
            "It’s a kludge, but put the tuple from the database in the cache.", PullRequestState.OPEN,
            null, null, RestPullRequestRef.REQUEST_FEATURE_EXAMPLE,
            RestPullRequestRef.REQUEST_MASTER_EXAMPLE, false, null,
            newHashSet(RestPullRequestParticipant.REQUEST_EXAMPLE_USER_ONLY), null, null, null);

    public static final RestPullRequest REQUEST_EXAMPLE_UPDATE = new RestPullRequest(101L, 2, "Talking Nerdy",
            "It’s a hack, but force AO to eagerly initialize to avoid deadlocks.", null, null, null, null, null, null,
            null, newHashSet(RestPullRequestParticipant.REQUEST_EXAMPLE_USER_ONLY), null, null, null);

    public static final RestPullRequest RESPONSE_EXAMPLE_BRAND_NEW = new RestPullRequest(101L, 1, "Talking Nerdy",
            "It’s a kludge, but put the tuple from the database in the cache.", PullRequestState.OPEN,
            new Date(1359075920), new Date(1359075920), RestPullRequestRef.RESPONSE_FEATURE_EXAMPLE,
            RestPullRequestRef.RESPONSE_MASTER_EXAMPLE, false,
            RestPullRequestParticipant.newExample("alice", PullRequestRole.PARTICIPANT,
                    PullRequestParticipantStatus.UNAPPROVED),
            newHashSet(RestPullRequestParticipant.EXAMPLE_RESPONSE_REVIEWER_UNAPPROVED),
            Sets.<RestPullRequestParticipant>newHashSet(), null, selfLink(PullRequest.class));

    public static final RestPullRequest RESPONSE_EXAMPLE = new RestPullRequest(101L, 1, "Talking Nerdy",
            "It’s a kludge, but put the tuple from the database in the cache.", PullRequestState.OPEN,
            new Date(1359075920), new Date(1359085920), RestPullRequestRef.REQUEST_FEATURE_EXAMPLE,
            RestPullRequestRef.REQUEST_MASTER_EXAMPLE, false,
            RestPullRequestParticipant.newExample("tom", PullRequestRole.AUTHOR, PullRequestParticipantStatus.APPROVED),
            newHashSet(RestPullRequestParticipant.EXAMPLE_RESPONSE_REVIEWER_APPROVED),
            newHashSet(RestPullRequestParticipant.newExample("dick", PullRequestRole.PARTICIPANT,
                            PullRequestParticipantStatus.UNAPPROVED),
                    RestPullRequestParticipant.newExample("harry", PullRequestRole.PARTICIPANT,
                            PullRequestParticipantStatus.APPROVED)),
            PropertyMap.EMPTY, selfLink(PullRequest.class));

    public static final RestPullRequest RESPONSE_EXAMPLE_MERGED = new RestPullRequest(101L, 1, "Talking Nerdy",
            "It’s a kludge, but put the tuple from the database in the cache.", PullRequestState.MERGED,
            new Date(1359075920), new Date(1359115920), RestPullRequestRef.REQUEST_FEATURE_EXAMPLE,
            RestPullRequestRef.REQUEST_MASTER_EXAMPLE, false,
            RestPullRequestParticipant.newExample("tom", PullRequestRole.AUTHOR, PullRequestParticipantStatus.APPROVED),
            newHashSet(RestPullRequestParticipant.EXAMPLE_RESPONSE_REVIEWER_APPROVED),
            newHashSet(RestPullRequestParticipant.newExample("dick", PullRequestRole.PARTICIPANT,
                            PullRequestParticipantStatus.APPROVED),
                    RestPullRequestParticipant.newExample("harry", PullRequestRole.PARTICIPANT,
                            PullRequestParticipantStatus.APPROVED)),
            new PropertyMap.Builder().property("commentCount", 3).build(),
            selfLink(PullRequest.class));

    public static final RestPage<RestPullRequest> PAGE_EXAMPLE = pageOf(RESPONSE_EXAMPLE);
}
