package org.thoughtcrime.securesms;

import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.text.util.Linkify;
import android.util.Log;
import android.view.MenuItem;
import android.widget.TextView;
import androidx.activity.OnBackPressedCallback;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import com.b44t.messenger.DcContext;
import com.b44t.messenger.DcEvent;
import com.google.zxing.integration.android.IntentIntegrator;
import com.google.zxing.integration.android.IntentResult;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import org.thoughtcrime.securesms.connect.AccountManager;
import org.thoughtcrime.securesms.connect.DcEventCenter;
import org.thoughtcrime.securesms.connect.DcHelper;
import org.thoughtcrime.securesms.mms.AttachmentManager;
import org.thoughtcrime.securesms.mms.PartAuthority;
import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.qr.BackupTransferActivity;
import org.thoughtcrime.securesms.qr.QrCodeHandler;
import org.thoughtcrime.securesms.qr.RegistrationQrActivity;
import org.thoughtcrime.securesms.service.GenericForegroundService;
import org.thoughtcrime.securesms.service.NotificationController;
import org.thoughtcrime.securesms.util.Prefs;
import org.thoughtcrime.securesms.util.StorageUtil;
import org.thoughtcrime.securesms.util.StreamUtil;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.ViewUtil;
import org.thoughtcrime.securesms.util.views.ProgressDialog;

public class WelcomeActivity extends BaseActionBarActivity
    implements DcEventCenter.DcEventDelegate {
  public static final String BACKUP_QR_EXTRA = "backup_qr_extra";
  public static final int PICK_BACKUP = 20574;
  private static final String TAG = "WelcomeActivity";
  public static final String TMP_BACKUP_FILE = "tmp-backup-file";

  private ProgressDialog progressDialog = null;
  private boolean imexUserAborted;
  DcContext dcContext;
  private NotificationController notificationController;

  @Override
  public void onCreate(Bundle bundle) {
    super.onCreate(bundle);
    setContentView(R.layout.welcome_activity);

    // add padding to avoid content hidden behind system bars
    ViewUtil.applyWindowInsets(findViewById(R.id.content_container));

    findViewById(R.id.signup_button)
        .setOnClickListener(
            (v) -> startActivity(new Intent(this, InstantOnboardingActivity.class)));
    findViewById(R.id.add_as_second_device_button)
        .setOnClickListener((v) -> showSignInDialogWithPermission());
    findViewById(R.id.backup_button).setOnClickListener((v) -> startImportBackup());

    registerForEvents();
    initializeActionBar();

    getOnBackPressedDispatcher()
        .addCallback(
            this,
            new OnBackPressedCallback(true) {
              @Override
              public void handleOnBackPressed() {
                AccountManager accountManager = AccountManager.getInstance();
                if (accountManager.canRollbackAccountCreation(WelcomeActivity.this)) {
                  accountManager.rollbackAccountCreation(WelcomeActivity.this);
                } else {
                  setEnabled(false);
                  getOnBackPressedDispatcher().onBackPressed();
                }
              }
            });

    DcHelper.maybeShowMigrationError(this);
  }

  private void showSignInDialogWithPermission() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
        && !Prefs.getBooleanPreference(this, Prefs.ASKED_FOR_NOTIFICATION_PERMISSION, false)) {
      Prefs.setBooleanPreference(this, Prefs.ASKED_FOR_NOTIFICATION_PERMISSION, true);
      Permissions.with(this)
          .request(Manifest.permission.POST_NOTIFICATIONS)
          .ifNecessary()
          .onAllGranted(
              () -> {
                startAddAsSecondDeviceActivity();
              })
          .onAnyDenied(
              () -> {
                startAddAsSecondDeviceActivity();
              })
          .execute();
    } else {
      startAddAsSecondDeviceActivity();
    }
  }

  protected void initializeActionBar() {
    ActionBar supportActionBar = getSupportActionBar();
    if (supportActionBar == null) throw new AssertionError();

    boolean canGoBack = AccountManager.getInstance().canRollbackAccountCreation(this);
    supportActionBar.setDisplayHomeAsUpEnabled(canGoBack);
    getSupportActionBar().setTitle(canGoBack ? R.string.add_account : R.string.app_name);
  }

  private void registerForEvents() {
    dcContext = DcHelper.getContext(this);
    DcHelper.getEventCenter(this).addObserver(DcContext.DC_EVENT_IMEX_PROGRESS, this);
  }

  @Override
  public boolean onOptionsItemSelected(MenuItem item) {
    super.onOptionsItemSelected(item);

    switch (item.getItemId()) {
      case android.R.id.home:
        getOnBackPressedDispatcher().onBackPressed();
        return true;
    }

    return false;
  }

  @Override
  protected void onNewIntent(Intent intent) {
    super.onNewIntent(intent);
    setIntent(intent);
  }

  @Override
  public void onStart() {
    super.onStart();
    String backupQr = getIntent().getStringExtra(BACKUP_QR_EXTRA);
    if (backupQr != null) {
      getIntent().removeExtra(BACKUP_QR_EXTRA);
      startBackupTransfer(backupQr);
    }
  }

  @Override
  public void onDestroy() {
    super.onDestroy();
    DcHelper.getEventCenter(this).removeObservers(this);
  }

  @Override
  public void onRequestPermissionsResult(
      int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) {
    Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults);
  }

  private void startAddAsSecondDeviceActivity() {
    new IntentIntegrator(this)
        .setCaptureActivity(RegistrationQrActivity.class)
        .addExtra(RegistrationQrActivity.ADD_AS_SECOND_DEVICE_EXTRA, true)
        .initiateScan();
  }

  @SuppressLint("InlinedApi")
  private void startImportBackup() {
    Permissions.with(this)
        .request(
            Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE)
        .alwaysGrantOnSdk30()
        .ifNecessary()
        .withPermanentDenialDialog(getString(R.string.perm_explain_access_to_storage_denied))
        .onAllGranted(
            () -> {
              File imexDir = DcHelper.getImexDir();
              if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
                AttachmentManager.selectMediaType(
                    this, "application/x-tar", null, PICK_BACKUP, StorageUtil.getDownloadUri());
              } else {
                final String backupFile = dcContext.imexHasBackup(imexDir.getAbsolutePath());
                if (backupFile != null) {
                  new AlertDialog.Builder(this)
                      .setTitle(R.string.import_backup_title)
                      .setMessage(
                          String.format(
                              getResources().getString(R.string.import_backup_ask), backupFile))
                      .setNegativeButton(android.R.string.cancel, null)
                      .setPositiveButton(
                          android.R.string.ok, (dialog, which) -> startImport(backupFile, null))
                      .show();
                } else {
                  new AlertDialog.Builder(this)
                      .setTitle(R.string.import_backup_title)
                      .setMessage(
                          String.format(
                              getResources().getString(R.string.import_backup_no_backup_found),
                              imexDir.getAbsolutePath()))
                      .setPositiveButton(android.R.string.ok, null)
                      .show();
                }
              }
            })
        .execute();
  }

  private void startImport(@Nullable final String backupFile, final @Nullable Uri backupFileUri) {
    notificationController =
        GenericForegroundService.startForegroundTask(this, getString(R.string.import_backup_title));

    if (progressDialog != null) {
      progressDialog.dismiss();
      progressDialog = null;
    }

    imexUserAborted = false;
    progressDialog = new ProgressDialog(this);
    progressDialog.setMessage(getResources().getString(R.string.one_moment));
    progressDialog.setCanceledOnTouchOutside(false);
    progressDialog.setCancelable(false);
    progressDialog.setButton(
        DialogInterface.BUTTON_NEGATIVE,
        getResources().getString(android.R.string.cancel),
        (dialog, which) -> {
          imexUserAborted = true;
          dcContext.stopOngoingProcess();
          notificationController.close();
          cleanupTempBackupFile();
        });
    progressDialog.show();

    Util.runOnBackground(
        () -> {
          String file = backupFile;
          if (backupFile == null) {
            try {
              file = copyToCacheDir(backupFileUri).getAbsolutePath();
            } catch (IOException e) {
              e.printStackTrace();
              notificationController.close();
              cleanupTempBackupFile();
              return;
            }
          }

          DcHelper.getEventCenter(this).captureNextError();
          dcContext.imex(DcContext.DC_IMEX_IMPORT_BACKUP, file);
        });
  }

  private File copyToCacheDir(Uri uri) throws IOException {
    try (InputStream inputStream = PartAuthority.getAttachmentStream(this, uri)) {
      File file = File.createTempFile(TMP_BACKUP_FILE, ".tmp", getCacheDir());
      try (OutputStream outputStream = new FileOutputStream(file)) {
        StreamUtil.copy(inputStream, outputStream);
      }
      return file;
    }
  }

  private void startBackupTransfer(String qrCode) {
    if (progressDialog != null) {
      progressDialog.dismiss();
      progressDialog = null;
    }

    Intent intent = new Intent(this, BackupTransferActivity.class);
    intent.putExtra(
        BackupTransferActivity.TRANSFER_MODE,
        BackupTransferActivity.TransferMode.RECEIVER_SCAN_QR.getInt());
    intent.putExtra(BackupTransferActivity.QR_CODE, qrCode);
    startActivity(intent);
  }

  private void progressError(String data2) {
    progressDialog.dismiss();
    maybeShowConfigurationError(this, data2);
  }

  private void progressUpdate(int progress) {
    int percent = progress / 10;
    progressDialog.setMessage(
        getResources().getString(R.string.one_moment) + String.format(" %d%%", percent));
  }

  private void progressSuccess() {
    DcHelper.getEventCenter(this).endCaptureNextError();
    progressDialog.dismiss();
    Intent intent = new Intent(getApplicationContext(), ConversationListActivity.class);
    intent.putExtra(ConversationListActivity.FROM_WELCOME, true);
    startActivity(intent);
    finish();
  }

  public static void maybeShowConfigurationError(Activity activity, String data2) {
    if (activity.isFinishing()) return; // avoid android.view.WindowManager$BadTokenException

    if (data2 != null && !data2.isEmpty()) {
      AlertDialog d =
          new AlertDialog.Builder(activity)
              .setMessage(data2)
              .setPositiveButton(android.R.string.ok, null)
              .create();
      d.show();
      try {
        //noinspection ConstantConditions
        Linkify.addLinks(
            (TextView) d.findViewById(android.R.id.message),
            Linkify.WEB_URLS | Linkify.EMAIL_ADDRESSES);
      } catch (NullPointerException e) {
        e.printStackTrace();
      }
    }
  }

  @Override
  public void handleEvent(@NonNull DcEvent event) {
    int eventId = event.getId();

    if (eventId == DcContext.DC_EVENT_IMEX_PROGRESS) {
      long progress = event.getData1Int();
      if (progressDialog == null || notificationController == null) {
        // IMEX runs in BackupTransferActivity
        if (progress == 1000) {
          finish(); // transfer done - remove ourself from the activity stack (finishAffinity is
          // available in API 16, we're targeting API 14)
        }
        return;
      }
      if (progress == 0 /*error/aborted*/) {
        if (!imexUserAborted) {
          progressError(dcContext.getLastError());
        }
        notificationController.close();
        cleanupTempBackupFile();
      } else if (progress < 1000 /*progress in permille*/) {
        progressUpdate((int) progress);
        notificationController.setProgress(
            1000, progress, String.format(" %d%%", (int) progress / 10));
      } else if (progress == 1000 /*done*/) {
        DcHelper.getAccounts(this).startIo();
        progressSuccess();
        notificationController.close();
        cleanupTempBackupFile();
      }
    }
  }

  @Override
  protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);

    if (resultCode != RESULT_OK) {
      return;
    }

    if (requestCode == IntentIntegrator.REQUEST_CODE) {
      String qrRaw = data.getStringExtra(RegistrationQrActivity.QRDATA_EXTRA);
      if (qrRaw == null) {
        IntentResult scanResult = IntentIntegrator.parseActivityResult(resultCode, data);
        qrRaw = scanResult.getContents();
      }
      if (!new QrCodeHandler(this).handleBackupQr(qrRaw)) {
        new AlertDialog.Builder(this)
            .setMessage(R.string.qraccount_qr_code_cannot_be_used)
            .setPositiveButton(R.string.ok, null)
            .show();
      }
    } else if (requestCode == PICK_BACKUP) {
      Uri uri = (data != null ? data.getData() : null);
      if (uri == null) {
        Log.e(TAG, " Can't import null URI");
        return;
      }
      startImport(null, uri);
    }
  }

  private void cleanupTempBackupFile() {
    try {
      File[] files = getCacheDir().listFiles((dir, name) -> name.startsWith(TMP_BACKUP_FILE));
      for (File file : files) {
        if (file.getName().endsWith("tmp")) {
          Log.i(TAG, "Deleting temp backup file " + file);
          file.delete();
        }
      }
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}
