package gov.va.vinci.dart.biz;

import gov.va.vinci.dart.common.ValidationHelper;
import gov.va.vinci.dart.common.exception.ObjectNotFoundException;
import gov.va.vinci.dart.common.exception.ValidationException;
import gov.va.vinci.dart.service.DartObjectFactory;

import java.util.Date;
import java.util.List;
import java.util.Set;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;

@Entity
@Table(name = "Review", schema = "hib")
public class Review extends BusinessObject {

    public static final String INITIAL_NDS_GROUP = "Initial NDS";
    public static final String FINAL_NDS_GROUP = "Final NDS";

    public static final String WAITING_FOR_REVIEW = "Waiting for Review";
    public static final String WITHDRAWN = "Withdrawn";

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "groupid")
    Group reviewer;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "requestid")
    Request request;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "workflowid")
    protected RequestWorkflow workflow;

    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "approved")
    protected Date approved;

    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "rejected")
    protected Date rejected;

    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "changerequested")
    protected Date changeRequested;

    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "withdrawndate")
    protected Date withdrawnDate;

    @Column(name = "withdrawn", columnDefinition = "BIT", length = 1)
    private boolean withdrawn;

  

    @ManyToMany(fetch = FetchType.LAZY)
    @JoinTable(
            name = "reviewcomment", schema = "hib",
            joinColumns = { @JoinColumn(name = "reviewid", referencedColumnName = "ID") },
            inverseJoinColumns = { @JoinColumn(name = "commentid", referencedColumnName = "ID") })
    Set<Comment> comments;

    Review() {
    }

    public static Review create(final RequestWorkflow workflow, final Request request, final ReviewTemplate template,
            final Group reviewer, final String createdBy) throws ValidationException {

        ValidationHelper.required("Request", request);
        ValidationHelper.required("Review Template", template);
        ValidationHelper.required("Reviewer Group", reviewer);
        ValidationHelper.required("Created By", createdBy);

        Review result = new Review();
        result.createdBy = createdBy;
        result.createdOn = new Date();

        result.name = template.getName();

        result.request = request;
        result.reviewer = reviewer;

        if (workflow != null) {
            result.workflow = workflow; // have a workflow object to associate with this review
        }

        DartObjectFactory.getInstance().getReviewDAO().save(result);

        request.addReview(result);

        if (workflow != null)
            workflow.addReview(result); // add this new review to the list attached to this workflow

        return result;
    }

    public static Review findById(final int reviewId) {
        return DartObjectFactory.getInstance().getReviewDAO().findById(reviewId);
    }

    public static List<Review> listByRequestId(final int requestId) {
        return DartObjectFactory.getInstance().getReviewDAO().listByRequestId(requestId);
    }

    public static List<Review> listByWorkflowId(final int workflowId) {
        return DartObjectFactory.getInstance().getReviewDAO().listByWorkflowId(workflowId);
    }

    public static List<Review> listByRequestIdAndWorkflowId(final int requestId, final int workflowId) {
        return DartObjectFactory.getInstance().getReviewDAO().listByRequestIdAndWorkflowId(requestId, workflowId);
    }

    public static List<Review> listByGroupId(final int groupId) {
        return DartObjectFactory.getInstance().getReviewDAO().listByGroupId(groupId);
    }

    public static List<Review> listByRequestIdAndGroupId(final int requestId, final int groupId) {
        return DartObjectFactory.getInstance().getReviewDAO().listByRequestIdAndGroupId(requestId, groupId);
    }

    public void delete() {
        DartObjectFactory.getInstance().getReviewDAO().delete(this);
    }

    // if changes are requested, once the review is re-submitted, clear out the status values
    public void clearStatus(final String userId) {
        approved = null;
        rejected = null;
        changeRequested = null;
        updatedOn = new Date();
        updatedBy = userId;
    }

    public void approve(final String userId) {
        approved = new Date();
        rejected = null;
        changeRequested = null;
        updatedOn = new Date();
        updatedBy = userId;
    }

    public void reject(final String userId) throws ValidationException, ObjectNotFoundException {
        approved = null;
        rejected = new Date();
        changeRequested = null;
        updatedOn = new Date();
        updatedBy = userId;
    }

    public void requestChanges(final String userId) {
        approved = null;
        rejected = null;
        changeRequested = new Date();
        updatedOn = new Date();
        updatedBy = userId;
    }

    public void withdrawReview(final String userId) {
        updatedOn = new Date();
        withdrawnDate = new Date();
        withdrawn = true;
        updatedBy = userId;
    }
    
    public Date getWithdrawnDate() {
        return withdrawnDate;
    }

    public void setWithdrawnDate(Date withdrawnDate) {
        this.withdrawnDate = withdrawnDate;
    }

    public boolean isWithdrawn() {
        return withdrawn;
    }

    public void setWithdrawn(boolean withdrawn) {
        this.withdrawn = withdrawn;
    }

    public Request getRequest() {
        return request;
    }

    public RequestWorkflow getWorkflow() {
        return workflow;
    }

    public boolean isApproved() {
        return approved != null;
    }

    public boolean isRejected() {
        return rejected != null;
    }

    public boolean isChangeRequested() {
        if (isWithdrawn()){
            return false;
        }
        return changeRequested != null;
    }

    public Group getReviewer() {
        return reviewer;
    }

    public void setReviewer(Group reviewer) {
        this.reviewer = reviewer;
    }

    public Set<Comment> getComments() {
        return comments;
    }

    public Comment getComment(final int reviewId) throws ObjectNotFoundException {

        for (Comment comment : getComments()) {
            if (comment != null && comment.getId() == reviewId) {
                return comment;
            }
        }

        throw new ObjectNotFoundException("Comment " + reviewId + " not found.");
    }

    /**
     * Returns a String containing the status of this (intermediate) review.
     * 
     * @return
     */
    public String getReviewStatus() {

        String reviewStatus = "";
        if (isWithdrawn())
            reviewStatus = Review.WITHDRAWN;
        else if (isApproved())   
            reviewStatus = RequestStatus.APPROVED.getName();
        else if (isRejected())
            reviewStatus = RequestStatus.DENIED.getName();
        else if (isChangeRequested())
            reviewStatus = RequestStatus.CHANGE_REQUESTED.getName();
        else 
            reviewStatus = Review.WAITING_FOR_REVIEW;

        return reviewStatus;
    }

    // necessary to use (List<Review>).contains()
    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }

        if ((obj instanceof Review) == false) {
            return false;
        }

        Review rs2 = (Review) obj;
        return rs2.getId() == this.getId();
    }

}
