package com.tencent.qian.webview.filechooser;

import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.provider.MediaStore;
import android.util.Log;
import android.webkit.WebChromeClient;

import androidx.activity.ComponentActivity;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.core.content.FileProvider;
import androidx.core.util.Supplier;
import androidx.lifecycle.DefaultLifecycleObserver;
import androidx.lifecycle.LifecycleOwner;

import com.tencent.qian.webview.model.TencentQianWebViewConfig.CameraCaptureBehavior;
import com.tencent.qian.webview.permission.RuntimePermissionRequester;

import java.io.File;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * H5 {@code <input type="file">} 文件选择启动器。
 */
public class FileChooserLauncher {

    private static final String TAG = "FileChooserLauncher";
    private static final String CAPTURE_DIR_NAME = "qian_capture";
    private static final long ORPHAN_FILE_MAX_AGE_MS = 60 * 60 * 1000L;

    public static final List<String> DEFAULT_LEGACY_FACE_CAPTURE_MIME_TYPES =
            Collections.unmodifiableList(Arrays.asList("video/kyc", "video/faceid"));

    private final ActivityResultLauncher<Intent> launcher;
    private final State state;
    private final Supplier<Context> contextProvider;
    @Nullable private final RuntimePermissionRequester permissionRequester;
    private final Supplier<Activity> activityProvider;

    private static final java.util.Map<String, String> MIME_NORMALIZATION_MAP;
    static {
        java.util.HashMap<String, String> map = new java.util.HashMap<>();
        map.put("image/jpg", "image/jpeg");
        map.put("image/pjpeg", "image/jpeg");
        map.put("image/x-png", "image/png");
        map.put("audio/mp3", "audio/mpeg");
        MIME_NORMALIZATION_MAP = Collections.unmodifiableMap(map);
    }

    private FileChooserLauncher(
            @NonNull ActivityResultLauncher<Intent> launcher,
            @NonNull State state,
            @NonNull Supplier<Context> contextProvider,
            @Nullable RuntimePermissionRequester permissionRequester,
            @NonNull Supplier<Activity> activityProvider
    ) {
        this.launcher = launcher;
        this.state = state;
        this.contextProvider = contextProvider;
        this.permissionRequester = permissionRequester;
        this.activityProvider = activityProvider;
    }

    private static class State {
        WebChromeClient.FileChooserParams pending;
        SafeValueCallback pathCallback;
        boolean isCameraCapture;
        Uri cameraOutputUri;
        final List<File> capturedFiles = new ArrayList<>();
    }

    private static class SafeValueCallback {
        private final android.webkit.ValueCallback<Uri[]> delegate;
        private final AtomicBoolean consumed = new AtomicBoolean(false);

        SafeValueCallback(@Nullable android.webkit.ValueCallback<Uri[]> delegate) {
            this.delegate = delegate;
        }

        void onReceiveValue(@Nullable Uri[] value) {
            if (!consumed.compareAndSet(false, true)) return;
            if (delegate != null) delegate.onReceiveValue(value);
        }
    }

    public boolean launch(
            @NonNull WebChromeClient.FileChooserParams params,
            @Nullable android.webkit.ValueCallback<Uri[]> filePathCallback,
            @NonNull List<String> legacyFaceCaptureMimeTypes,
            @NonNull CameraCaptureBehavior cameraCaptureBehavior
    ) {
        if (state.pathCallback != null) {
            state.pathCallback.onReceiveValue(null);
        }
        SafeValueCallback safeCallback = new SafeValueCallback(filePathCallback);
        state.pathCallback = safeCallback;
        state.pending = params;
        state.isCameraCapture = false;

        if (cameraCaptureBehavior != CameraCaptureBehavior.DISABLED && params.isCaptureEnabled()) {
            return launchCameraForCaptureAttribute(params, cameraCaptureBehavior);
        }

        boolean needsLegacyCapture = !legacyFaceCaptureMimeTypes.isEmpty()
                && matchesAny(params.getAcceptTypes(), legacyFaceCaptureMimeTypes);
        if (needsLegacyCapture) {
            return launchLegacyVideoCapture(params);
        }

        return launchDefault(params);
    }

    private boolean launchDefault(@NonNull WebChromeClient.FileChooserParams params) {
        try {
            Intent intent = params.createIntent();
            normalizeIntentMimeTypes(intent, params);
            if (params.getMode() == WebChromeClient.FileChooserParams.MODE_OPEN_MULTIPLE) {
                intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
                    try {
                        int maxLimit = MediaStore.getPickImagesMaxLimit();
                        intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, maxLimit);
                    } catch (Exception ignored) {}
                }
            }
            launcher.launch(Intent.createChooser(intent, null));
            return true;
        } catch (Exception e) {
            Log.w(TAG, "createIntent() failed, fallback to manual ACTION_GET_CONTENT", e);
            try {
                List<String> allMimes = extractAllMimes(params.getAcceptTypes());
                Intent fallbackIntent = new Intent(Intent.ACTION_GET_CONTENT);
                fallbackIntent.addCategory(Intent.CATEGORY_OPENABLE);
                if (allMimes.size() > 1) {
                    if (hasMultipleTopLevelTypes(allMimes)) {
                        fallbackIntent.setType("*/*");
                    } else {
                        String topLevel = allMimes.get(0).split("/")[0];
                        fallbackIntent.setType(topLevel + "/*");
                    }
                    fallbackIntent.putExtra(Intent.EXTRA_MIME_TYPES, allMimes.toArray(new String[0]));
                } else {
                    String firstMime = extractFirstMime(params.getAcceptTypes());
                    fallbackIntent.setType(firstMime != null ? firstMime : "*/*");
                }
                if (params.getMode() == WebChromeClient.FileChooserParams.MODE_OPEN_MULTIPLE) {
                    fallbackIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
                }
                launcher.launch(Intent.createChooser(fallbackIntent, null));
                return true;
            } catch (Exception fallbackEx) {
                Log.w(TAG, "Fallback file chooser also failed", fallbackEx);
                state.pathCallback.onReceiveValue(null);
                state.pathCallback = null;
                state.pending = null;
                return false;
            }
        }
    }

    private boolean launchCameraForCaptureAttribute(
            @NonNull WebChromeClient.FileChooserParams params,
            @NonNull CameraCaptureBehavior behavior
    ) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            Context ctx = contextProvider.get();
            if (ctx == null) return launchDefault(params);
            boolean hasCameraPermission = ContextCompat.checkSelfPermission(ctx, Manifest.permission.CAMERA)
                    == PackageManager.PERMISSION_GRANTED;
            if (!hasCameraPermission) {
                Activity activity = activityProvider.get();
                if (activity == null || permissionRequester == null) {
                    return launchDefault(params);
                }
                permissionRequester.request(activity, new String[]{Manifest.permission.CAMERA}, grants -> {
                    Boolean granted = grants.get(Manifest.permission.CAMERA);
                    if (granted != null && granted) {
                        doLaunchCameraForCapture(params, behavior);
                    } else {
                        launchDefault(params);
                    }
                });
                return true;
            }
        }
        return doLaunchCameraForCapture(params, behavior);
    }

    private boolean doLaunchCameraForCapture(
            @NonNull WebChromeClient.FileChooserParams params,
            @NonNull CameraCaptureBehavior behavior
    ) {
        boolean wantsImage = matchesPrefix(params.getAcceptTypes(), "image/");
        boolean wantsVideo = matchesPrefix(params.getAcceptTypes(), "video/");

        Intent intent;
        if (wantsImage && !wantsVideo) {
            intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        } else {
            intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
            intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1);
        }
        applyCameraLensHint(intent, behavior);

        Uri outputUri = createCaptureOutputUri(wantsImage && !wantsVideo);
        if (outputUri != null) {
            intent.putExtra(MediaStore.EXTRA_OUTPUT, outputUri);
            intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
            state.cameraOutputUri = outputUri;
        }

        try {
            state.isCameraCapture = true;
            launcher.launch(intent);
            return true;
        } catch (Exception e) {
            state.isCameraCapture = false;
            state.cameraOutputUri = null;
            return launchDefault(params);
        }
    }

    private boolean launchLegacyVideoCapture(@NonNull WebChromeClient.FileChooserParams params) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            Context ctx = contextProvider.get();
            if (ctx == null) return launchDefault(params);
            boolean hasCameraPermission = ContextCompat.checkSelfPermission(ctx, Manifest.permission.CAMERA)
                    == PackageManager.PERMISSION_GRANTED;
            if (!hasCameraPermission) {
                Activity activity = activityProvider.get();
                if (activity == null || permissionRequester == null) {
                    return launchDefault(params);
                }
                permissionRequester.request(activity, new String[]{Manifest.permission.CAMERA}, grants -> {
                    Boolean granted = grants.get(Manifest.permission.CAMERA);
                    if (granted != null && granted) {
                        doLaunchLegacyVideoCapture(params);
                    } else {
                        launchDefault(params);
                    }
                });
                return true;
            }
        }
        return doLaunchLegacyVideoCapture(params);
    }

    private boolean doLaunchLegacyVideoCapture(@NonNull WebChromeClient.FileChooserParams params) {
        Intent captureIntent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
        captureIntent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1);
        captureIntent.putExtra(MediaStore.EXTRA_DURATION_LIMIT, 10);
        applyCameraLensHint(captureIntent, CameraCaptureBehavior.PREFER_FRONT);

        Uri outputUri = createCaptureOutputUri(false);
        if (outputUri != null) {
            captureIntent.putExtra(MediaStore.EXTRA_OUTPUT, outputUri);
            captureIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
            state.cameraOutputUri = outputUri;
        }

        try {
            state.isCameraCapture = true;
            launcher.launch(captureIntent);
            return true;
        } catch (Exception e) {
            state.isCameraCapture = false;
            state.cameraOutputUri = null;
            return launchDefault(params);
        }
    }

    @Nullable
    private Uri createCaptureOutputUri(boolean isImage) {
        Context ctx = contextProvider.get();
        if (ctx == null) return null;
        try {
            File cacheDir = new File(ctx.getCacheDir(), CAPTURE_DIR_NAME);
            cacheDir.mkdirs();
            String suffix = isImage ? ".jpg" : ".mp4";
            String prefix = isImage ? "IMG_" : "VID_";
            File tempFile = File.createTempFile(prefix, suffix, cacheDir);
            state.capturedFiles.add(tempFile);
            return FileProvider.getUriForFile(ctx, ctx.getPackageName() + ".qian.fileprovider", tempFile);
        } catch (Exception e) {
            Log.w(TAG, "Failed to create capture output Uri", e);
            return null;
        }
    }

    private void cleanCapturedFiles() {
        try {
            for (File file : state.capturedFiles) {
                if (file.exists()) file.delete();
            }
            state.capturedFiles.clear();
        } catch (Exception e) {
            Log.w(TAG, "Failed to clean captured files on destroy", e);
        }
    }

    private void applyCameraLensHint(@NonNull Intent intent, @NonNull CameraCaptureBehavior behavior) {
        switch (behavior) {
            case PREFER_FRONT:
                intent.putExtra("android.intent.extras.CAMERA_FACING", 1);
                intent.putExtra("android.intent.extras.LENS_FACING_FRONT", 1);
                intent.putExtra("android.intent.extra.USE_FRONT_CAMERA", true);
                intent.putExtra("android.intent.extras.CAMERA_FACING_FRONT", 1);
                break;
            case PREFER_BACK:
                intent.putExtra("android.intent.extras.CAMERA_FACING", 0);
                intent.putExtra("android.intent.extras.LENS_FACING_BACK", 1);
                intent.putExtra("android.intent.extra.USE_FRONT_CAMERA", false);
                intent.putExtra("android.intent.extras.CAMERA_FACING_BACK", 1);
                break;
            default:
                break;
        }
    }

    // ────────── MIME 工具方法 ──────────

    @Nullable
    private String extractFirstMime(@Nullable String[] acceptTypes) {
        if (acceptTypes == null || acceptTypes.length == 0) return null;
        for (String raw : acceptTypes) {
            for (String piece : raw.split(",")) {
                String norm = piece.trim();
                if (!norm.isEmpty() && norm.contains("/")) return normalizeMime(norm);
            }
        }
        return null;
    }

    @NonNull
    private List<String> extractAllMimes(@Nullable String[] acceptTypes) {
        if (acceptTypes == null || acceptTypes.length == 0) return Collections.emptyList();
        LinkedHashSet<String> seen = new LinkedHashSet<>();
        for (String raw : acceptTypes) {
            for (String piece : raw.split(",")) {
                String norm = piece.trim();
                if (!norm.isEmpty() && norm.contains("/")) {
                    seen.add(normalizeMime(norm));
                }
            }
        }
        return new ArrayList<>(seen);
    }

    private boolean hasMultipleTopLevelTypes(@NonNull List<String> mimes) {
        java.util.Set<String> topLevels = new java.util.HashSet<>();
        for (String mime : mimes) {
            String[] parts = mime.split("/");
            if (parts.length > 0) topLevels.add(parts[0].toLowerCase());
        }
        return topLevels.size() > 1;
    }

    @NonNull
    private String normalizeMime(@NonNull String mime) {
        String result = MIME_NORMALIZATION_MAP.get(mime.toLowerCase());
        return result != null ? result : mime;
    }

    private void normalizeIntentMimeTypes(@NonNull Intent intent, @NonNull WebChromeClient.FileChooserParams params) {
        String originalType = intent.getType();
        if (originalType != null && !originalType.isEmpty() && originalType.contains("/")) {
            String normalized = normalizeMime(originalType);
            if (!normalized.equals(originalType)) {
                intent.setType(normalized);
            }
        }

        String[] extraMimes = intent.getStringArrayExtra(Intent.EXTRA_MIME_TYPES);
        if (extraMimes != null && extraMimes.length > 0) {
            LinkedHashSet<String> normalizedSet = new LinkedHashSet<>();
            for (String m : extraMimes) normalizedSet.add(normalizeMime(m));
            intent.putExtra(Intent.EXTRA_MIME_TYPES, normalizedSet.toArray(new String[0]));
        }

        if (extraMimes == null) {
            List<String> allMimes = extractAllMimes(params.getAcceptTypes());
            if (allMimes.size() > 1) {
                if (hasMultipleTopLevelTypes(allMimes)) {
                    intent.setType("*/*");
                } else {
                    String topLevel = allMimes.get(0).split("/")[0];
                    intent.setType(topLevel + "/*");
                }
                intent.putExtra(Intent.EXTRA_MIME_TYPES, allMimes.toArray(new String[0]));
            }
        }
    }

    // ────────── 静态工具方法 ──────────

    public static boolean matchesAny(@Nullable String[] acceptTypes, @NonNull List<String> target) {
        if (acceptTypes == null || acceptTypes.length == 0 || target.isEmpty()) return false;
        java.util.Set<String> targetSet = new java.util.HashSet<>();
        for (String t : target) {
            String trimmed = t.trim().toLowerCase();
            if (!trimmed.isEmpty()) targetSet.add(trimmed);
        }
        if (targetSet.isEmpty()) return false;
        for (String raw : acceptTypes) {
            for (String piece : raw.split(",")) {
                String norm = piece.trim().toLowerCase();
                if (!norm.isEmpty() && targetSet.contains(norm)) return true;
            }
        }
        return false;
    }

    public static boolean matchesPrefix(@Nullable String[] acceptTypes, @NonNull String prefix) {
        if (acceptTypes == null || acceptTypes.length == 0) return false;
        String lowerPrefix = prefix.toLowerCase();
        for (String raw : acceptTypes) {
            for (String piece : raw.split(",")) {
                String norm = piece.trim().toLowerCase();
                if (norm.startsWith(lowerPrefix)) return true;
            }
        }
        return false;
    }

    // ────────── 静态 URI 校验 ──────────

    private static boolean isUriContentValid(@NonNull Context ctx, @NonNull Uri uri) {
        try (InputStream stream = ctx.getContentResolver().openInputStream(uri)) {
            return stream != null && stream.read() != -1;
        } catch (Exception e) {
            return false;
        }
    }

    private static void cleanOrphanCaptureFiles(@NonNull Context ctx) {
        try {
            File dir = new File(ctx.getCacheDir(), CAPTURE_DIR_NAME);
            if (!dir.exists()) return;
            long now = System.currentTimeMillis();
            File[] files = dir.listFiles();
            if (files == null) return;
            for (File file : files) {
                if (file.isFile() && (now - file.lastModified()) > ORPHAN_FILE_MAX_AGE_MS) {
                    file.delete();
                }
            }
        } catch (Exception e) {
            Log.w(TAG, "Failed to clean orphan capture files", e);
        }
    }

    // ────────── 注册工厂方法 ──────────

    @NonNull
    public static FileChooserLauncher register(
            @NonNull ComponentActivity activity,
            @Nullable RuntimePermissionRequester permissionRequester,
            @NonNull Supplier<Activity> activityProvider
    ) {
        State state = new State();
        Supplier<Context> contextProvider = () -> activity;

        cleanOrphanCaptureFiles(activity);

        ActivityResultLauncher<Intent> resultLauncher = activity.registerForActivityResult(
                new ActivityResultContracts.StartActivityForResult(),
                result -> {
                    SafeValueCallback cb = state.pathCallback;
                    if (cb == null) return;
                    boolean isCameraCapture = state.isCameraCapture;
                    Uri cameraOutputUri = state.cameraOutputUri;
                    state.pathCallback = null;
                    state.pending = null;
                    state.isCameraCapture = false;
                    state.cameraOutputUri = null;

                    if (result.getResultCode() != Activity.RESULT_OK) {
                        cb.onReceiveValue(null);
                        return;
                    }

                    Uri[] uris;
                    if (isCameraCapture) {
                        Uri uri = null;
                        Intent data = result.getData();
                        if (data != null && data.getData() != null) {
                            uri = data.getData();
                        } else if (data != null && data.getClipData() != null && data.getClipData().getItemCount() > 0) {
                            uri = data.getClipData().getItemAt(0).getUri();
                        } else if (cameraOutputUri != null && isUriContentValid(activity, cameraOutputUri)) {
                            uri = cameraOutputUri;
                        }
                        uris = uri != null ? new Uri[]{uri} : null;
                    } else {
                        Intent data = result.getData();
                        if (data == null) {
                            cb.onReceiveValue(null);
                            return;
                        }
                        android.content.ClipData clipData = data.getClipData();
                        if (clipData != null && clipData.getItemCount() > 0) {
                            uris = new Uri[clipData.getItemCount()];
                            for (int i = 0; i < clipData.getItemCount(); i++) {
                                uris[i] = clipData.getItemAt(i).getUri();
                            }
                        } else {
                            uris = WebChromeClient.FileChooserParams.parseResult(result.getResultCode(), data);
                            if (uris == null) uris = new Uri[0];
                        }
                    }
                    cb.onReceiveValue(uris);
                }
        );

        FileChooserLauncher fileChooserLauncher = new FileChooserLauncher(
                resultLauncher, state, contextProvider, permissionRequester, activityProvider
        );

        activity.getLifecycle().addObserver(new DefaultLifecycleObserver() {
            @Override
            public void onDestroy(@NonNull LifecycleOwner owner) {
                if (state.pathCallback != null) {
                    state.pathCallback.onReceiveValue(null);
                }
                state.pathCallback = null;
                state.pending = null;
                state.isCameraCapture = false;
                state.cameraOutputUri = null;
                fileChooserLauncher.cleanCapturedFiles();
            }
        });

        return fileChooserLauncher;
    }
}
