package io.fullstack.firestack;

import android.os.Environment;
import android.os.StatFs;
import android.content.Context;
import android.util.Log;
import java.util.Map;
import java.util.HashMap;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.FileNotFoundException;

import android.net.Uri;
import android.provider.MediaStore;
import android.database.Cursor;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;

import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.facebook.react.bridge.ReactContext;

import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.OnFailureListener;
import com.google.android.gms.tasks.OnSuccessListener;
import com.google.android.gms.tasks.Task;

import com.google.firebase.storage.OnProgressListener;
import com.google.firebase.storage.OnPausedListener;

import com.google.firebase.FirebaseApp;

import com.google.firebase.storage.FirebaseStorage;
import com.google.firebase.storage.UploadTask;

import com.google.firebase.storage.StorageMetadata;
import com.google.firebase.storage.StorageReference;

class FirestackStorage extends ReactContextBaseJavaModule {

  private static final String TAG = "FirestackStorage";
  private static final String DocumentDirectoryPath = "DOCUMENT_DIRECTORY_PATH";
  private static final String ExternalDirectoryPath = "EXTERNAL_DIRECTORY_PATH";
  private static final String ExternalStorageDirectoryPath = "EXTERNAL_STORAGE_DIRECTORY_PATH";
  private static final String PicturesDirectoryPath = "PICTURES_DIRECTORY_PATH";
  private static final String TemporaryDirectoryPath = "TEMPORARY_DIRECTORY_PATH";
  private static final String CachesDirectoryPath = "CACHES_DIRECTORY_PATH";
  private static final String DocumentDirectory = "DOCUMENT_DIRECTORY_PATH";
  private static final String BundlePath = "MAIN_BUNDLE_PATH";

  private static final String FileTypeRegular = "FILETYPE_REGULAR";
  private static final String FileTypeDirectory = "FILETYPE_DIRECTORY";


  private Context context;
  private ReactContext mReactContext;
  private FirebaseApp app;

  public FirestackStorage(ReactApplicationContext reactContext) {
    super(reactContext);

    Log.d(TAG, "Attaching FirestackStorage");
    this.context = reactContext;
    mReactContext = reactContext;

    Log.d(TAG, "New instance");
  }

  @Override
  public String getName() {
    return TAG;
  }

  @ReactMethod
  public void downloadUrl(final String javascriptStorageBucket,
                          final String path,
                          final Callback callback) {
      FirebaseStorage storage = FirebaseStorage.getInstance();
      String storageBucket = storage.getApp().getOptions().getStorageBucket();
      String storageUrl = "gs://"+storageBucket;
      Log.d(TAG, "Storage url " + storageUrl + path);
      final StorageReference storageRef = storage.getReferenceFromUrl(storageUrl);
      final StorageReference fileRef = storageRef.child(path);

      Task<Uri> downloadTask = fileRef.getDownloadUrl();
      downloadTask.addOnSuccessListener(new OnSuccessListener<Uri>() {
        @Override
        public void onSuccess(Uri uri) {
          final WritableMap res = Arguments.createMap();

          res.putString("status", "success");
          res.putString("bucket", storageRef.getBucket());
          res.putString("fullPath", uri.toString());
          res.putString("path", uri.getPath());
          res.putString("url", uri.toString());

          fileRef.getMetadata()
          .addOnSuccessListener(new OnSuccessListener<StorageMetadata>() {
              @Override
              public void onSuccess(final StorageMetadata storageMetadata) {
                Log.d(TAG, "getMetadata success " + storageMetadata);
                res.putString("name", storageMetadata.getName());

                WritableMap metadata = Arguments.createMap();
                metadata.putString("getBucket", storageMetadata.getBucket());
                metadata.putString("getName", storageMetadata.getName());
                metadata.putDouble("sizeBytes", storageMetadata.getSizeBytes());
                metadata.putDouble("created_at", storageMetadata.getCreationTimeMillis());
                metadata.putDouble("updated_at", storageMetadata.getUpdatedTimeMillis());
                metadata.putString("md5hash", storageMetadata.getMd5Hash());
                metadata.putString("encoding", storageMetadata.getContentEncoding());
                res.putString("url", storageMetadata.getDownloadUrl().toString());

                res.putMap("metadata", metadata);
                callback.invoke(null, res);
              }
          }).addOnFailureListener(new OnFailureListener() {
              @Override
              public void onFailure(@NonNull Exception exception) {
                Log.e(TAG, "Failure in download " + exception);
                  callback.invoke(makeErrorPayload(1, exception));
              }
          });

        }
      }).addOnFailureListener(new OnFailureListener() {
        @Override
        public void onFailure(@NonNull Exception exception) {
          Log.e(TAG, "Failed to download file " + exception.getMessage());

          WritableMap err = Arguments.createMap();
          err.putString("status", "error");
          err.putString("description", exception.getLocalizedMessage());

          callback.invoke(err);
        }
      });
  }

  // STORAGE
  @ReactMethod
  public void uploadFile(final String urlStr, final String name, final String filepath, final ReadableMap metadata, final Callback callback) {
    FirebaseStorage storage = FirebaseStorage.getInstance();

    StorageReference storageRef = urlStr!=null ? storage.getReferenceFromUrl(urlStr) : storage.getReference();
    StorageReference fileRef = storageRef.child(name);

Log.i(TAG, "From file: " + filepath + " to " + urlStr + " with name " + name);
    try {
      // InputStream stream = new FileInputStream(new File(filepath));
      Uri file = Uri.fromFile(new File(filepath));

      StorageMetadata.Builder metadataBuilder = new StorageMetadata.Builder();
      Map<String, Object> m = FirestackUtils.recursivelyDeconstructReadableMap(metadata);

      StorageMetadata md = metadataBuilder.build();
      UploadTask uploadTask = fileRef.putFile(file, md);
      // UploadTask uploadTask = fileRef.putStream(stream, md);

      // Register observers to listen for when the download is done or if it fails
      uploadTask.addOnFailureListener(new OnFailureListener() {
        @Override
        public void onFailure(@NonNull Exception exception) {
          // Handle unsuccessful uploads
          Log.e(TAG, "Failed to upload file " + exception.getMessage());

          WritableMap err = Arguments.createMap();
          err.putString("description", exception.getLocalizedMessage());

          callback.invoke(err);
        }
      }).addOnSuccessListener(new OnSuccessListener<UploadTask.TaskSnapshot>() {
        @Override
        public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) {
          Log.d(TAG, "Successfully uploaded file " + taskSnapshot);
          // taskSnapshot.getMetadata() contains file metadata such as size, content-type, and download URL.
          WritableMap resp = getDownloadData(taskSnapshot);
                  // NSDictionary *props = @{
                  //               @"fullPath": ref.fullPath,
                  //               @"bucket": ref.bucket,
                  //               @"name": ref.name,
                  //               @"metadata": [snapshot.metadata dictionaryRepresentation]
                  //               };

          callback.invoke(null, resp);
        }
      })
      .addOnProgressListener(new OnProgressListener<UploadTask.TaskSnapshot>() {
        @Override
        public void onProgress(UploadTask.TaskSnapshot taskSnapshot) {
          double totalBytes = taskSnapshot.getTotalByteCount();
          double bytesTransferred = taskSnapshot.getBytesTransferred();
          double progress = (100.0 * bytesTransferred) / totalBytes;

          System.out.println("Transferred " + bytesTransferred + "/" + totalBytes + "("+progress + "% complete)");

          if (progress >= 0) {
            WritableMap data = Arguments.createMap();
            data.putString("eventName", "upload_progress");
            data.putDouble("progress", progress);
            FirestackUtils.sendEvent(mReactContext, "upload_progress", data);
          }
        }
      }).addOnPausedListener(new OnPausedListener<UploadTask.TaskSnapshot>() {
        @Override
        public void onPaused(UploadTask.TaskSnapshot taskSnapshot) {
          System.out.println("Upload is paused");
          StorageMetadata d = taskSnapshot.getMetadata();
          String bucket = d.getBucket();
          WritableMap data = Arguments.createMap();
          data.putString("eventName", "upload_paused");
          data.putString("ref", bucket);
          FirestackUtils.sendEvent(mReactContext, "upload_paused", data);
        }
      });
    }
    catch (Exception ex) {
      callback.invoke(makeErrorPayload(2, ex));
    }
  }

  @ReactMethod
  public void getRealPathFromURI(final String uri, final Callback callback) {
    try {
      Context context = getReactApplicationContext();
      String [] proj = {MediaStore.Images.Media.DATA};
      Cursor cursor = context.getContentResolver().query(Uri.parse(uri), proj,  null, null, null);
      int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
      cursor.moveToFirst();
      String path = cursor.getString(column_index); 
      cursor.close();
  
      callback.invoke(null, path);
    } catch (Exception ex) {
      ex.printStackTrace();
      callback.invoke(makeErrorPayload(1, ex));
    }
  }

  private WritableMap getDownloadData(final UploadTask.TaskSnapshot taskSnapshot) {
    Uri downloadUrl = taskSnapshot.getDownloadUrl();
    StorageMetadata d = taskSnapshot.getMetadata();

    WritableMap resp = Arguments.createMap();
    resp.putString("downloadUrl", downloadUrl.toString());
    resp.putString("fullPath", d.getPath());
    resp.putString("bucket", d.getBucket());
    resp.putString("name", d.getName());

    WritableMap metadataObj = Arguments.createMap();
    metadataObj.putString("cacheControl", d.getCacheControl());
    metadataObj.putString("contentDisposition", d.getContentDisposition());
    metadataObj.putString("contentType", d.getContentType());
    resp.putMap("metadata", metadataObj);

    return resp;
  }

  private WritableMap makeErrorPayload(double code, Exception ex) {
    WritableMap error = Arguments.createMap();
    error.putDouble("code", code);
    error.putString("message", ex.getMessage());
    return error;
  }

  // Comes almost directory from react-native-fs
  @Override
  public Map<String, Object> getConstants() {
    final Map<String, Object> constants = new HashMap<>();

    constants.put(DocumentDirectory, 0);
    constants.put(DocumentDirectoryPath, this.getReactApplicationContext().getFilesDir().getAbsolutePath());
    constants.put(TemporaryDirectoryPath, null);
    constants.put(PicturesDirectoryPath, Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).getAbsolutePath());
    constants.put(CachesDirectoryPath, this.getReactApplicationContext().getCacheDir().getAbsolutePath());
    constants.put(FileTypeRegular, 0);
    constants.put(FileTypeDirectory, 1);
    constants.put(BundlePath, null);

    File externalStorageDirectory = Environment.getExternalStorageDirectory();
    if (externalStorageDirectory != null) {
        constants.put(ExternalStorageDirectoryPath, externalStorageDirectory.getAbsolutePath());
    } else {
        constants.put(ExternalStorageDirectoryPath, null);
    }

    File externalDirectory = this.getReactApplicationContext().getExternalFilesDir(null);
    if (externalDirectory != null) {
      constants.put(ExternalDirectoryPath, externalDirectory.getAbsolutePath());
    } else {
      constants.put(ExternalDirectoryPath, null);
    }

    return constants;
  }
}