package uprrp.tania.fragments;

import android.app.Activity;
import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.Fragment;

import org.researchstack.backbone.StorageAccess;
import org.researchstack.backbone.answerformat.AnswerFormat;
import org.researchstack.backbone.answerformat.ChoiceAnswerFormat;
import org.researchstack.backbone.answerformat.TextAnswerFormat;
import org.researchstack.backbone.model.Choice;
import org.researchstack.backbone.result.StepResult;
import org.researchstack.backbone.result.TaskResult;
import org.researchstack.backbone.step.InstructionStep;
import org.researchstack.backbone.step.QuestionStep;
import org.researchstack.backbone.step.Step;
import org.researchstack.backbone.task.OrderedTask;
import org.researchstack.backbone.ui.ViewTaskActivity;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Observable;
import java.util.Observer;

import uprrp.tania.GlobalValues;
import uprrp.tania.R;
import uprrp.tania.utils.URLEventListener;
import uprrp.tania.models.AnsweredAssessmentModel;
import uprrp.tania.models.AnsweredQuestionModel;
import uprrp.tania.models.AssessmentModel;
import uprrp.tania.models.QuestionModel;
import uprrp.tania.models.UserStatusModel;
import uprrp.tania.networking.FetchAssessment;
import uprrp.tania.networking.FetchUserStatus;
import uprrp.tania.networking.SendAnswers;

public class AssessmentsFragment extends Fragment implements Observer {

    // These variables came from the sample app provided to explain the use of Research Stack
    private static final int REQUEST_SURVEY = 1;
    private static final String INSTRUCTION = "identifier";
    private static final String SAMPLE_SURVEY = "sample_assessment";

    // Our variables
    private static final String TAG = "AssessmentsFragment";
    private String ASSESSMENT_ID;
    private View thisFragment;

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {

        // Constructor
        thisFragment = inflater.inflate(R.layout.fragment_assessments, container, false);

        // Listen for device token changes
        this.startObservingGlobals();

        // Disable interaction initially while token is being fetched
        if(GlobalValues.getInstance().getDeviceToken() == null) {
            disableInteractionTemporarily();
        } else {
            enableInteraction();
        }

        // Display user status if available
        UserStatusModel userStatus = GlobalValues.getInstance().getUserStatus();
        if(userStatus != null) {
            positiveUserStatus(userStatus);
        }

        // Change all caps text to normal capitalization and add onClick listener
        final Button surveyButton = thisFragment.findViewById(R.id.surveyButton);
        final Button refreshButton = thisFragment.findViewById(R.id.refreshButton);
        surveyButton.setTransformationMethod(null); // TODO: this is a workaround I found, any other acceptable solution is welcome
        refreshButton.setTransformationMethod(null); // TODO: this is a workaround I found, any other acceptable solution is welcome
        surveyButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                fetchAssessment();
            }
        });
        refreshButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                fetchUserStatus();
            }
        });

        return thisFragment;

    }

    private void disableInteractionTemporarily() {
        final Button surveyButton = thisFragment.findViewById(R.id.surveyButton);
        final Button refreshButton = thisFragment.findViewById(R.id.refreshButton);
        final TextView statusTextView = thisFragment.findViewById(R.id.statusTextView);
        final TextView nextSurveyTextView = thisFragment.findViewById(R.id.nextSurveyTextView);

        surveyButton.setEnabled(false);
        refreshButton.setEnabled(false);
        statusTextView.setText(R.string.loadingText);
        nextSurveyTextView.setVisibility(View.INVISIBLE);
    }

    private void disableInteraction() {
        final Button surveyButton = thisFragment.findViewById(R.id.surveyButton);
        final Button refreshButton = thisFragment.findViewById(R.id.refreshButton);
        final TextView statusTextView = thisFragment.findViewById(R.id.statusTextView);
        final TextView nextSurveyTextView = thisFragment.findViewById(R.id.nextSurveyTextView);

        surveyButton.setEnabled(false);
        refreshButton.setEnabled(false);
        statusTextView.setText(R.string.tokenErrorText);
        nextSurveyTextView.setVisibility(View.INVISIBLE);
    }

    private void enableInteraction() {
        final Button surveyButton = thisFragment.findViewById(R.id.surveyButton);
        final Button refreshButton = thisFragment.findViewById(R.id.refreshButton);

        surveyButton.setEnabled(true);
        refreshButton.setEnabled(true);
        if(GlobalValues.getInstance().getUserStatus() == null) {
            fetchUserStatus(); // refresh user status
        }
    }

    // TAKEN FROM https://stackoverflow.com/questions/21886768/is-there-any-method-i-can-listen-for-changes-to-global-variable-to-trigger-an-ev
    private void startObservingGlobals() {
        GlobalValues.getInstance().addObserver(this);
    }

    // TAKEN FROM https://stackoverflow.com/questions/21886768/is-there-any-method-i-can-listen-for-changes-to-global-variable-to-trigger-an-ev
    @Override
    public void update(Observable observable, Object o) {
        if(observable instanceof GlobalValues) {
            if(o instanceof GlobalValues.ValueKey) {
                switch(((GlobalValues.ValueKey) o).getKey()) {
                    case DEVICE_TOKEN:
                        if(GlobalValues.getInstance().getDeviceToken() == null) {
                            disableInteraction();
                        } else {
                            enableInteraction();
                        }
                        break;
                    case USER_STATUS:
                        UserStatusModel userStatus = GlobalValues.getInstance().getUserStatus();
                        if(userStatus == null) {
                            negativeUserStatus();
                        } else {
                            positiveUserStatus(userStatus);
                        }
                        break;
                }
            }
        }
    }

    private void negativeUserStatus() {
        Toast.makeText(getContext(), "Can't verify identity!", Toast.LENGTH_LONG).show();

        final TextView statusTextView = thisFragment.findViewById(R.id.statusTextView);
        final TextView nextSurveyTextView = thisFragment.findViewById(R.id.nextSurveyTextView);
        final Button surveyButton = thisFragment.findViewById(R.id.surveyButton);
        final Button refreshButton = thisFragment.findViewById(R.id.refreshButton);

        statusTextView.setText(R.string.userStatusErrorText);
        nextSurveyTextView.setVisibility(View.INVISIBLE);
        surveyButton.setVisibility(View.INVISIBLE);
        surveyButton.setEnabled(false);
        refreshButton.setVisibility(View.VISIBLE);
    }

    private void positiveUserStatus(UserStatusModel userStatus) {

        final TextView statusTextView = thisFragment.findViewById(R.id.statusTextView);
        final TextView nextSurveyTextView = thisFragment.findViewById(R.id.nextSurveyTextView);
        final Button surveyButton = thisFragment.findViewById(R.id.surveyButton);
        final Button refreshButton = thisFragment.findViewById(R.id.refreshButton);

        statusTextView.setText(userStatus.toString());

        if(userStatus.isEnrolled()) {

            nextSurveyTextView.setText(userStatus.getTimeToNext());
            nextSurveyTextView.setVisibility(View.VISIBLE);

            if(userStatus.surveyAvailable()) {
                refreshButton.setVisibility(View.INVISIBLE);
                refreshButton.setEnabled(false);
                surveyButton.setVisibility(View.VISIBLE);
                surveyButton.setEnabled(true);
            } else {
                surveyButton.setVisibility(View.INVISIBLE);
                surveyButton.setEnabled(false);
                refreshButton.setVisibility(View.VISIBLE);
                refreshButton.setEnabled(true);
            }

        } else {

            nextSurveyTextView.setText("");
            nextSurveyTextView.setVisibility(View.INVISIBLE);

            surveyButton.setVisibility(View.INVISIBLE);
            surveyButton.setEnabled(false);
            refreshButton.setVisibility(View.VISIBLE);
            refreshButton.setEnabled(true);

        }

    }

    private void fetchUserStatus() {

        disableInteractionTemporarily();

        // Define task
        FetchUserStatus userStatusTask = new FetchUserStatus(new URLEventListener() {
            @Override
            public void onSuccess(UserStatusModel userStatus) {
                GlobalValues.getInstance().setUserStatus(userStatus);
                enableInteraction();
            }
            @Override
            public void onFailure(Exception e) {
                GlobalValues.getInstance().setUserStatus(null);
                Log.e(TAG, "Error while fetching user status: " + e.toString());
            }
        });

        // Start task
        userStatusTask.execute(GlobalValues.getInstance().getDeviceToken());

    }

    private void fetchAssessment() {

        // Initiate progress dialog
        // TODO: find substitute for this deprecated dialog box
        final ProgressDialog progressDialog = ProgressDialog.show(getContext(),
                "Looking for Survey",
                getString(R.string.progressDialogDescriptionText));

        // Fetch the assessment to be taken (if there is at least one available),
        // and launch the assessment (if the button is pressed)
        FetchAssessment assessmentTask = new FetchAssessment(new URLEventListener() {

            @Override
            public void onSuccess(AssessmentModel assessment) {
                progressDialog.dismiss();
                if(assessment.isEmpty()) {
                    fetchUserStatus(); // update status to let user know if it already had expired by the time he clicked
                    Toast.makeText(getContext(), "There are no surveys at the moment!", Toast.LENGTH_LONG).show();
                } else {
                    launchSurvey(assessment);
                }
            }

            @Override
            public void onFailure(Exception e) {
                progressDialog.dismiss();
                Toast.makeText(getContext(), "Can't reach surveys at the moment!", Toast.LENGTH_LONG).show();
                Log.e("ERROR WHILE FETCHING ASSESSMENT", e.toString());
            }

        });

        // Start task
        assessmentTask.execute(GlobalValues.getInstance().getDeviceToken());

    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if(requestCode == REQUEST_SURVEY && resultCode == Activity.RESULT_OK) {
            this.processSurveyResult((TaskResult) data.getSerializableExtra(ViewTaskActivity.EXTRA_TASK_RESULT));
        }
    }

    private void launchSurvey(AssessmentModel assessment) {
        this.ASSESSMENT_ID = assessment.getID();
        List<Step> steps = this.prepareSurveySteps(assessment);
        OrderedTask task = new OrderedTask(SAMPLE_SURVEY, steps);
        Intent intent = ViewTaskActivity.newIntent(getContext(), task);
        startActivityForResult(intent, REQUEST_SURVEY);
    }

    private void processSurveyResult(TaskResult result) {

        // Save results to local database
        StorageAccess.getInstance().getAppDatabase().saveTaskResult(result);

        // Load results
        TaskResult taskResult = StorageAccess.getInstance()
                .getAppDatabase()
                .loadLatestTaskResult(SAMPLE_SURVEY);

        // Prepare and send results
        AnsweredAssessmentModel answeredAssessment = this.prepareAnsweredAssessment(taskResult);
        this.sendSurveyInfo(answeredAssessment);

    }

    private void sendSurveyInfo(final AnsweredAssessmentModel answeredAssessment) {

        // Initiate progress dialog
        // TODO: find substitute for this deprecated dialog box
        final ProgressDialog progressDialog = ProgressDialog.show(getContext(),
                "Sending Answers",
                getString(R.string.progressDialogDescriptionText));

        // Send answers and prompt user if he wants to retry sending them if an error occurs
        SendAnswers sendAnswersTask = new SendAnswers(new URLEventListener() {

           @Override public void onSuccess() {
               progressDialog.dismiss();
               Toast.makeText(getContext(), "Answers sent!", Toast.LENGTH_LONG).show();
               fetchUserStatus();
           }

           @Override
           public void onFailure(Exception e) {
               Log.e(TAG, "Error whilte sending answers: " + e.toString());
               e.printStackTrace();
               progressDialog.dismiss();
               promptRetrySend(answeredAssessment);
           }

        });


        // Prepare results and start task
       sendAnswersTask.execute(answeredAssessment);

        // UNCOMMENT THIS FOR TESTING (REMEMBER TO COMMENT .execute() LINE)
//        Handler handler = new Handler();
//        handler.postDelayed(new Runnable() {
//            public void run() {
//                progressDialog.dismiss();
//                if(true) { // Imitate success
//                    Toast.makeText(getContext(), "Answers sent!", Toast.LENGTH_LONG).show();
//                    fetchUserStatus();
//                } else { // Imitate failure
//                    promptRetrySend2(answeredAssessment);
//                }
//            }
//        }, 5000);
        // UNCOMMENT THIS FOR TESTING (REMEMBER TO COMMENT .execute() LINE)

    }

    private AnsweredAssessmentModel prepareAnsweredAssessment(TaskResult taskResult) {

        // Gather answered questions
        ArrayList<AnsweredQuestionModel> answeredQuestions = new ArrayList<>();
        for (String questionID : taskResult.getResults().keySet()) {

            // Extract question info
            // TODO: check if instead of getting the taskResult start/end times,
            //  we should be getting the stepResult start/end times...
            //  IF NOT: we should think about restructuring the JSON
            //  because we're being highly redundant and inefficient
            StepResult stepResult = taskResult.getStepResult(questionID);
            String answer = stepResult.getResult().toString();
            String startDatetime = taskResult.getStartDate().toString();
            String endDatetime = taskResult.getEndDate().toString();

            // Append question
            AnsweredQuestionModel answeredQuestion = new AnsweredQuestionModel(questionID, answer, startDatetime, endDatetime);
            answeredQuestions.add(answeredQuestion);

        }

        // Return answered assessment
        return new AnsweredAssessmentModel(this.ASSESSMENT_ID, GlobalValues.getInstance().getDeviceToken(), answeredQuestions);

    }

    private List<Step> prepareSurveySteps(AssessmentModel assessment) {

        // Initialize survey steps
        List<Step> steps = new ArrayList<>();

        // Create instruction screen (add it to total steps)
        InstructionStep instructionStep = new InstructionStep(INSTRUCTION,
                getString(R.string.appName),
                assessment.getDescription());
        instructionStep.setStepTitle(R.string.surveyToolbarText);
        steps.add(0, instructionStep);

        // Prepare survey question by question
        List<QuestionModel> questions = assessment.getQuestions();
        Log.d(TAG, "AssessmentCount: " + assessment.getQuestionCount() + " QuestionCount: " + questions.size()); // just to be sure
        for(int i = 0; i < assessment.getQuestionCount(); i++) {

            QuestionModel question = questions.get(i);
            Log.d(TAG, "QuestionType:" + question.getType());

            if(question.getType().equals("SCALED")) {

                // Create question choices
                Choice[] choices = new Choice[question.getMaxValue()];
                for(int m = 0; m < question.getMaxValue(); m++) {
                    if (m == 0) {
                        choices[m] = new Choice<>((m + 1) + " " + question.getMinText(), m + 1);
                    } else if (m == question.getMaxValue() - 1) {
                        choices[m] = new Choice<>((m + 1) + " " + question.getMaxText(), m + 1);
                    } else {
                        choices[m] = new Choice<>(Integer.toString(m + 1), m + 1);
                    }
                }

                // Create question step along with its choices
                QuestionStep newQuestionStep = new QuestionStep(question.getID());
                AnswerFormat multiFormat = new ChoiceAnswerFormat(AnswerFormat.ChoiceAnswerStyle.SingleChoice, choices);

                // Set question step details
                newQuestionStep.setStepTitle(R.string.surveyToolbarText);
                newQuestionStep.setTitle(question.getPremise());
                newQuestionStep.setAnswerFormat(multiFormat);
                newQuestionStep.setOptional(false);

                // Add step to arrays
                steps.add(newQuestionStep);

            } else if(question.getType().equals("OPEN")) {

                // Create question step along with its choices
                QuestionStep newQuestionStep = new QuestionStep(question.getID());
                TextAnswerFormat format = new TextAnswerFormat(100);
                format.setIsMultipleLines(true);

                // Set question step details
                newQuestionStep.setStepTitle(R.string.surveyToolbarText);
                newQuestionStep.setTitle(question.getPremise());
                newQuestionStep.setAnswerFormat(format);
                newQuestionStep.setOptional(false);

                // Add step to arrays
                steps.add(newQuestionStep);

            } else {
                Log.wtf(TAG, "Neither OPEN nor SCALED if-statement has been reached");
            }

        }

        return steps;
    }

    private void promptRetrySend(final AnsweredAssessmentModel answeredAssessment) {
        AlertDialog.Builder builder = new AlertDialog.Builder(Objects.requireNonNull(getContext()));
        builder.setCancelable(true);
        builder.setTitle("ERROR");
        builder.setMessage("Couldn't send answers!");
        builder.setPositiveButton("Retry", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                Log.d(TAG, "Retrying answer send!");
                sendSurveyInfo(answeredAssessment);
            }
        });
        builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                Toast.makeText(getContext(), "Didn't send answers!", Toast.LENGTH_LONG).show();
                fetchUserStatus();
            }
        });

        AlertDialog dialog = builder.create();
        dialog.show();
    }

}
