package com.qrcodestudio;

import android.Manifest;
import android.app.Activity;
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.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;

import androidx.activity.result.ActivityResult;
import androidx.annotation.RequiresApi;

import com.getcapacitor.JSArray;
import com.getcapacitor.JSObject;
import com.getcapacitor.PermissionState;
import com.getcapacitor.Plugin;
import com.getcapacitor.PluginCall;
import com.getcapacitor.PluginMethod;
import com.getcapacitor.annotation.ActivityCallback;
import com.getcapacitor.annotation.CapacitorPlugin;
import com.getcapacitor.annotation.Permission;
import com.getcapacitor.annotation.PermissionCallback;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.util.ArrayList;
import java.util.List;

@CapacitorPlugin(
    name = "QRCodeStudio",
    permissions = {
        @Permission(
            strings = { Manifest.permission.CAMERA },
            alias = QRCodeStudioPlugin.CAMERA
        ),
        @Permission(
            strings = { Manifest.permission.READ_EXTERNAL_STORAGE },
            alias = QRCodeStudioPlugin.PHOTOS
        )
    }
)
public class QRCodeStudioPlugin extends Plugin {
    static final String CAMERA = "camera";
    static final String PHOTOS = "photos";
    
    private static final String TAG = "QRCodeStudioPlugin";
    private static final int REQUEST_IMAGE_PICK = 12345;
    
    private QRCodeStudio implementation = new QRCodeStudio();
    private QRCodeScanner scanner;
    private QRCodeGenerator generator;
    private PluginCall currentCall;
    private FrameLayout scannerContainer;
    
    @Override
    public void load() {
        super.load();
        implementation.initialize(getContext());
        generator = new QRCodeGenerator(getContext());
    }
    
    @PluginMethod
    public void scan(PluginCall call) {
        if (getPermissionState(CAMERA) != PermissionState.GRANTED) {
            requestPermissionForAlias(CAMERA, call, "cameraPermissionCallback");
        } else {
            startScanning(call);
        }
    }
    
    @PermissionCallback
    private void cameraPermissionCallback(PluginCall call) {
        if (getPermissionState(CAMERA) == PermissionState.GRANTED) {
            startScanning(call);
        } else {
            call.reject("Camera permission denied");
        }
    }
    
    private void startScanning(PluginCall call) {
        currentCall = call;
        
        getActivity().runOnUiThread(() -> {
            try {
                // Get options
                JSArray formatsArray = call.getArray("formats");
                List<String> formats = new ArrayList<>();
                if (formatsArray != null) {
                    for (int i = 0; i < formatsArray.length(); i++) {
                        try {
                            formats.add(formatsArray.getString(i));
                        } catch (JSONException e) {
                            // Ignore invalid format
                        }
                    }
                }
                
                // Create scanner view
                scannerContainer = new FrameLayout(getContext());
                scannerContainer.setLayoutParams(new FrameLayout.LayoutParams(
                    ViewGroup.LayoutParams.MATCH_PARENT,
                    ViewGroup.LayoutParams.MATCH_PARENT
                ));
                
                // Add scanner view to activity
                ViewGroup rootView = (ViewGroup) getActivity().getWindow().getDecorView().getRootView();
                rootView.addView(scannerContainer);
                
                // Create scanner overlay
                View overlayView = LayoutInflater.from(getContext())
                    .inflate(getResourceId("layout", "scanner_overlay"), scannerContainer, false);
                scannerContainer.addView(overlayView);
                
                // Set up close button
                View closeButton = overlayView.findViewById(getResourceId("id", "close_button"));
                if (closeButton != null) {
                    closeButton.setOnClickListener(v -> {
                        stopScanning();
                        call.reject("CANCELLED", "User cancelled scanning");
                    });
                }
                
                // Set up torch button
                View torchButton = overlayView.findViewById(getResourceId("id", "torch_button"));
                if (torchButton != null && scanner != null && scanner.isTorchAvailable()) {
                    torchButton.setVisibility(View.VISIBLE);
                    torchButton.setOnClickListener(v -> {
                        if (scanner != null) {
                            scanner.toggleTorch();
                        }
                    });
                }
                
                // Set up flip camera button
                View flipButton = overlayView.findViewById(getResourceId("id", "flip_camera_button"));
                if (flipButton != null) {
                    flipButton.setOnClickListener(v -> {
                        if (scanner != null) {
                            scanner.flipCamera();
                        }
                    });
                }
                
                // Initialize scanner
                scanner = new QRCodeScanner(getContext(), getActivity());
                if (!formats.isEmpty()) {
                    scanner.setScanFormats(formats);
                }
                
                // Start scanning
                scanner.startScanning(scannerContainer, new QRCodeScanner.ScanCallback() {
                    @Override
                    public void onScanSuccess(String result) {
                        stopScanning();
                        JSObject scanResult = implementation.parseScanResult(result);
                        call.resolve(scanResult);
                    }
                    
                    @Override
                    public void onScanError(Exception error) {
                        stopScanning();
                        call.reject("Scan failed", error.getMessage());
                    }
                });
                
            } catch (Exception e) {
                Log.e(TAG, "Failed to start scanning", e);
                call.reject("Failed to start scanning", e.getMessage());
            }
        });
    }
    
    @PluginMethod
    public void stopScan(PluginCall call) {
        stopScanning();
        call.resolve();
    }
    
    private void stopScanning() {
        getActivity().runOnUiThread(() -> {
            if (scanner != null) {
                scanner.stopScanning();
                scanner.release();
                scanner = null;
            }
            
            if (scannerContainer != null && scannerContainer.getParent() != null) {
                ((ViewGroup) scannerContainer.getParent()).removeView(scannerContainer);
                scannerContainer = null;
            }
        });
    }
    
    @PluginMethod
    public void pauseScan(PluginCall call) {
        if (scanner != null) {
            scanner.pauseScanning();
        }
        call.resolve();
    }
    
    @PluginMethod
    public void resumeScan(PluginCall call) {
        if (scanner != null) {
            scanner.resumeScanning();
        }
        call.resolve();
    }
    
    @PluginMethod
    public void toggleTorch(PluginCall call) {
        if (scanner != null) {
            scanner.toggleTorch();
            call.resolve();
        } else {
            call.reject("Scanner not active");
        }
    }
    
    @PluginMethod
    public void generate(PluginCall call) {
        String type = call.getString("type");
        JSObject data = call.getObject("data");
        JSObject options = call.getObject("options", new JSObject());
        
        if (type == null || data == null) {
            call.reject("Missing required parameters");
            return;
        }
        
        try {
            JSObject result = generator.generate(type, data, options);
            call.resolve(result);
        } catch (Exception e) {
            call.reject("Failed to generate QR code", e.getMessage());
        }
    }
    
    @PluginMethod
    public void checkPermissions(PluginCall call) {
        JSObject result = new JSObject();
        
        // Camera permission
        PermissionState cameraState = getPermissionState(CAMERA);
        result.put("camera", getPermissionText(cameraState));
        
        // Photos permission
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            // Android 13+ doesn't need READ_EXTERNAL_STORAGE for photos
            result.put("photos", "granted");
        } else {
            PermissionState photosState = getPermissionState(PHOTOS);
            result.put("photos", getPermissionText(photosState));
        }
        
        call.resolve(result);
    }
    
    @PluginMethod
    public void requestPermissions(PluginCall call) {
        JSArray permissions = call.getArray("permissions");
        if (permissions == null) {
            call.reject("Missing permissions parameter");
            return;
        }
        
        List<String> permissionsToRequest = new ArrayList<>();
        
        try {
            for (int i = 0; i < permissions.length(); i++) {
                String permission = permissions.getString(i);
                if ("camera".equals(permission)) {
                    permissionsToRequest.add(CAMERA);
                } else if ("photos".equals(permission)) {
                    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
                        permissionsToRequest.add(PHOTOS);
                    }
                }
            }
        } catch (JSONException e) {
            call.reject("Invalid permissions array");
            return;
        }
        
        if (permissionsToRequest.isEmpty()) {
            checkPermissions(call);
        } else {
            String[] aliases = permissionsToRequest.toArray(new String[0]);
            requestPermissionForAliases(aliases, call, "permissionsCallback");
        }
    }
    
    @PermissionCallback
    private void permissionsCallback(PluginCall call) {
        checkPermissions(call);
    }
    
    @PluginMethod
    public void scanFromFile(PluginCall call) {
        currentCall = call;
        
        Intent intent = new Intent(Intent.ACTION_PICK);
        intent.setType("image/*");
        startActivityForResult(call, intent, "handleImagePickResult");
    }
    
    @ActivityCallback
    private void handleImagePickResult(PluginCall call, ActivityResult result) {
        if (result.getResultCode() == Activity.RESULT_OK && result.getData() != null) {
            Uri imageUri = result.getData().getData();
            if (imageUri != null) {
                scanImageFile(imageUri, call);
            } else {
                call.reject("No image selected");
            }
        } else {
            call.reject("CANCELLED", "User cancelled image selection");
        }
    }
    
    private void scanImageFile(Uri imageUri, PluginCall call) {
        if (scanner == null) {
            scanner = new QRCodeScanner(getContext(), getActivity());
        }
        
        scanner.scanFromImage(imageUri, new QRCodeScanner.ScanCallback() {
            @Override
            public void onScanSuccess(String result) {
                JSObject scanResult = implementation.parseScanResult(result);
                call.resolve(scanResult);
            }
            
            @Override
            public void onScanError(Exception error) {
                call.reject("Scan failed", error.getMessage());
            }
        });
    }
    
    @PluginMethod
    public void saveToHistory(PluginCall call) {
        JSObject item = call.getObject("item");
        if (item == null) {
            call.reject("Missing history item");
            return;
        }
        
        implementation.saveToHistory(item);
        call.resolve();
    }
    
    @PluginMethod
    public void getHistory(PluginCall call) {
        JSArray history = implementation.getHistory();
        JSObject result = new JSObject();
        result.put("items", history);
        call.resolve(result);
    }
    
    @PluginMethod
    public void clearHistory(PluginCall call) {
        implementation.clearHistory();
        call.resolve();
    }
    
    @PluginMethod
    public void readBarcodesFromImage(PluginCall call) {
        String path = call.getString("path");
        JSArray formatsArray = call.getArray("formats");
        
        if (path == null) {
            call.reject("Missing image path");
            return;
        }
        
        List<String> formats = new ArrayList<>();
        if (formatsArray != null) {
            for (int i = 0; i < formatsArray.length(); i++) {
                try {
                    formats.add(formatsArray.getString(i));
                } catch (JSONException e) {
                    // Ignore invalid format
                }
            }
        }
        
        currentCall = call;
        
        // If path is a content URI or file path, scan directly
        Uri uri = Uri.parse(path);
        scanImageFileForBarcodes(uri, formats, call);
    }
    
    @PluginMethod
    public void getSupportedFormats(PluginCall call) {
        JSArray formats = new JSArray();
        formats.put("QR_CODE");
        formats.put("AZTEC");
        formats.put("CODE_128");
        formats.put("CODE_39");
        formats.put("CODE_93");
        formats.put("CODABAR");
        formats.put("DATA_MATRIX");
        formats.put("EAN_13");
        formats.put("EAN_8");
        formats.put("ITF");
        formats.put("PDF_417");
        formats.put("UPC_A");
        formats.put("UPC_E");
        
        JSObject result = new JSObject();
        result.put("formats", formats);
        call.resolve(result);
    }
    
    @PluginMethod
    public void getTorchState(PluginCall call) {
        JSObject result = new JSObject();
        result.put("isEnabled", scanner != null && scanner.isTorchEnabled());
        result.put("isAvailable", scanner != null && scanner.isTorchAvailable());
        call.resolve(result);
    }
    
    @PluginMethod
    public void setZoom(PluginCall call) {
        Float zoom = call.getFloat("zoom");
        if (zoom == null) {
            call.reject("Missing zoom parameter");
            return;
        }
        
        if (scanner != null) {
            scanner.setZoom(zoom);
            call.resolve();
        } else {
            call.reject("Scanner not active");
        }
    }
    
    @PluginMethod
    public void generateBarcode(PluginCall call) {
        String format = call.getString("format");
        String data = call.getString("data");
        Integer width = call.getInt("width", 300);
        Integer height = call.getInt("height", 100);
        Boolean displayText = call.getBoolean("displayText", true);
        String outputFormat = call.getString("outputFormat", "png");
        
        if (format == null || data == null) {
            call.reject("Missing required parameters");
            return;
        }
        
        try {
            JSObject result = generator.generateBarcode(format, data, width, height, displayText, outputFormat);
            call.resolve(result);
        } catch (Exception e) {
            call.reject("Failed to generate barcode", e.getMessage());
        }
    }
    
    private void scanImageFileForBarcodes(Uri imageUri, List<String> formats, PluginCall call) {
        if (scanner == null) {
            scanner = new QRCodeScanner(getContext(), getActivity());
        }
        
        if (!formats.isEmpty()) {
            scanner.setScanFormats(formats);
        }
        
        scanner.scanFromImageWithDetails(imageUri, new QRCodeScanner.DetailedScanCallback() {
            @Override
            public void onScanSuccess(List<JSObject> results) {
                JSArray resultsArray = new JSArray();
                for (JSObject result : results) {
                    resultsArray.put(result);
                }
                call.resolve(new JSObject().put("results", resultsArray));
            }
            
            @Override
            public void onScanError(Exception error) {
                call.reject("Scan failed", error.getMessage());
            }
        });
    }
    
    // Helper methods
    
    private String getPermissionText(PermissionState state) {
        switch (state) {
            case GRANTED:
                return "granted";
            case DENIED:
                return "denied";
            case PROMPT:
            case PROMPT_WITH_RATIONALE:
                return "prompt";
            default:
                return "prompt";
        }
    }
    
    private int getResourceId(String defType, String name) {
        return getContext().getResources().getIdentifier(
            name, defType, getContext().getPackageName()
        );
    }
    
    @Override
    protected void handleOnDestroy() {
        super.handleOnDestroy();
        if (scanner != null) {
            scanner.release();
        }
    }
}