package org.thoughtcrime.securesms.providers;

import android.content.ContentUris;
import android.content.Context;
import android.content.UriMatcher;
import android.net.Uri;
import android.webkit.MimeTypeMap;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.BuildConfig;
import org.thoughtcrime.securesms.crypto.AttachmentSecret;
import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider;
import org.thoughtcrime.securesms.crypto.ClassicDecryptingPartInputStream;
import org.thoughtcrime.securesms.crypto.ModernDecryptingPartInputStream;
import org.thoughtcrime.securesms.util.FileProviderUtil;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;

/**
 * @deprecated Use {@link BlobProvider} instead. Keeping in read-only mode due to the number of
 * legacy URIs it handles. Given that this was largely used for drafts, and that files were stored
 * in the cache directory, it's possible that we could remove this class after a reasonable amount
 * of time has passed.
 */
@Deprecated
public class DeprecatedPersistentBlobProvider {

  private static final String TAG = Log.tag(DeprecatedPersistentBlobProvider.class);

  public  static final String     AUTHORITY             = BuildConfig.APPLICATION_ID;
  private static final String     URI_STRING            = "content://" + AUTHORITY + "/capture-new";
  public  static final Uri        CONTENT_URI           = Uri.parse(URI_STRING);
  public  static final String     EXPECTED_PATH_OLD     = "capture/*/*/#";
  public  static final String     EXPECTED_PATH_NEW     = "capture-new/*/*/*/*/#";

  private static final int        MIMETYPE_PATH_SEGMENT = 1;
  private static final int        FILENAME_PATH_SEGMENT = 2;
  private static final int        FILESIZE_PATH_SEGMENT = 3;

  private static final String     BLOB_EXTENSION        = "blob";
  private static final int        MATCH_OLD             = 1;
  private static final int        MATCH_NEW             = 2;

  private static final UriMatcher MATCHER               = new UriMatcher(UriMatcher.NO_MATCH) {{
    addURI(AUTHORITY, EXPECTED_PATH_OLD, MATCH_OLD);
    addURI(AUTHORITY, EXPECTED_PATH_NEW, MATCH_NEW);
  }};

  private static volatile DeprecatedPersistentBlobProvider instance;

  /**
   * @deprecated Use {@link BlobProvider} instead.
   */
  @Deprecated
  public static DeprecatedPersistentBlobProvider getInstance(Context context) {
    if (instance == null) {
      synchronized (DeprecatedPersistentBlobProvider.class) {
        if (instance == null) {
          instance = new DeprecatedPersistentBlobProvider(context);
        }
      }
    }
    return instance;
  }

  private final AttachmentSecret  attachmentSecret;

  private DeprecatedPersistentBlobProvider(@NonNull Context context) {
    this.attachmentSecret = AttachmentSecretProvider.getInstance(context).getOrCreateAttachmentSecret();
  }

  public Uri createForExternal(@NonNull Context context, @NonNull String mimeType) throws IOException {
    File target = new File(getExternalDir(context), String.valueOf(System.currentTimeMillis()) + "." + getExtensionFromMimeType(mimeType));
    return FileProviderUtil.getUriFor(context, target);
  }

  public boolean delete(@NonNull Context context, @NonNull Uri uri) {
    switch (MATCHER.match(uri)) {
    case MATCH_OLD:
    case MATCH_NEW:
      long id = ContentUris.parseId(uri);
      return getFile(context, ContentUris.parseId(uri)).file.delete();
    }

    //noinspection SimplifiableIfStatement
    if (isExternalBlobUri(context, uri)) {
      return FileProviderUtil.delete(context, uri);
    }

    return false;
  }

  public @NonNull InputStream getStream(@NonNull Context context, long id) throws IOException {
    FileData fileData = getFile(context, id);

    if (fileData.modern) return ModernDecryptingPartInputStream.createFor(attachmentSecret, fileData.file, 0);
    else                 return ClassicDecryptingPartInputStream.createFor(attachmentSecret, fileData.file);
  }

  private FileData getFile(@NonNull Context context, long id) {
    File legacy      = getLegacyFile(context, id);
    File cache       = getCacheFile(context, id);
    File modernCache = getModernCacheFile(context, id);

    if      (legacy.exists()) return new FileData(legacy, false);
    else if (cache.exists())  return new FileData(cache, false);
    else                      return new FileData(modernCache, true);
  }

  private File getLegacyFile(@NonNull Context context, long id) {
    return new File(context.getDir("captures", Context.MODE_PRIVATE), id + "." + BLOB_EXTENSION);
  }

  private File getCacheFile(@NonNull Context context, long id) {
    return new File(context.getCacheDir(), "capture-" + id + "." + BLOB_EXTENSION);
  }

  private File getModernCacheFile(@NonNull Context context, long id) {
    return new File(context.getCacheDir(), "capture-m-" + id + "." + BLOB_EXTENSION);
  }

  public static @Nullable String getMimeType(@NonNull Context context, @NonNull Uri persistentBlobUri) {
    if (!isAuthority(context, persistentBlobUri)) return null;
    return isExternalBlobUri(context, persistentBlobUri)
        ? getMimeTypeFromExtension(persistentBlobUri)
        : persistentBlobUri.getPathSegments().get(MIMETYPE_PATH_SEGMENT);
  }

  public static @Nullable String getFileName(@NonNull Context context, @NonNull Uri persistentBlobUri) {
    if (!isAuthority(context, persistentBlobUri))      return null;
    if (isExternalBlobUri(context, persistentBlobUri)) return null;
    if (MATCHER.match(persistentBlobUri) == MATCH_OLD) return null;

    return persistentBlobUri.getPathSegments().get(FILENAME_PATH_SEGMENT);
  }

  public static @Nullable Long getFileSize(@NonNull Context context, Uri persistentBlobUri) {
    if (!isAuthority(context, persistentBlobUri))      return null;
    if (isExternalBlobUri(context, persistentBlobUri)) return null;
    if (MATCHER.match(persistentBlobUri) == MATCH_OLD) return null;

    try {
      return Long.valueOf(persistentBlobUri.getPathSegments().get(FILESIZE_PATH_SEGMENT));
    } catch (NumberFormatException e) {
      Log.w(TAG, e);
      return null;
    }
  }

  private static @NonNull String getExtensionFromMimeType(String mimeType) {
    final String extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType);
    return extension != null ? extension : BLOB_EXTENSION;
  }

  private static @NonNull String getMimeTypeFromExtension(@NonNull Uri uri) {
    final String mimeType = MimeTypeMap.getSingleton()
        .getMimeTypeFromExtension(MimeTypeMap.getFileExtensionFromUrl(uri.toString()));
    return mimeType != null ? mimeType : "application/octet-stream";
  }

  private static @NonNull File getExternalDir(Context context) throws IOException {
    final File externalDir = context.getExternalCacheDir();
    if (externalDir == null) throw new IOException("no external files directory");
    return externalDir;
  }

  public static boolean isAuthority(@NonNull Context context, @NonNull Uri uri) {
    int matchResult = MATCHER.match(uri);
    return matchResult == MATCH_NEW || matchResult == MATCH_OLD || isExternalBlobUri(context, uri);
  }

  private static boolean isExternalBlobUri(@NonNull Context context, @NonNull Uri uri) {
    try {
      return uri.getPath().startsWith(getExternalDir(context).getAbsolutePath()) || FileProviderUtil.isAuthority(uri);
    } catch (IOException ioe) {
      Log.w(TAG, "Failed to determine if it's an external blob URI.", ioe);
      return false;
    }
  }

  private static class FileData {
    private final File    file;
    private final boolean modern;

    private FileData(File file, boolean modern) {
      this.file   = file;
      this.modern = modern;
    }
  }
}
