package gov.va.vinci.dart.wf2;

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

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.stereotype.Service;

import gov.va.vinci.dart.biz.Comment;
import gov.va.vinci.dart.biz.Event;
import gov.va.vinci.dart.biz.EventType;
import gov.va.vinci.dart.biz.Group;
import gov.va.vinci.dart.biz.Request;
import gov.va.vinci.dart.biz.Review;
import gov.va.vinci.dart.biz.RequestWorkflow;
import gov.va.vinci.dart.json.ReviewStatusListView;
import gov.va.vinci.dart.json.ReviewStatusView;
import gov.va.vinci.dart.json.builder.ReviewStatusViewBuilder;


/** Independent Workflow:  A workflow for VINCI DART requests that select Data Sources controlled by groups other than NDS.
 * 
 */
@Service
public class WfIndependent extends AbstractWorkflow {
	private static Log log = LogFactory.getLog(WfIndependent.class);


	public static final int INDEPENDENT_REVIEW_STATE = 2;
	public static final int FINAL_STATE = 16;


//	public static final long INIT_MASK = 0L;	//unused for this workflow type



	/** Get the final state for the current workflow item.
	 * 
	 */
	@Override 
	public int getFinalState() {
		return FINAL_STATE;
	}
	
	@Override
	public int getFinalReviewState() {
		return INDEPENDENT_REVIEW_STATE;
	}

	@Override
	public int getSubmittedState() {
		return INDEPENDENT_REVIEW_STATE;
	}
	
	
	/** Calculate the percentage of review completion on the request.
	 * 
	 * @return
	 */
	@Override
	public String calculateReviewCompletion( final RequestWorkflow workflow, final Request request ) {

		//only one review to be performed for the Independent Workflow
		if( workflow != null && workflow.isCompleted() == true ) {
			return "100%";	//only one review to be performed for the Independent Workflow
		}
		
		return "0%";
	}

	
	/**
	 * Returns true if ready for the initial review, based on workflow state
	 * @return
	 */
	@Override
	public boolean isReadyForInitialReview( final RequestWorkflow workflow, final Request request ) {

		if( request != null ) {
		
			try {
				int workflowState = -1;
				if( workflow != null ) {
					workflowState = workflow.getWorkflowState();	//specific workflow state
				} else {
					workflowState = request.getWorkflowState();		//top-level workflow state (old data)
				}


//testing -- we might just want to check the state of the only review attached to this request
				if( workflowState == getSubmittedState() && request.isSubmittedOrChanged(workflow) ) {	//submitted or change requested
					
					if( request.allReviewsCompleted(workflow) == false ) {	//review not yet completed?
						return true;
					}//end if
					
				}//end if

			} catch (Exception e) {
				log.debug("Exception occurred while determining the initial review state:  " + e.getMessage());
				e.printStackTrace();
			}

		}//end if
			
		return false;
	}


	/**
	 * Returns true if the initial review is completed
	 * @return
	 */
	@Override
	public boolean isInitialReviewCompleted( final RequestWorkflow workflow, final Request request ) {

		if( request != null ) {
			
			try {
				int workflowState = -1;
				if( workflow != null ) {
					workflowState = workflow.getWorkflowState();	//specific workflow state
				} else {
					workflowState = request.getWorkflowState();		//top-level workflow state (old data)
				}

				if( workflowState == getFinalState() ) {

					//TODO: could use the existing function
					//return (request.allReviewsCompleted(workflow));
					
					if( workflow != null ) {
						Set<Review> reviewSet = workflow.getReviews();
						if( reviewSet != null ) {
							for( Review review : reviewSet ) {
								if( review != null ) {
									
									if( review.isApproved() || review.isRejected() ) {	//only one review for the Independent Workflow
										return true;
									}//end if

								}//end if
							}//end for
						}//end if
					}//end if

				}//end if
			
			} catch( Exception e ) {
				log.debug("Exception occurred while determining the initial review state:  " + e.getMessage());
				e.printStackTrace();
			}
		}//end if
		
		return false;
	}
	
	/**
	 * Returns true if ready for the final review (only a single review for the Independent Workflow)
	 * @return
	 */
	@Override
	public boolean isReadyForFinalReview( final RequestWorkflow workflow, final Request request ) {
		return (isReadyForInitialReview(workflow, request));	//only a single review for the Independent Workflow
	}
	
	/**
	 * Returns true if the final review has been completed (only a single review for the Independent Workflow)
	 * @return
	 */
	@Override
	public boolean isFinalReviewCompleted( final RequestWorkflow workflow, final Request request ) {
		return (isInitialReviewCompleted(workflow, request));	//only a single review for the Independent Workflow
	}


	/**
	 * Retrieves the list of ReviewStatusView details for the specified Independent workflow.
	 * Used for the Median Wait Time display.
	 * 
	 * @param workflow
	 * @param request
	 * @param result
	 */
	@SuppressWarnings("unchecked")
	@Override
	public void populateReviewStatusList( final RequestWorkflow workflow, final Request request, ReviewStatusListView result ) {
	
		if( result != null ) {
		
			Set<Review> reviewSet = request.getReviews(workflow);	//get the reviews for this workflow
			if(reviewSet != null) {
	
				List<ReviewStatusView> intermedReviewList = new ArrayList<ReviewStatusView>();
				
				for(Review review : reviewSet) {
					ReviewStatusView reviewStatusView = ReviewStatusViewBuilder.populateReviewStatus(workflow, request, review);
					intermedReviewList.add( reviewStatusView );
				}
				
				//sort the list of intermediate reviews (Privacy, Security, ORD, others) 
				Collections.sort( intermedReviewList );
				result.getReviews().addAll( intermedReviewList );	//add the list of intermediate reviews to the overall list
			}//end if -- reviewSet

			result.setDisplayTotals(false);	//by default, don't display the totals (only one review group for the Independent Workflow)

		}//end if
	}
	
	
	/** Handle an event on the current workflow item.
	 * 
	 */
	@Override
	protected void transition(final RequestWorkflow workflow, final Review review, final Request request, final int operation, final String userLoginId) throws WorkflowException {
		
		int workflowState = -1;
		
		if( workflow != null ) {
			workflowState = workflow.getWorkflowState();	//get the state for this specific workflow (rather than for the request as a whole)
		} else {
			workflowState = request.getWorkflowState();		//top-level workflow (currently not used here)
		}//end else


		switch( workflowState ) {	
		
			case INDEPENDENT_REVIEW_STATE:
				stateIndependentReview(workflow, review, request, operation, userLoginId);
				break;
				
			case FINAL_STATE:
				return;
				
			default:
				throw new WorkflowException("wfIndependent: Illegal workflow state " + workflowState + " encountered in request " + request.getTrackingNumber());
		}//end switch
	}

	
	/** Initialize workflow processing for the current workflow item.
	 * 
	 */
	@Override
	public void initialize(final RequestWorkflow workflow, final Request request, final String userLoginId) throws WorkflowException {
		log.debug("WfIndependent initialize " + request.getTrackingNumber());
			
		setState(workflow, null, request, INDEPENDENT_REVIEW_STATE, userLoginId);
	}
	
	

	/** Set the state of the current workflow item.
	 * 	Transitions to the specified workflow state, with the WF_OPERATION_ENTRY operation.
	 * 		
	 * @param request
	 * @param newState
	 * @param userLoginId
	 * @throws WorkflowException
	 */
	@Override
	protected void setState(final RequestWorkflow workflow, final Review review, final Request request, final int newState, final String userLoginId) throws WorkflowException {
		
		if( workflow != null ) {
			workflow.setWorkflowState(newState);	//set the workflow state for this specific workflow (NOT just for the request as a whole)
		} else {
			request.setWorkflowState(newState);		//set the top-level workflow state
		}

		//move directly to the specified state
		transition(workflow, review, request, WF_OPERATION_ENTRY, userLoginId);
	}



	/** Independent Review state handler
	 * 
	 * 		If this review hasn't yet been done, allow it to be approved / denied
	 *			Compare to the overall mask (at the request) -> list of reviews to be done
	 *		Update the mask for this review once it has been done
	 * 
	 * @param review -- review that is currently being processed
	 * @param request
	 * @param operation
	 * @param userLoginId
	 * @throws WorkflowException
	 */
	protected synchronized void stateIndependentReview(final RequestWorkflow workflow, final Review review, final Request request, final int operation, final String userLoginId) throws WorkflowException {
		log.debug("WfIndependent stateIndependentReview operation = " + operation + " request = " + request.getTrackingNumber());

		Review reviewForGroup = null;
		
		try {
			EventType.initialize();	//this may be extra, but initialize just in case
			Group.initialize();


			if( workflow == null ) {	//verify that we have a non-NULL workflow object.  Otherwise, nothing to do.
				throw new WorkflowException("Error: workflow cannot be null when processing an Independent Review. Request: " + request.getTrackingNumber());
			}

			
			if( review == null ) {	//did NOT come from a review group (requestor event)
				//log.debug("stateIndependentReview, review is null (requestor event)");

				if (operation == Workflow.WF_OPERATION_ENTRY) {
					//log.debug("stateIndependentReview entry");

					return;  // stay in this state
				}//end WF_OPERATION_ENTRY

				else if (operation == Workflow.WF_OPERATION_SUBMIT) {	//submitted request (either a change request or an initial submission)
					//log.debug("request submitted (possibly with changes)!");

					//
					//create a task and event for this workflow's review group (only a single review for the Independent Workflow)
					if( workflow != null ) {
						
						for( Review rev : workflow.getReviews() ) {
							if( rev != null ) {
								
								Group reviewGroup = rev.getReviewer();
								if( reviewGroup !=  null ) {

									String reviewGroupShortName = reviewGroup.getShortName();


									//track this event (sent the review to this group)
									Event.create("Request Sent for " + reviewGroupShortName + " Review", "Request Sent for " + reviewGroupShortName + " Review", EventType.SENT_FOR_REVIEW, reviewGroup, false, request, review, userLoginId);

									//create a review task for this group
									createGroupReviewTask(workflow, reviewGroup, rev, userLoginId);
									
								}


								if( rev.isChangeRequested() ) {
								    EmailUtils.createAndSendChangeSubmittedEmails(request, reviewGroup, new Date());
									//request re-submitted, so update the review status (should go back to waiting for review)
									rev.clearStatus( userLoginId );

								}else{
								    createAndSendReadyForGroupReviewEmails(workflow, request, reviewGroup);
								}
							}
						}
					}
					

					// close tasks relative to this request (if this is a change request, close the requestor tasks)
					TaskUtils.closeUserTasksForChangeRequest( workflow, request, userLoginId );


					workflow.submit(userLoginId);	//update the status for this workflow only (top-level request is updated elsewhere)
					
					
					return;  // stay in this state -> ready for independent review
				}//end WF_OPERATION_SUBMIT

				else if (operation == Workflow.WF_OPERATION_CLOSE) {
								
					setState(workflow, review, request, FINAL_STATE, userLoginId);  // go to final state		
				}//end WF_OPERATION_CLOSE				
				
			} else {	//have the review to work with (review event)
								
				Group group = review.getReviewer();
				final String groupShortName = group.getShortName();				
				log.debug("Independent Review, group = " + group.getName() + ", shortName = " + groupShortName);


				//verify that there is a review for this current group . . . 			
				reviewForGroup = request.findReviewAssignedToGroup( workflow, group );
				if (reviewForGroup == null) {
					throw new WorkflowException("Error: could not find review assigned to " + groupShortName + " group for request " + request.getTrackingNumber());
				}
				
				
				// current reviewer group approves
				if (operation == Workflow.WF_OPERATION_APPROVE) {	//independent review approved
		
					//verify that this review has not yet been approved
					if( request.isReviewOpen( workflow, group ) ) {

						//approve this review (now that we have verified that this review is open)
						review.approve(userLoginId);	//update the review status
						workflow.approve(userLoginId);	//update the workflow status

						
//TODO: there shouldn't be any other tasks for this workflow at this point, but we might want to just close all tasks for this workflow, because we're moving to a final state
						// close Group Tasks relative to this request (for this review group and this workflow)
						TaskUtils.closeTasksForWorkflowAndGroup( workflow, group, request, userLoginId );


						//save a communication for this workflow
						Comment.create((groupShortName + " Review Completed"), request, userLoginId, "VINCI Dart request " + request.getTrackingNumber() + " approved by " + groupShortName + "!");

						// add an event
						EventType.initialize();
						Event.create((groupShortName + " Request Approval"), (groupShortName + " Request Approval"), EventType.APPROVE_WORKFLOW, group, false, request, review, userLoginId);
						
						
						//
						//after completing this review, move to the final state
						setState(workflow, review, request, FINAL_STATE, userLoginId);  // go to final state
					}
					else {
						log.error("Error: request " + request.getTrackingNumber() + " is not in the correct state for a review by " + groupShortName);
					}
				}
				else if (operation == Workflow.WF_OPERATION_CHANGE_REQUEST) {	//change requested by independent review group

					workflow.requestChange(userLoginId);	//update the workflow status (this is technically extra, because the workflow gets updated in the Controller)


					// create an event
					Event.create("Change Requested by " + groupShortName, "Change Requested by " + groupShortName, EventType.CHANGE_REQUEST, group, false, request, review, userLoginId);
					
					
					//email the requestor and participants that a change has been requested
					Set<Review> reviewSet = request.getReviews(workflow);	//get the reviews for this workflow

					//TODO: might want to just close all tasks:  TaskUtils.closeAllTasksForWorkflowAndRequest( workflow, request, userLoginId );	//close all tasks associated with this workflow (workflow will be in a final state))
					// close Group Tasks relative to this request (for this review group and workflow)
					TaskUtils.closeTasksForWorkflowAndGroup( workflow, group, request, userLoginId );


					return;	//stay in this state (independent review) -> send the change request to the requestor
				}
				else if (operation == Workflow.WF_OPERATION_DENY) {	//independent review denied

					if( request.isReviewOpen( workflow, group ) ) {	//verify that this review hasn't already been submitted

						//set the review to denied
						review.reject(userLoginId);		//update the review status
						workflow.reject(userLoginId);	//update the workflow status
						
						
						// create an event
						Event.create(groupShortName + " Review Denied", groupShortName + " Review Denied", EventType.DENY_REVIEW, group, false, request, review, userLoginId);


						// close tasks relative to this request for this Independent Review group (denying the independent review group will NOT affect any other workflow)
						TaskUtils.closeAllTasksForWorkflowAndRequest( workflow, request, userLoginId );	//close all tasks associated with this workflow (workflow will be in a final state)


						//
						//after completing this review, move to the final state
						setState(workflow, review, request, FINAL_STATE, userLoginId);  // go to final state
					}	
					else {
						log.error("Error: request " + request.getTrackingNumber() + " is not in the correct state for a review by " + groupShortName);
					}
				}
				else if (operation == Workflow.WF_OPERATION_CLOSE) {
					

				}
				else {
					log.error("Error: request " + request.getTrackingNumber() + " unexpected workflow operation " + operation + " for state Independent Review.");
				}
			}//end else
		}
		catch (Exception e) {
			throw new WorkflowException(e);
		}
	}

    private void createAndSendReadyForGroupReviewEmails(final RequestWorkflow workflow, final Request request,
            Group reviewGroup) {
        //send out an email to the Independent review group that the request is ready to be reviewed
        StringBuffer subject = EmailUtils.createGroupMemberEmailSubject(workflow, request,  "", Workflow.WF_OPERATION_SUBMIT);

        // generate the email body
        StringBuffer emailBody = EmailUtils.createInitialReviewEmailBody( request );

        //
        //send the email to the entire review group
        EmailUtils.sendEmailToGroupAndGroupMailBox(reviewGroup, subject.toString(), emailBody.toString());
    }

}
