package com.datarecovery.master.utils;

import android.Manifest;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.os.ParcelFileDescriptor;
import android.provider.MediaStore;

import androidx.annotation.IntDef;
import androidx.annotation.RequiresApi;

import com.atmob.common.permission.PermissionUtil;
import com.atmob.common.runtime.ContextUtil;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

public class MediaStoreHelper {

    public static final int TYPE_IMAGE = 1;

    public static final int TYPE_VIDEO = 2;

    public static final int TYPE_AUDIO = 3;

    public static final int TYPE_FILES = 4;

    public static final int TYPE_DOWNLOADS = 5;

    @Retention(RetentionPolicy.SOURCE)
    @IntDef({TYPE_IMAGE, TYPE_VIDEO, TYPE_AUDIO, TYPE_FILES, TYPE_DOWNLOADS})
    public @interface MediaType {
    }

    public static void saveToSharedStorage(@MediaType int mediaType, File file, String fileName) throws Exception {
        assertPermission();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            saveToSharedStorage(mediaType, java.nio.file.Files.newInputStream(file.toPath()), fileName);
        } else {
            saveToSharedStorage(mediaType, new FileInputStream(file), fileName);
        }
    }

    private static void assertPermission() {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
            if (!PermissionUtil.hasPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
                throw new IllegalStateException("Missing WRITE_EXTERNAL_STORAGE permission.");
            }
        }
    }

    public static void saveToSharedStorage(@MediaType int mediaType, InputStream inputStream, String fileName) throws Exception {
        // Add a media item that other apps don't see until the item is
        // fully written to the media store.
        Context applicationContext = ContextUtil.getContext().getApplicationContext();

        ContentResolver resolver = applicationContext.getContentResolver();

        Media targetMedia = getTargetMedia(mediaType);

        // Find all media files on the primary external storage device.
        Uri mediaCollection = targetMedia.getMediaCollection();

        ContentValues mediaDetails = new ContentValues();
        mediaDetails.put(targetMedia.displayName(), fileName);
        mediaDetails.put(MediaStore.MediaColumns.RELATIVE_PATH, targetMedia.relativePath());

        // lock media file until it's fully written.
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            mediaDetails.put(targetMedia.isPending(), 1);
        }

        Uri mediaContentUri;
        try {
            mediaContentUri = resolver.insert(mediaCollection, mediaDetails);
        } catch (Exception ignore) {
            // downgrade to the old way.
            saveToExternalStorage(targetMedia, inputStream, fileName);
            return;
        }

        if (mediaContentUri == null) {
            saveToExternalStorage(targetMedia, inputStream, fileName);
            return;
        }

        // "w" for write.
        try (ParcelFileDescriptor pfd =
                     resolver.openFileDescriptor(mediaContentUri, "w", null);
             InputStream is = inputStream;
             FileOutputStream fos = pfd == null ? null : new FileOutputStream(pfd.getFileDescriptor())
        ) {
            if (fos == null) {
                throw new IllegalStateException("Failed to open new media item.");
            }
            // Write data into the pending media file.
            byte[] buf = new byte[8192];
            int len;
            while ((len = is.read(buf)) > 0) {
                fos.write(buf, 0, len);
            }
            fos.flush();
        } finally {
            // finished, release the "pending" status.
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                mediaDetails.clear();
                mediaDetails.put(targetMedia.isPending(), 0);
                resolver.update(mediaContentUri, mediaDetails, null, null);
            }
        }
    }

    private static void saveToExternalStorage(Media media, InputStream inputStream, String fileName) throws Exception {
        File externalDir = media.getExternalDir();
        if (!Environment.isExternalStorageEmulated(externalDir)) {
            throw new IllegalStateException("External storage is not emulated.");
        }
        if (!externalDir.exists() && !externalDir.mkdirs()) {
            throw new IllegalStateException("Failed to create external storage directory: " + externalDir);
        }
        File targetFile = new File(externalDir, fileName);
        targetFile = createNotRepeatingFile(targetFile, 1);
        try (FileOutputStream fos = new FileOutputStream(targetFile)) {
            byte[] buf = new byte[8192];
            int len;
            while ((len = inputStream.read(buf)) > 0) {
                fos.write(buf, 0, len);
            }
            fos.flush();
        } catch (Exception e) {
            throw new IllegalStateException("Failed to save media file to external storage.", e);
        }
        // scan the file to make it visible to other apps.
        sendMediaScanBroadcast(targetFile);
    }

    private static void sendMediaScanBroadcast(File targetFile) {
        Context applicationContext = ContextUtil.getContext().getApplicationContext();
        applicationContext.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(targetFile)));
    }

    private static File createNotRepeatingFile(File targetFile, int index) throws Exception {
        if (targetFile.exists()) {
            String name = targetFile.getName();
            int dotIndex = name.lastIndexOf('.');
            String prefix = name.substring(0, dotIndex);
            String suffix = name.substring(dotIndex);
            targetFile = new File(targetFile.getParentFile(), prefix + "(" + index + ")" + suffix);
            return createNotRepeatingFile(targetFile, index + 1);
        } else {
            if (!targetFile.createNewFile()) {
                throw new IllegalStateException("Failed to create new file: " + targetFile);
            }
        }
        return targetFile;
    }

    private static Media getTargetMedia(int mediaType) {
        switch (mediaType) {
            case TYPE_IMAGE:
                return new Image();
            case TYPE_VIDEO:
                return new Video();
            case TYPE_AUDIO:
                return new Audio();
            case TYPE_FILES:
                return new Files();
            case TYPE_DOWNLOADS:
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                    return new Downloads();
                }
                return new Files();
            default:
                throw new IllegalArgumentException("Unknown media type: " + mediaType);
        }
    }

    private static class Image implements Media {
        @Override
        public Uri getMediaCollection() {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                return MediaStore.Images.Media
                        .getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY);
            } else {
                return MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
            }
        }

        @Override
        public String displayName() {
            return MediaStore.Images.Media.DISPLAY_NAME;
        }

        @RequiresApi(api = Build.VERSION_CODES.Q)
        @Override
        public String isPending() {
            return MediaStore.Images.Media.IS_PENDING;
        }

        @Override
        public String relativePath() {
            return Environment.DIRECTORY_PICTURES;
        }

        @Override
        public File getExternalDir() {
            return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
        }
    }

    private static class Video implements Media {

        @Override
        public Uri getMediaCollection() {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                return MediaStore.Video.Media
                        .getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY);
            } else {
                return MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
            }
        }

        @Override
        public String displayName() {
            return MediaStore.Video.Media.DISPLAY_NAME;
        }

        @RequiresApi(api = Build.VERSION_CODES.Q)
        @Override
        public String isPending() {
            return MediaStore.Video.Media.IS_PENDING;
        }

        @Override
        public String relativePath() {
            return Environment.DIRECTORY_MOVIES;
        }

        @Override
        public File getExternalDir() {
            return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES);
        }
    }

    private static class Audio implements Media {

        @Override
        public Uri getMediaCollection() {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                return MediaStore.Audio.Media
                        .getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY);
            } else {
                return MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
            }
        }

        @Override
        public String displayName() {
            return MediaStore.Audio.Media.DISPLAY_NAME;
        }

        @RequiresApi(api = Build.VERSION_CODES.Q)
        @Override
        public String isPending() {
            return MediaStore.Audio.Media.IS_PENDING;
        }

        @Override
        public String relativePath() {
            return Environment.DIRECTORY_MUSIC;
        }

        @Override
        public File getExternalDir() {
            return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC);
        }
    }

    private static class Files implements Media {

        @Override
        public Uri getMediaCollection() {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                return MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY);
            } else {
                return MediaStore.Files.getContentUri("external");
            }
        }

        @Override
        public String displayName() {
            return MediaStore.Files.FileColumns.DISPLAY_NAME;
        }

        @RequiresApi(api = Build.VERSION_CODES.Q)
        @Override
        public String isPending() {
            return MediaStore.Files.FileColumns.IS_PENDING;
        }

        @Override
        public String relativePath() {
            return Environment.DIRECTORY_DOCUMENTS;
        }

        @Override
        public File getExternalDir() {
            return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS);
        }
    }

    private static class Downloads implements Media {

        @RequiresApi(api = Build.VERSION_CODES.Q)
        @Override
        public Uri getMediaCollection() {
            return MediaStore.Downloads.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY);
        }

        @Override
        public String displayName() {
            return MediaStore.Downloads.DISPLAY_NAME;
        }

        @RequiresApi(api = Build.VERSION_CODES.Q)
        @Override
        public String isPending() {
            return MediaStore.Downloads.IS_PENDING;
        }

        @Override
        public String relativePath() {
            return Environment.DIRECTORY_DOWNLOADS;
        }

        @Override
        public File getExternalDir() {
            return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
        }
    }

    private interface Media extends MediaDir {
        Uri getMediaCollection();

        String displayName();

        String isPending();

        String relativePath();
    }

    private interface MediaDir {
        File getExternalDir();
    }
}