package app.gwo.safenhancer.lite;

import android.Manifest;
import android.app.AlertDialog;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.provider.MediaStore;
import android.util.Log;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.View;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.documentfile.provider.DocumentFile;
import app.gwo.safenhancer.lite.compat.Optional;
import app.gwo.safenhancer.lite.util.BuildUtils;
import app.gwo.safenhancer.lite.util.DumpUtils;
import app.gwo.safenhancer.lite.util.Settings;
import moe.shizuku.redirectstorage.RedirectPackageInfo;
import moe.shizuku.redirectstorage.StorageRedirectManager;

import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static app.gwo.safenhancer.lite.BuildConfig.DEBUG;
import static java.util.Objects.requireNonNull;

/**
 * Proxy open result to WeChat (include other apps who call camera apps)
 *
 * @author Fung Gwo ([email protected])
 */
public final class ProxyCameraActivity extends BaseActivity {

    public static final String TAG = ProxyCameraActivity.class.getSimpleName();

    public static final int REQUEST_CODE_OPEN = 1;
    public static final int REQUEST_CODE_CAPTURE = 2;
    public static final int REQUEST_CODE_REQUEST_CAMERA_PERMISSION = 3;

    private boolean shouldBeHandled = false;

    private Uri mExpectedOutput = null;

    @Nullable
    private ComponentName mExpectedCameraComponent = null;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Prevent invalid actions
        final Intent intent = getIntent();
        if (intent == null || intent.getAction() == null) {
            Log.e(TAG, "Invalid intent. Activity will exit now.");
            finish();
            return;
        }

        if (!MediaStore.ACTION_IMAGE_CAPTURE.equals(intent.getAction())) {
            Log.e(TAG, "ProxyCameraActivity can receive Media.ACTION_IMAGE_CAPTURE only. " +
                    "But its action is " + intent.getAction());
            finish();
            return;
        }

        // Get extras from current capture intent
        getExtrasFromCaptureIntent(intent);

        // Start process
        shouldBeHandled = Settings.isSourceAppShouldBeHandled(getReferrerPackage());
        if (shouldBeHandled) {
            Log.d(TAG, "Receive an valid capture intent from WeChat. " +
                    "Now we start process it.");
            processIntentForWeChat(intent);
        } else {
            Log.v(TAG, "Receive an valid capture intent from other apps. " +
                    "We should open a preferred camera application or start chooser.");
            processIntentForOthers(intent);
        }
    }

    private void getExtrasFromCaptureIntent(@NonNull Intent intent) {
        if (DEBUG) {
            Log.d(TAG, "Received intent extras=" + DumpUtils.toString(intent.getExtras()));
        }

        if (intent.hasExtra(MediaStore.EXTRA_OUTPUT)) {
            mExpectedOutput = intent.getParcelableExtra(MediaStore.EXTRA_OUTPUT);
            final String referrerPackage = getReferrerPackage();

            boolean isolatedStoragePathProceed = false;

            if (mExpectedOutput != null
                    && "file".equals(mExpectedOutput.getScheme())
                    && referrerPackage != null
                    && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
                    && StorageRedirectManager.isSupported(getPackageManager())
                    && checkSelfPermission(StorageRedirectManager.PERMISSION) == PERMISSION_GRANTED
            ) {
                StorageRedirectManager srm = StorageRedirectManager.create();
                if (srm != null) {
                    try {
                        RedirectPackageInfo rpi = srm.getRedirectPackageInfo(
                                referrerPackage, 0, 0);
                        if (rpi != null && rpi.enabled) {
                            Log.d(TAG, "Package " + referrerPackage + " is enabled redirect.");
                            String originalPath = mExpectedOutput.toString();
                            String externalRoot = Environment.getExternalStorageDirectory()
                                    .getAbsolutePath();
                            String redirectTarget = rpi.redirectTarget;
                            if (redirectTarget == null) {
                                redirectTarget = srm.getDefaultRedirectTarget();
                            }
                            if (rpi.redirectTarget.contains("%s")) {
                                redirectTarget = String.format(redirectTarget, referrerPackage);
                            }
                            String newExternalRoot = externalRoot;
                            if (!redirectTarget.isEmpty()) {
                                newExternalRoot = newExternalRoot + "/" + redirectTarget;
                            }
                            mExpectedOutput = Uri.parse(
                                    originalPath.replace(externalRoot, newExternalRoot)
                            );
                            isolatedStoragePathProceed = true;
                            Log.d(TAG, "Original path: " + originalPath + ", external root: " +
                                    externalRoot + ", redirect target: " + rpi.redirectTarget +
                                    ", after: " + mExpectedOutput);
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                        if (DEBUG) {
                            throw e;
                        }
                    }
                }
            }

            if (mExpectedOutput != null
                    && "file".equals(mExpectedOutput.getScheme())
                    && referrerPackage != null
                    && BuildUtils.isAtLeastQ()
                    && !isolatedStoragePathProceed) {
                // TODO Check LEGACY_STORAGE app ops
                final Uri rootUri = Settings.getInstance().getRootStorageUri().get();
                if (rootUri == null) {
                    // TODO Show warning
                    Log.w(TAG, "Android Q+ cannot work without Storage Access Framework API.");
                } else {
                    DocumentFile rootFile = DocumentFile.fromTreeUri(this, rootUri);
                    DocumentFile sandboxRoot = Optional.ofNullable(rootFile)
                            .map(file -> file.findFile("Android"))
                            .filter(DocumentFile::isDirectory)
                            .map(file -> file.findFile("sandbox"))
                            .filter(DocumentFile::isDirectory)
                            .map(file -> {
                                String sandboxDirName = referrerPackage;
                                try {
                                    PackageInfo pi = getPackageManager()
                                            .getPackageInfo(referrerPackage, 0);
                                    if (pi.sharedUserId != null) {
                                        sandboxDirName = "shared-" + pi.sharedUserId;
                                    }
                                } catch (PackageManager.NameNotFoundException e) {
                                    e.printStackTrace();
                                }
                                return file.findFile(sandboxDirName);
                            })
                            .filter(DocumentFile::isDirectory)
                            .get();
                    if (sandboxRoot == null) {
                        sandboxRoot = rootFile;
                    }
                    String externalRoot = Environment.getExternalStorageDirectory()
                            .getAbsolutePath();
                    String originalPath = mExpectedOutput.toString();
                    try {
                        originalPath = URLDecoder.decode(originalPath, "UTF-8");
                    } catch (UnsupportedEncodingException e) {
                        e.printStackTrace();
                    }
                    originalPath = originalPath.replace("file://" + externalRoot + "/", "");
                    String[] pathSegs = Optional.ofNullable(originalPath.split("/"))
                            .orElse(new String[0]);
                    Optional<DocumentFile> curFile = Optional.ofNullable(sandboxRoot);
                    for (int i = 0; i < pathSegs.length; i++) {
                        String path = pathSegs[i];
                        curFile = curFile.map(file -> file.findFile(path));
                        if (i != pathSegs.length - 1) {
                            curFile = curFile.filter(DocumentFile::isDirectory);
                            if (!curFile.isPresent()) {
                                break;
                            }
                        }
                    }
                    mExpectedOutput = curFile.map(DocumentFile::getUri).orElse(mExpectedOutput);
                }
            }

            Log.d(TAG, "Expected output path: " + mExpectedOutput);
        }
    }

    private void processIntentForWeChat(@NonNull Intent intent) {
        final Context themedContext = new ContextThemeWrapper(
                this, android.R.style.Theme_Material_Light_Dialog);
        final View view = LayoutInflater.from(themedContext)
                .inflate(R.layout.dialog_doc_or_cam_chooser_content, null);
        view.findViewById(R.id.action_camera).setOnClickListener(v -> {
            processIntentForOthers(intent);
        });
        view.findViewById(R.id.action_documents).setOnClickListener(v -> {
            Intent openIntent = new Intent(Intent.ACTION_GET_CONTENT);
            openIntent.setType("image/*");
            startActivityForResult(openIntent, REQUEST_CODE_OPEN);
        });
        new AlertDialog.Builder(themedContext)
                .setView(view)
                .setNegativeButton(android.R.string.cancel, null)
                .show();
    }

    private void processIntentForOthers(@NonNull Intent intent) {
        final ComponentName preferredCamera = Settings.getInstance().getPreferredCamera();
        boolean launched = false;
        if (preferredCamera != null) {
            Log.d(TAG, "Launch preferred camera: " + preferredCamera.toString());
            try {
                onStartCameraApp(preferredCamera);
                launched = true;
            } catch (ActivityNotFoundException e) {
                e.printStackTrace();
                Settings.getInstance().setPreferredCamera(null);
            }
        }
        if (!launched) {
            CameraChooserDialogFragment
                    .newInstance()
                    .show(getFragmentManager(), "CameraChooser");
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (DEBUG) {
            Log.d(TAG, "onActivityResult: requestCode=" + requestCode +
                    ", resultCode=" + resultCode);
            if (data != null) {
                Log.d(TAG, "data=" + data.toString());
                Log.d(TAG, "extras=" + data.getExtras());
            }
        }
        if (RESULT_CANCELED == resultCode) {
            setResult(RESULT_CANCELED);
            finish();
        } else if (RESULT_OK == resultCode) {
            switch (requestCode) {
                case REQUEST_CODE_OPEN: {
                    if (data != null && data.getData() != null) {
                        CopyProgressDialogFragment
                                .newInstance(data.getData(), mExpectedOutput)
                                .show(getFragmentManager(), "Copy");
                    } else {
                        // TODO Show failed
                        finish();
                    }
                    break;
                }
                case REQUEST_CODE_CAPTURE: {
                    if (data != null) {
                        setResult(RESULT_OK, new Intent());
                        finish();
                    } else {
                        // TODO Show failed
                        finish();
                    }
                    break;
                }
                default: {
                    Log.e(TAG, "onActivityResult: Unsupported requestCode!");
                    finish();
                }
            }
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode,
                                           @NonNull String[] permissions,
                                           @NonNull int[] grantResults) {
        switch (requestCode) {
            case REQUEST_CODE_REQUEST_CAMERA_PERMISSION:
                if (!Manifest.permission.CAMERA.equals(permissions[0]) ||
                        PERMISSION_GRANTED != grantResults[0]) {
                    Log.e(TAG, "No permission.");
                    finish();
                    return;
                }
                if (mExpectedCameraComponent != null) {
                    onStartCameraApp(mExpectedCameraComponent);
                } else {
                    finish();
                }
                break;
            default:
                Log.e(TAG, "Unsupported result.");
                finish();
        }
    }

    void onCopyResult(boolean success) {
        if (success) {
            setResult(RESULT_OK, new Intent());
        } else {
            Log.e(TAG, "Failed to copy.");
        }
        finish();
    }

    void onStartCameraApp(@NonNull ComponentName target) {
        requireNonNull(target);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            if (checkSelfPermission(Manifest.permission.CAMERA) != PERMISSION_GRANTED) {
                mExpectedCameraComponent = target;
                requestPermissions(
                        new String[] { Manifest.permission.CAMERA },
                        REQUEST_CODE_REQUEST_CAMERA_PERMISSION
                );
                return;
            }
        }
        Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        cameraIntent.setComponent(target);
        cameraIntent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
        cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, mExpectedOutput);
        startActivityForResult(cameraIntent, REQUEST_CODE_CAPTURE);
    }

}