package com.fivemileslab;

import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
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.util.Base64;
import android.util.Log;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.HashMap;
import java.util.Iterator;

public class USBPrinterHelper {
    private static final String TAG = "USBPrinter";
    private static final String ACTION_USB_PERMISSION = "com.fivemileslab.USB_PRINTER_PERMISSION";

    private Context context;
    private UsbManager usbManager;
    private PendingIntent permissionIntent;
    private BroadcastReceiver usbReceiver;
    private UsbDeviceConnection connection;
    private UsbEndpoint outEndpoint;
    private UsbEndpoint inEndpoint;
    private UsbDevice connectedDevice;

    private static final int DNP_VENDOR_ID = 4931;
    private static final int DNP_PRODUCT_ID = 5;

    public USBPrinterHelper(Context context) {
        this.context = context;
        this.usbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
    }

    public interface PermissionCallback {
        void onSuccess();
        void onError(String message);
    }

    public interface PrintCallback {
        void onSuccess(String message);
        void onError(String message);
    }

    public void requestPermission(final PermissionCallback callback) {
        HashMap<String, UsbDevice> deviceList = usbManager.getDeviceList();

        if (deviceList.isEmpty()) {
            callback.onError("No USB devices found. Please connect the printer.");
            return;
        }

        UsbDevice targetDevice = null;
        Iterator<UsbDevice> iterator = deviceList.values().iterator();

        while (iterator.hasNext()) {
            UsbDevice device = iterator.next();
            Log.d(TAG, "Found USB device: vendorId=" + device.getVendorId()
                  + " productId=" + device.getProductId()
                  + " deviceName=" + device.getDeviceName());

            if (device.getVendorId() == DNP_VENDOR_ID && device.getProductId() == DNP_PRODUCT_ID) {
                targetDevice = device;
                Log.d(TAG, "Found DNP printer: " + device.getDeviceName());
                break;
            }
        }

        if (targetDevice == null) {
            Log.d(TAG, "No DNP printer found, searching for printer class devices...");
            iterator = deviceList.values().iterator();
            while (iterator.hasNext()) {
                UsbDevice device = iterator.next();
                if (device.getDeviceClass() == 7) {
                    targetDevice = device;
                    break;
                }
            }
        }

        if (targetDevice == null) {
            iterator = deviceList.values().iterator();
            if (iterator.hasNext()) {
                targetDevice = iterator.next();
            }
        }

        if (targetDevice == null) {
            callback.onError("No suitable USB device found.");
            return;
        }

        final UsbDevice device = targetDevice;

        if (usbManager.hasPermission(device)) {
            openDevice(device, callback);
            return;
        }

        permissionIntent = PendingIntent.getBroadcast(context, 0,
            new Intent(ACTION_USB_PERMISSION), PendingIntent.FLAG_IMMUTABLE);

        usbReceiver = new BroadcastReceiver() {
            public void onReceive(Context context, Intent intent) {
                String action = intent.getAction();
                if (ACTION_USB_PERMISSION.equals(action)) {
                    synchronized (this) {
                        UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
                        if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
                            if (device != null) {
                                openDevice(device, callback);
                            }
                        } else {
                            callback.onError("USB permission denied by user.");
                        }
                    }
                }
            }
        };

        context.registerReceiver(usbReceiver, new IntentFilter(ACTION_USB_PERMISSION));
        usbManager.requestPermission(device, permissionIntent);
    }

    private void openDevice(UsbDevice device, PermissionCallback callback) {
        connectedDevice = device;

        for (int i = 0; i < device.getInterfaceCount(); i++) {
            UsbInterface usbInterface = device.getInterface(i);
            Log.d(TAG, "Interface " + i + ": " + usbInterface.toString());

            connection = usbManager.openDevice(device);
            if (connection == null) {
                callback.onError("Failed to open USB connection.");
                return;
            }

            if (!connection.claimInterface(usbInterface, true)) {
                callback.onError("Failed to claim USB interface.");
                connection.close();
                connection = null;
                return;
            }

            for (int j = 0; j < usbInterface.getEndpointCount(); j++) {
                UsbEndpoint endpoint = usbInterface.getEndpoint(j);
                if (endpoint.getType() == 2) { // USB_ENDPOINT_XFER_BULK = 2
                    if (endpoint.getDirection() == 0) { // USB_ENDPOINT_DIR_OUT = 0
                        outEndpoint = endpoint;
                        Log.d(TAG, "Found OUT endpoint: " + j);
                    } else if (endpoint.getDirection() == 128) { // USB_ENDPOINT_DIR_IN = 128 (0x80)
                        inEndpoint = endpoint;
                        Log.d(TAG, "Found IN endpoint: " + j);
                    }
                }
            }

            if (outEndpoint != null) {
                break;
            }
        }

        if (outEndpoint == null) {
            if (connection != null) {
                connection.close();
                connection = null;
            }
            callback.onError("No suitable USB endpoints found.");
            return;
        }

        Log.d(TAG, "USB device opened successfully");
        callback.onSuccess();
    }

    public void printImage(final String imageData, final PrintCallback callback) {
        if (connection == null || outEndpoint == null) {
            callback.onError("USB printer not connected. Please connect the printer first.");
            return;
        }

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    byte[] imageBytes;

                    if (imageData.startsWith("data:")) {
                        String base64Data = imageData.substring(imageData.indexOf(",") + 1);
                        imageBytes = Base64.decode(base64Data, Base64.DEFAULT);
                    } else if (imageData.startsWith("http://") || imageData.startsWith("https://")) {
                        imageBytes = downloadImage(imageData);
                    } else {
                        imageBytes = Base64.decode(imageData, Base64.DEFAULT);
                    }

                    if (imageBytes == null || imageBytes.length == 0) {
                        callback.onError("Failed to decode image data.");
                        return;
                    }

                    Log.d(TAG, "Image decoded, size: " + imageBytes.length + " bytes");

                    int chunkSize = 8192;
                    int offset = 0;
                    int totalSent = 0;

                    while (offset < imageBytes.length) {
                        int remaining = imageBytes.length - offset;
                        int size = Math.min(chunkSize, remaining);

                        byte[] chunk = new byte[size];
                        System.arraycopy(imageBytes, offset, chunk, 0, size);

                        int transferred = connection.bulkTransfer(outEndpoint, chunk, chunk.length, 10000);
                        if (transferred < 0) {
                            callback.onError("USB write failed at offset " + offset);
                            return;
                        }

                        totalSent += transferred;
                        offset += size;
                    }

                    Log.d(TAG, "Image sent successfully. Total: " + totalSent + " bytes");

                    if (inEndpoint != null) {
                        byte[] responseBuffer = new byte[64];
                        int response = connection.bulkTransfer(inEndpoint, responseBuffer, responseBuffer.length, 5000);
                        if (response > 0) {
                            String responseStr = new String(responseBuffer, 0, response);
                            Log.d(TAG, "Printer response: " + responseStr);
                        }
                    }

                    callback.onSuccess("Print job sent to USB printer successfully. Total: " + totalSent + " bytes");

                } catch (Exception e) {
                    Log.e(TAG, "Print error: " + e.getMessage(), e);
                    callback.onError("Print failed: " + e.getMessage());
                }
            }
        }).start();
    }

    private byte[] downloadImage(String imageUrl) throws IOException {
        URL url = new URL(imageUrl);
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        connection.setRequestMethod("GET");
        connection.setConnectTimeout(10000);
        connection.setReadTimeout(10000);
        connection.connect();

        InputStream inputStream = connection.getInputStream();
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

        byte[] buffer = new byte[4096];
        int bytesRead;
        while ((bytesRead = inputStream.read(buffer)) != -1) {
            outputStream.write(buffer, 0, bytesRead);
        }

        inputStream.close();
        outputStream.close();
        connection.disconnect();

        return outputStream.toByteArray();
    }

    public void close() {
        if (connection != null) {
            connection.close();
            connection = null;
        }
        if (usbReceiver != null) {
            try {
                context.unregisterReceiver(usbReceiver);
            } catch (Exception e) {
            }
            usbReceiver = null;
        }
        connectedDevice = null;
        outEndpoint = null;
        inEndpoint = null;
    }
}