package com.manasuka.pos;

import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.usb.UsbConstants;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbEndpoint;
import android.hardware.usb.UsbInterface;
import android.hardware.usb.UsbManager;
import android.os.Build;
import android.util.Base64;
import android.util.Log;
import com.getcapacitor.JSArray;
import com.getcapacitor.JSObject;
import com.getcapacitor.Plugin;
import com.getcapacitor.PluginCall;
import com.getcapacitor.PluginMethod;
import com.getcapacitor.annotation.CapacitorPlugin;
import java.util.HashMap;

@CapacitorPlugin(name = "UsbPrinter")
public class UsbPrinterPlugin extends Plugin {
    private static final String TAG = "UsbPrinterPlugin";
    private static final String ACTION_USB_PERMISSION = "com.manasuka.pos.USB_PERMISSION";

    private UsbManager usbManager;
    private UsbDevice connectedDevice = null;
    private UsbDeviceConnection connection = null;
    private UsbInterface printerInterface = null;
    private UsbEndpoint bulkOutEndpoint = null;
    private PluginCall pendingPermissionCall = null;
    private UsbDevice pendingDevice = null;

    private static class PrinterEndpointInfo {
        final UsbInterface usbInterface;
        final UsbEndpoint bulkOut;

        PrinterEndpointInfo(UsbInterface usbInterface, UsbEndpoint bulkOut) {
            this.usbInterface = usbInterface;
            this.bulkOut = bulkOut;
        }
    }

    private final BroadcastReceiver usbReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if (action == null) return;

            if (ACTION_USB_PERMISSION.equals(action)) {
                synchronized (this) {
                    UsbDevice device = null;
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
                        device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE, UsbDevice.class);
                    } else {
                        device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
                    }

                    if (device == null) {
                        device = pendingDevice;
                    }

                    boolean granted = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false);
                    Log.i(TAG, "USB Permission result: granted=" + granted + ", device=" + (device != null ? device.getDeviceName() : "null"));

                    if (granted && device != null) {
                        tryOpenDevice(device, pendingPermissionCall);
                    } else {
                        if (pendingPermissionCall != null) {
                            pendingPermissionCall.reject("Izin akses printer USB ditolak oleh pengguna");
                            pendingPermissionCall = null;
                        }
                    }
                    pendingDevice = null;
                }
            } else if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(action)) {
                Log.i(TAG, "USB Device Attached via USB Hub / OTG");
                notifyListeners("usbDevicesChanged", getDevicesListJson());
            } else if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)) {
                UsbDevice detachedDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
                Log.i(TAG, "USB Device Detached");
                if (connectedDevice != null && detachedDevice != null && connectedDevice.getDeviceId() == detachedDevice.getDeviceId()) {
                    closeConnection();
                    notifyListeners("usbPrinterStateChange", getStatusJson());
                }
                notifyListeners("usbDevicesChanged", getDevicesListJson());
            }
        }
    };

    @Override
    public void load() {
        usbManager = (UsbManager) getContext().getSystemService(Context.USB_SERVICE);
        IntentFilter filter = new IntentFilter();
        filter.addAction(ACTION_USB_PERMISSION);
        filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
        filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            // Must be RECEIVER_EXPORTED so Android system server can deliver the permission broadcast
            getContext().registerReceiver(usbReceiver, filter, Context.RECEIVER_EXPORTED);
        } else {
            getContext().registerReceiver(usbReceiver, filter);
        }
    }

    /**
     * List all connected USB devices (Printers, Scanners, Hubs) with detailed diagnostics
     */
    @PluginMethod
    public void list(PluginCall call) {
        JSObject ret = getDevicesListJson();
        call.resolve(ret);
    }

    private JSObject getDevicesListJson() {
        if (usbManager == null) {
            usbManager = (UsbManager) getContext().getSystemService(Context.USB_SERVICE);
        }

        HashMap<String, UsbDevice> deviceList = usbManager.getDeviceList();
        JSArray resultList = new JSArray();

        for (UsbDevice device : deviceList.values()) {
            boolean isPrinter = isPrinterCandidate(device);
            boolean isScanner = isKeyboardScanner(device);
            String name = getDeviceDisplayName(device);

            JSObject obj = new JSObject();
            obj.put("deviceId", device.getDeviceId());
            obj.put("vendorId", String.format("0x%04X", device.getVendorId()));
            obj.put("productId", String.format("0x%04X", device.getProductId()));
            obj.put("name", name);
            obj.put("isPrinter", isPrinter);
            obj.put("isScanner", isScanner);
            obj.put("hasPermission", usbManager.hasPermission(device));
            obj.put("isConnected", connectedDevice != null && connectedDevice.getDeviceId() == device.getDeviceId());
            resultList.put(obj);
        }

        JSObject ret = new JSObject();
        ret.put("devices", resultList);
        ret.put("count", deviceList.size());
        return ret;
    }

    /**
     * Connect to a specific USB printer by deviceId, or auto-detect first printer
     */
    @PluginMethod
    public void connect(PluginCall call) {
        if (usbManager == null) {
            usbManager = (UsbManager) getContext().getSystemService(Context.USB_SERVICE);
        }

        Integer targetDeviceId = call.getInt("deviceId");
        HashMap<String, UsbDevice> deviceList = usbManager.getDeviceList();
        UsbDevice targetDevice = null;

        if (targetDeviceId != null) {
            for (UsbDevice d : deviceList.values()) {
                if (d.getDeviceId() == targetDeviceId) {
                    targetDevice = d;
                    break;
                }
            }
        } else {
            // Auto-detect priority:
            // 1. Device with explicit Printer Class (Class 7)
            for (UsbDevice d : deviceList.values()) {
                if (hasPrinterClass(d)) {
                    targetDevice = d;
                    break;
                }
            }
            // 2. Device with Bulk OUT endpoint that is NOT a scanner / keyboard
            if (targetDevice == null) {
                for (UsbDevice d : deviceList.values()) {
                    if (isPrinterCandidate(d)) {
                        targetDevice = d;
                        break;
                    }
                }
            }
        }

        if (targetDevice == null) {
            call.reject("Tidak ada printer USB / Eppos yang terdeteksi di USB Hub. Pastikan kabel OTG / USB Hub terpasang kencang dan printer menyala.");
            return;
        }

        if (!usbManager.hasPermission(targetDevice)) {
            pendingPermissionCall = call;
            pendingDevice = targetDevice;

            int flags = PendingIntent.FLAG_UPDATE_CURRENT;
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
                flags |= PendingIntent.FLAG_MUTABLE;
            }

            Intent intent = new Intent(ACTION_USB_PERMISSION);
            intent.setPackage(getContext().getPackageName());
            PendingIntent permissionIntent = PendingIntent.getBroadcast(getContext(), 0, intent, flags);
            usbManager.requestPermission(targetDevice, permissionIntent);
            return;
        }

        tryOpenDevice(targetDevice, call);
    }

    private void tryOpenDevice(UsbDevice device, PluginCall call) {
        try {
            closeConnection();

            PrinterEndpointInfo endpointInfo = findPrinterEndpoints(device);
            if (endpointInfo == null || endpointInfo.bulkOut == null) {
                if (call != null) call.reject("Perangkat USB tidak memiliki endpoint transfer bulk data pencetak.");
                return;
            }

            UsbDeviceConnection conn = usbManager.openDevice(device);
            if (conn == null) {
                if (call != null) call.reject("Gagal membuka koneksi USB. Pastikan kabel OTG / USB Hub terhubung dengan baik.");
                return;
            }

            boolean claimed = conn.claimInterface(endpointInfo.usbInterface, true);
            if (!claimed) {
                conn.close();
                if (call != null) call.reject("Gagal mengklaim interface printer USB.");
                return;
            }

            connectedDevice = device;
            connection = conn;
            printerInterface = endpointInfo.usbInterface;
            bulkOutEndpoint = endpointInfo.bulkOut;

            String name = getDeviceDisplayName(device);
            Log.i(TAG, "Berhasil terhubung ke printer USB: " + name + " (VID: " + device.getVendorId() + ")");

            JSObject ret = getStatusJson();
            notifyListeners("usbPrinterStateChange", ret);

            if (call != null) {
                call.resolve(ret);
                pendingPermissionCall = null;
            }
        } catch (Exception e) {
            closeConnection();
            Log.e(TAG, "Error opening USB printer", e);
            if (call != null) {
                call.reject("Gagal menghubungkan printer USB: " + e.getMessage());
                pendingPermissionCall = null;
            }
        }
    }

    /**
     * Send raw ESC/POS bytes over USB Bulk OUT endpoint with chunking
     */
    @PluginMethod
    public void write(PluginCall call) {
        String base64Value = call.getString("value");
        if (base64Value == null) {
            call.reject("Data cetak kosong.");
            return;
        }

        if (connection == null || bulkOutEndpoint == null || connectedDevice == null) {
            // Attempt auto-reconnection
            HashMap<String, UsbDevice> deviceList = usbManager.getDeviceList();
            UsbDevice candidate = null;
            for (UsbDevice d : deviceList.values()) {
                if (isPrinterCandidate(d) && usbManager.hasPermission(d)) {
                    candidate = d;
                    break;
                }
            }

            if (candidate != null) {
                tryOpenDevice(candidate, null);
            }

            if (connection == null || bulkOutEndpoint == null) {
                call.reject("Printer USB tidak terhubung. Silakan buka Pengaturan Perangkat untuk menyambungkan printer.");
                return;
            }
        }

        try {
            byte[] bytes = Base64.decode(base64Value, Base64.DEFAULT);
            int offset = 0;
            int total = bytes.length;
            int timeout = 5000;

            // Chunk transfer to prevent buffer overflow on thermal printer chip
            while (offset < total) {
                int chunkSize = Math.min(1024, total - offset);
                byte[] chunk = new byte[chunkSize];
                System.arraycopy(bytes, offset, chunk, 0, chunkSize);
                int transferred = connection.bulkTransfer(bulkOutEndpoint, chunk, chunkSize, timeout);
                if (transferred < 0) {
                    call.reject("Gagal mengirim data ke printer USB (transferred: " + transferred + " di offset " + offset + ")");
                    return;
                }
                offset += transferred;
            }

            JSObject ret = new JSObject();
            ret.put("success", true);
            ret.put("transferred", total);
            call.resolve(ret);
        } catch (Exception e) {
            closeConnection();
            call.reject("Gagal mencetak ke printer USB: " + e.getMessage());
        }
    }

    @PluginMethod
    public void disconnect(PluginCall call) {
        closeConnection();
        JSObject ret = getStatusJson();
        notifyListeners("usbPrinterStateChange", ret);
        call.resolve(ret);
    }

    @PluginMethod
    public void getStatus(PluginCall call) {
        call.resolve(getStatusJson());
    }

    private JSObject getStatusJson() {
        boolean isConn = connection != null && bulkOutEndpoint != null && connectedDevice != null;
        JSObject ret = new JSObject();
        ret.put("isConnected", isConn);
        ret.put("name", isConn ? getDeviceDisplayName(connectedDevice) : null);
        ret.put("deviceId", isConn ? connectedDevice.getDeviceId() : null);
        ret.put("vendorId", isConn ? String.format("0x%04X", connectedDevice.getVendorId()) : null);
        ret.put("productId", isConn ? String.format("0x%04X", connectedDevice.getProductId()) : null);
        return ret;
    }

    private PrinterEndpointInfo findPrinterEndpoints(UsbDevice device) {
        for (int i = 0; i < device.getInterfaceCount(); i++) {
            UsbInterface uif = device.getInterface(i);
            UsbEndpoint outEp = null;

            for (int j = 0; j < uif.getEndpointCount(); j++) {
                UsbEndpoint ep = uif.getEndpoint(j);
                if (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK && ep.getDirection() == UsbConstants.USB_DIR_OUT) {
                    outEp = ep;
                    break;
                }
            }

            if (outEp != null) {
                return new PrinterEndpointInfo(uif, outEp);
            }
        }
        return null;
    }

    private boolean hasPrinterClass(UsbDevice device) {
        if (device.getDeviceClass() == UsbConstants.USB_CLASS_PRINTER) {
            return true;
        }
        for (int i = 0; i < device.getInterfaceCount(); i++) {
            if (device.getInterface(i).getInterfaceClass() == UsbConstants.USB_CLASS_PRINTER) {
                return true;
            }
        }
        return false;
    }

    private boolean isPrinterCandidate(UsbDevice device) {
        if (hasPrinterClass(device)) {
            return true;
        }

        // Exclude Barcode Scanners / HID keyboards
        if (isKeyboardScanner(device)) {
            return false;
        }

        // Exclude USB Hubs (Class 9) and Mass Storage (Class 8)
        if (device.getDeviceClass() == UsbConstants.USB_CLASS_HUB || device.getDeviceClass() == UsbConstants.USB_CLASS_MASS_STORAGE) {
            return false;
        }

        // Check if device has a Bulk OUT endpoint
        return findPrinterEndpoints(device) != null;
    }

    private boolean isKeyboardScanner(UsbDevice device) {
        if (device.getDeviceClass() == UsbConstants.USB_CLASS_HID) {
            return true;
        }
        for (int i = 0; i < device.getInterfaceCount(); i++) {
            if (device.getInterface(i).getInterfaceClass() == UsbConstants.USB_CLASS_HID) {
                return true;
            }
        }
        return false;
    }

    private String getDeviceDisplayName(UsbDevice device) {
        String name = null;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            try {
                name = device.getProductName();
            } catch (Exception ignored) {}
        }

        if (name == null || name.trim().isEmpty() || "null".equalsIgnoreCase(name)) {
            try {
                name = device.getDeviceName();
            } catch (Exception ignored) {}
        }

        String hexVid = String.format("0x%04X", device.getVendorId());
        String hexPid = String.format("0x%04X", device.getProductId());

        if (hasPrinterClass(device) || isPrinterCandidate(device)) {
            if (name == null || name.startsWith("/dev/")) {
                name = "Printer Thermal Eppos / POS (" + hexVid + ":" + hexPid + ")";
            }
        } else if (isKeyboardScanner(device)) {
            if (name == null || name.startsWith("/dev/")) {
                name = "Scanner Barcode USB (HID)";
            }
        } else {
            if (name == null || name.startsWith("/dev/")) {
                name = "Perangkat USB (" + hexVid + ":" + hexPid + ")";
            }
        }

        return name;
    }

    private void closeConnection() {
        if (connection != null) {
            try {
                if (printerInterface != null) {
                    connection.releaseInterface(printerInterface);
                }
                connection.close();
            } catch (Exception ignored) {}
            connection = null;
        }
        printerInterface = null;
        bulkOutEndpoint = null;
        connectedDevice = null;
    }

    @Override
    protected void handleOnDestroy() {
        try {
            getContext().unregisterReceiver(usbReceiver);
        } catch (Exception ignored) {}
        closeConnection();
        super.handleOnDestroy();
    }
}
