/*
 * Copyright 2020 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package com.google.android.apps.exposurenotification.notify;

import static com.google.android.apps.exposurenotification.notify.ShareDiagnosisViewModel.EMPTY_DIAGNOSIS;

import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.CallSuper;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import androidx.lifecycle.ViewModelProvider;
import com.google.android.apps.exposurenotification.R;
import com.google.android.apps.exposurenotification.common.IntentUtil;
import com.google.android.apps.exposurenotification.common.PairLiveData;
import com.google.android.apps.exposurenotification.databinding.FragmentShareDiagnosisBinding;
import com.google.android.apps.exposurenotification.edgecases.VerificationFlowEdgeCaseFragment;
import com.google.android.apps.exposurenotification.home.BaseFragment;
import com.google.android.apps.exposurenotification.notify.ShareDiagnosisFlowHelper.ShareDiagnosisFlow;
import com.google.android.apps.exposurenotification.notify.ShareDiagnosisViewModel.Step;
import com.google.android.apps.exposurenotification.storage.DiagnosisEntity;
import com.google.android.apps.exposurenotification.storage.DiagnosisEntity.Shared;
import dagger.hilt.android.AndroidEntryPoint;
import java.util.Objects;

/**
 * Main fragment that handles the Share Diagnosis flow (and is a single entry point to the flow).
 *
 * <p>
 * <ul>
 *   <li>Flow for adding a new positive diagnosis starting with {@link ShareDiagnosisBeginFragment}
 *   <li>Flow for viewing a previously diagnosis starting with {@link ShareDiagnosisViewFragment}
 * </ul>
 * <p>
 */
@AndroidEntryPoint
public class ShareDiagnosisFragment extends BaseFragment {

  // Parameters that can be accepted as fragment arguments to land in the View flow.
  public static final String EXTRA_DIAGNOSIS_ID = "ShareDiagnosisFragment.EXTRA_DIAGNOSIS_ID";
  public static final String EXTRA_STEP = "ShareDiagnosisFragment.EXTRA_STEP";

  static final String NOTIFY_OTHERS_FRAGMENT_TAG =
      "ShareDiagnosisFragment.NOTIFY_OTHERS_FRAGMENT_TAG";

  private static final String SAVED_INSTANCE_STATE_FRAGMENT_KEY =
      "ShareDiagnosisFragment.SAVED_INSTANCE_STATE_FRAGMENT_KEY";

  private static final int VIEWFLIPPER_EN_ENABLED = 0;
  private static final int VIEWFLIPPER_EN_DISABLED = 1;

  private ShareDiagnosisViewModel shareDiagnosisViewModel;
  private FragmentShareDiagnosisBinding binding;
  private boolean restoredFromSavedInstanceState = false;
  private boolean showCloseWarningDialog = false;

  /**
   * Creates a {@link ShareDiagnosisFragment} instance.
   */
  public static ShareDiagnosisFragment newInstance() {
    return new ShareDiagnosisFragment();
  }

  /**
   * Creates a {@link ShareDiagnosisFragment} to open the Share Diagnosis flow for viewing
   * an already existing diagnosis.
   */
  public static ShareDiagnosisFragment newInstanceForDiagnosis(
      Context context, DiagnosisEntity entity) {
    Bundle fragmentArguments = new Bundle();
    fragmentArguments.putLong(EXTRA_DIAGNOSIS_ID, entity.getId());
    fragmentArguments.putString(EXTRA_STEP,
        ShareDiagnosisFlowHelper.getMaxStepForDiagnosisEntity(entity).name());

    ShareDiagnosisFragment shareDiagnosisFragment = ShareDiagnosisFragment.newInstance();
    shareDiagnosisFragment.setArguments(fragmentArguments);

    return shareDiagnosisFragment;
  }

  /**
   * Creates a {@link ShareDiagnosisFragment} instance for the SMS Deep Link flow.
   */
  public static ShareDiagnosisFragment newInstanceForCode(String codeFromDeepLink) {
    ShareDiagnosisFragment shareDiagnosisFragment = new ShareDiagnosisFragment();
    Bundle args = new Bundle();
    args.putString(EXTRA_CODE_FROM_DEEP_LINK, codeFromDeepLink);
    shareDiagnosisFragment.setArguments(args);
    return shareDiagnosisFragment;
  }

  /**
   * Creates a {@link ShareDiagnosisFragment} instance for the Self-report Deep Link flow.
   */
  public static ShareDiagnosisFragment newInstanceForSelfReport() {
    ShareDiagnosisFragment shareDiagnosisFragment = new ShareDiagnosisFragment();
    Bundle args = new Bundle();
    args.putBoolean(EXTRA_IS_SELF_REPORT_FLOW, true);
    shareDiagnosisFragment.setArguments(args);
    return shareDiagnosisFragment;
  }

  @Override
  public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup parent,
      @Nullable Bundle savedInstanceState) {
    binding = FragmentShareDiagnosisBinding.inflate(inflater, parent, false);
    return binding.getRoot();
  }

  @CallSuper
  @Override
  public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);

    shareDiagnosisViewModel = new ViewModelProvider(this).get(ShareDiagnosisViewModel.class);

    if (savedInstanceState != null) {
      // If this is a configuration change such as rotation, restore the fragment that was
      // previously saved in onSaveInstanceState.
      getChildFragmentManager()
          .beginTransaction()
          .replace(
              R.id.notify_others_fragment,
              Objects.requireNonNull(
                  getChildFragmentManager()
                      .getFragment(savedInstanceState, SAVED_INSTANCE_STATE_FRAGMENT_KEY)),
              NOTIFY_OTHERS_FRAGMENT_TAG)
          .commit();
      restoredFromSavedInstanceState = true;
    } else {
      // This fragment can be created (1) from the Sharing History screen, (2) from within the SMS
      // deep link flow, (3) from within the deep link flow for self-reporting, or (4) from the
      // "Share your test result" card. Check for the intent and fragment arguments to decide
      // between these options.
      Bundle fragmentArgs = getArguments();
      boolean isSmsDeepLinkFlow = getArguments() != null
          && getArguments().containsKey(EXTRA_CODE_FROM_DEEP_LINK);
      boolean isSelfReport = getArguments() != null
          && getArguments().getBoolean(EXTRA_IS_SELF_REPORT_FLOW, false);

      if (IntentUtil.isValidBundleToOpenShareDiagnosisFlow(fragmentArgs)) {
        // 1. Viewing an already existing flow after coming from the Sharing History screen.
        long diagnosisId = fragmentArgs.getLong(EXTRA_DIAGNOSIS_ID);
        String stepName = fragmentArgs.getString(EXTRA_STEP);
        shareDiagnosisViewModel.setCurrentDiagnosisId(diagnosisId);
        shareDiagnosisViewModel.nextStepIrreversible(Step.valueOf(stepName));
      } else if (isSmsDeepLinkFlow) {
        // 2. Sharing a new diagnosis from within the SMS deep link flow.
        shareDiagnosisViewModel.setShareDiagnosisFlow(ShareDiagnosisFlow.DEEP_LINK);
        shareDiagnosisViewModel.nextStepIrreversible(Step.BEGIN);
      } else if (isSelfReport) {
        // 3. Starting the flow from the Self-report deep link.
        if (ShareDiagnosisFlowHelper.isSelfReportEnabled(requireContext())) {
          // Self-report is enabled, so move straight to the "Get a verification code" screen.
          shareDiagnosisViewModel.setShareDiagnosisFlow(ShareDiagnosisFlow.SELF_REPORT);
          shareDiagnosisViewModel.nextStepIrreversible(Step.GET_CODE);
        } else {
          // Self-report is disabled, so go to the first step in the flow.
          shareDiagnosisViewModel.nextStepIrreversible(Step.BEGIN);
        }
      } else {
        // 4. Starting the flow from the "Share your test result" card.
        exposureNotificationViewModel.getLastNotSharedDiagnosisLiveEvent()
            .observe(this, diagnosis -> {
              if (diagnosis == null || EMPTY_DIAGNOSIS.equals(diagnosis)) {
                // New flow!
                shareDiagnosisViewModel
                    .setCurrentDiagnosisId(ShareDiagnosisViewModel.NO_EXISTING_ID);
                shareDiagnosisViewModel.nextStepIrreversible(Step.BEGIN);
              } else {
                // Resuming an already existing flow automatically.
                shareDiagnosisViewModel.setResumingAndNotConfirmed(true);
                shareDiagnosisViewModel.setCurrentDiagnosisId(diagnosis.getId());
                shareDiagnosisViewModel.setVerifiedCodeForCodeStep(diagnosis.getVerificationCode());
                shareDiagnosisViewModel.nextStepIrreversible(Step.CODE);
              }
            });
        exposureNotificationViewModel.getLastNotSharedDiagnosisIfAny();
      }
    }

    shareDiagnosisViewModel.getCurrentStepLiveData()
        .observe(getViewLifecycleOwner(), step -> {
          // If triggered on configuration changes (e.g. device rotations), do not attempt to
          // replace fragment as the desired fragment has been set already.
          if (!restoredFromSavedInstanceState) {
            getChildFragmentManager()
                .beginTransaction()
                .replace(
                    R.id.notify_others_fragment,
                    fragmentForStep(step),
                    NOTIFY_OTHERS_FRAGMENT_TAG)
                .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
                .commit();
          } else {
            restoredFromSavedInstanceState = false;
          }
        });

    /*
     * Make sure to prevent users from continuing at any step in the diagnosis sharing flow if the
     * EN is disabled as they won't be able to share their keys in that case. But never prevent
     * users from viewing diagnoses they've already shared.
     */
    PairLiveData<DiagnosisEntity, Boolean> displayEdgeCaseForDiagnosisLiveData = PairLiveData.of(
        shareDiagnosisViewModel.getCurrentDiagnosisLiveData(),
        exposureNotificationViewModel.getEnEnabledLiveData());
    displayEdgeCaseForDiagnosisLiveData
        .observe(this,
            (currentDiagnosis, isEnabled) -> {
              if (isEnabled || DiagnosisEntityHelper.hasBeenShared(currentDiagnosis)) {
                binding.enEnabledFlipper.setDisplayedChild(VIEWFLIPPER_EN_ENABLED);
              } else {
                binding.enEnabledFlipper.setDisplayedChild(VIEWFLIPPER_EN_DISABLED);
              }
              showCloseWarningDialog = DiagnosisEntityHelper.hasVerified(currentDiagnosis)
                  && Shared.NOT_ATTEMPTED.equals(currentDiagnosis.getSharedStatus());
            });

    /*
     * Attach the edge-case logic as a fragment
     */
    FragmentManager fragmentManager = getChildFragmentManager();
    if (fragmentManager.findFragmentById(R.id.edge_case_fragment) == null) {
      Fragment verificationEdgeCaseFragment = VerificationFlowEdgeCaseFragment
          .newInstance(/* handleApiErrorLiveEvents= */ true, /* handleResolutions= */ true);
      fragmentManager.beginTransaction()
          .replace(R.id.edge_case_fragment, verificationEdgeCaseFragment)
          .commit();
    }
  }

  @Override
  public boolean onBackPressed() {
    @Nullable ShareDiagnosisBaseFragment shareDiagnosisBaseFragment = (ShareDiagnosisBaseFragment)
        getChildFragmentManager().findFragmentById(R.id.notify_others_fragment);

    if (shareDiagnosisBaseFragment == null) {
      // We shouldn't get here. But if we somehow do, let the parent activity handle onBackPressed()
      return false;
    }

    if (binding.enEnabledFlipper.getDisplayedChild() == VIEWFLIPPER_EN_DISABLED) {
      // We hit an edge case, so no need to call the child fragment's onBackPressed(). Instead
      // immediately attempt to close the sharing flow.
      shareDiagnosisBaseFragment.maybeCloseShareDiagnosisFlow(showCloseWarningDialog);
      return true;
    }
    return shareDiagnosisBaseFragment.onBackPressed();
  }

  @Override
  public void onDestroyView() {
    super.onDestroyView();
    binding = null;
  }

  @Override
  public void onSaveInstanceState(@NonNull Bundle outState) {
    super.onSaveInstanceState(outState);
    getChildFragmentManager()
        .putFragment(
            outState,
            SAVED_INSTANCE_STATE_FRAGMENT_KEY,
            Objects.requireNonNull(
                getChildFragmentManager().findFragmentByTag(NOTIFY_OTHERS_FRAGMENT_TAG)));
  }

  /**
   * Checks whether to show the confirmation dialog when exiting the "Share diagnosis" flow.
   */
  public boolean isShowCloseWarningDialog() {
    return showCloseWarningDialog;
  }

  @NonNull
  private static ShareDiagnosisBaseFragment fragmentForStep(Step step) {
    switch (step) {
      case IS_CODE_NEEDED:
        return new ShareDiagnosisIsCodeNeededFragment();
      case GET_CODE:
        return new ShareDiagnosisGetCodeFragment();
      case CODE:
        return new ShareDiagnosisCodeFragment();
      case UPLOAD:
        return new ShareDiagnosisUploadFragment();
      case SHARED:
        return new ShareDiagnosisSharedFragment();
      case NOT_SHARED:
        return new ShareDiagnosisNotSharedFragment();
      case VIEW:
        return new ShareDiagnosisViewFragment();
      case ALREADY_REPORTED:
        return new ShareDiagnosisAlreadyReportedFragment();
      case VACCINATION:
        return new ShareDiagnosisVaccinationFragment();
      case PRE_AUTH:
        return new ShareDiagnosisPreAuthFragment();
      case BEGIN:
      default:
        // We "shouldn't" get here, but start at the beginning if we somehow do.
        return new ShareDiagnosisBeginFragment();
    }
  }
}
