package fr.pizzexpress.landitest;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.os.IBinder;
import android.os.Parcel;
import android.os.RemoteException;

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

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;

final class LandiPrinterManager {
    private static final String SERVICE_NAME = "landi.printer";
    private static final String[] SERVICE_CANDIDATES = {
        "landi.printer",
        "printer",
        "rockos.printer",
        "android.printer",
        "btprinter",
        "BTPrinter"
    };
    private static final int DEVICE_58MM = 1;
    private static final int ALIGN_LEFT = 0;
    private static final int ALIGN_CENTER = 1;
    private static final int ALIGN_RIGHT = 2;

    private static final int TX_OPEN_DEVICE = 1;
    private static final int TX_CLOSE_DEVICE = 4;
    private static final int TX_INIT_PRINTER = 5;
    private static final int TX_SET_FORMAT = 6;
    private static final int TX_FEED_LINE = 8;
    private static final int TX_STATUS = 10;
    private static final int TX_PRINT_TEXT = 12;
    private static final int TX_CONTROL = 15;
    private static final int TX_QR_CODE = 32;
    private static final int TX_MONOCHROME_BMP = 33;

    private static final int CONTROL_START_PRINT = 7;
    private static final int FORMAT_ASCII_PRINT_TYPE = 512;
    private static final int FORMAT_HZ_PRINT_TYPE = 1024;
    private static final int FORMAT_BOLD_FONT = 85;
    private static final int ASCII_SCALE_1X1 = 48;
    private static final int ASCII_SCALE_1X2 = 49;
    private static final int ASCII_SCALE_2X2 = 52;
    private static final int HZ_SCALE_1X1 = 32;
    private static final int HZ_SCALE_1X2 = 33;
    private static final int HZ_SCALE_2X2 = 36;

    private IBinder printerBinder;
    private int deviceHandle = -1;
    private final Context context;

    LandiPrinterManager(Context context) {
        this.context = context.getApplicationContext();
    }

    boolean isAvailable() {
        try {
            return getPrinterBinder() != null;
        } catch (Exception ignored) {
            return false;
        }
    }

    void printText(String text) throws PrinterException {
        ensureReady();
        normal();
        printAligned(ALIGN_LEFT, text);
        feedPaper();
        startPrint();
    }

    void printDemoTicket() throws PrinterException {
        ensureReady();
        centerLarge();
        printAligned(ALIGN_CENTER, "PIZZ'EXPRESS\n");
        normal();
        printAligned(ALIGN_CENTER, "TEST LANDI M20\n");
        printSeparator();
        centerLarge();
        printAligned(ALIGN_CENTER, "A EMPORTER\n");
        normal();
        printAligned(ALIGN_CENTER, "#TEST01\n");
        printSeparator();
        printAligned(ALIGN_LEFT, leftRight("1x Reine", "11,20 EUR"));
        printAligned(ALIGN_LEFT, leftRight("2x Welsh", "23,40 EUR"));
        printAligned(ALIGN_CENTER, "\nTotal Produits : 3\n");
        printSeparator();
        centerTall();
        printAligned(ALIGN_LEFT, leftRight("Total", "34,60 EUR"));
        normal();
        feedPaper();
        startPrint();
    }

    void printDemoImageTicket() throws PrinterException {
        ensureReady();
        Bitmap bitmap = renderDemoTicketBitmap();
        byte[] bmp = convert1BitBmp(bitmap);
        printMonochromeBmp(bmp);
        feedPaper();
        startPrint();
    }

    byte[] buildEscPosRasterDemoTicket() {
        return bitmapToEscPosRaster(renderDemoTicketBitmap());
    }

    byte[] buildEscPosRasterTicket(String jsonOrder) throws PrinterException {
        try {
            return bitmapToEscPosRaster(renderOrderTicketBitmap(new JSONObject(jsonOrder)));
        } catch (JSONException exception) {
            throw new PrinterException("JSON commande invalide", exception);
        }
    }

    byte[] buildEscPosRasterFromBitmap(Bitmap bitmap) {
        return bitmapToEscPosRaster(bitmap);
    }

    void printQrCode(String value) throws PrinterException {
        ensureReady();
        Parcel data = Parcel.obtain();
        Parcel reply = Parcel.obtain();
        try {
            data.writeInt(deviceHandle);
            writeGbkString(data, value);
            data.writeInt(6);
            writeGbkString(data, "");
            transact(TX_QR_CODE, data, reply);
            checkCode(reply.readInt(), "QR code");
            startPrint();
        } finally {
            data.recycle();
            reply.recycle();
        }
    }

    void feedPaper() throws PrinterException {
        ensureOpen();
        Parcel data = Parcel.obtain();
        Parcel reply = Parcel.obtain();
        try {
            data.writeInt(deviceHandle);
            data.writeInt(0);
            data.writeInt(4);
            transact(TX_FEED_LINE, data, reply);
            checkCode(reply.readInt(), "avance papier");
        } finally {
            data.recycle();
            reply.recycle();
        }
    }

    int queryStatus() throws PrinterException {
        ensureOpen();
        Parcel data = Parcel.obtain();
        Parcel reply = Parcel.obtain();
        try {
            data.writeInt(deviceHandle);
            transact(TX_STATUS, data, reply);
            return reply.readInt();
        } finally {
            data.recycle();
            reply.recycle();
        }
    }

    void printTicket(String jsonOrder) throws PrinterException {
        ensureReady();
        try {
            JSONObject order = new JSONObject(jsonOrder);
            Bitmap bitmap = renderOrderTicketBitmap(order);
            byte[] bmp = convert1BitBmp(bitmap);
            printMonochromeBmp(bmp);
            feedPaper();
            startPrint();
        } catch (JSONException exception) {
            throw new PrinterException("JSON commande invalide", exception);
        }
    }

    void close() {
        if (deviceHandle < 0 || printerBinder == null) {
            return;
        }
        Parcel data = Parcel.obtain();
        Parcel reply = Parcel.obtain();
        try {
            data.writeInt(deviceHandle);
            printerBinder.transact(TX_CLOSE_DEVICE, data, reply, 0);
        } catch (Exception ignored) {
        } finally {
            data.recycle();
            reply.recycle();
            deviceHandle = -1;
        }
    }

    private void ensureReady() throws PrinterException {
        ensureOpen();
        initPrinter();
    }

    private void ensureOpen() throws PrinterException {
        if (deviceHandle >= 0 && printerBinder != null) {
            return;
        }

        Parcel data = Parcel.obtain();
        Parcel reply = Parcel.obtain();
        try {
            data.writeInt(DEVICE_58MM);
            transact(TX_OPEN_DEVICE, data, reply);
            deviceHandle = reply.readInt();
            checkCode(reply.readInt(), "ouverture imprimante");
        } finally {
            data.recycle();
            reply.recycle();
        }
    }

    private void initPrinter() throws PrinterException {
        Parcel data = Parcel.obtain();
        Parcel reply = Parcel.obtain();
        try {
            data.writeInt(deviceHandle);
            transact(TX_INIT_PRINTER, data, reply);
            checkCode(reply.readInt(), "initialisation imprimante");
        } finally {
            data.recycle();
            reply.recycle();
        }
    }

    private void centerLarge() {
        setFormatQuietly(FORMAT_ASCII_PRINT_TYPE, ASCII_SCALE_2X2);
        setFormatQuietly(FORMAT_HZ_PRINT_TYPE, HZ_SCALE_2X2);
        setFormatQuietly(FORMAT_BOLD_FONT, 1);
    }

    private void centerTall() {
        setFormatQuietly(FORMAT_ASCII_PRINT_TYPE, ASCII_SCALE_1X2);
        setFormatQuietly(FORMAT_HZ_PRINT_TYPE, HZ_SCALE_1X2);
        setFormatQuietly(FORMAT_BOLD_FONT, 1);
    }

    private void normal() {
        setFormatQuietly(FORMAT_ASCII_PRINT_TYPE, ASCII_SCALE_1X1);
        setFormatQuietly(FORMAT_HZ_PRINT_TYPE, HZ_SCALE_1X1);
        setFormatQuietly(FORMAT_BOLD_FONT, 0);
    }

    private void setFormatQuietly(int type, int value) {
        Parcel data = Parcel.obtain();
        Parcel reply = Parcel.obtain();
        try {
            data.writeInt(deviceHandle);
            data.writeInt(type);
            data.writeInt(value);
            transact(TX_SET_FORMAT, data, reply);
            reply.readInt();
        } catch (PrinterException ignored) {
        } finally {
            data.recycle();
            reply.recycle();
        }
    }

    private void printAligned(int alignment, String text) throws PrinterException {
        Parcel data = Parcel.obtain();
        Parcel reply = Parcel.obtain();
        try {
            data.writeInt(deviceHandle);
            data.writeInt(alignment);
            writeGbkString(data, text == null ? "" : text);
            transact(TX_PRINT_TEXT, data, reply);
            checkCode(reply.readInt(), "impression texte");
        } finally {
            data.recycle();
            reply.recycle();
        }
    }

    private void printMonochromeBmp(byte[] bmpData) throws PrinterException {
        Parcel data = Parcel.obtain();
        Parcel reply = Parcel.obtain();
        try {
            data.writeInt(deviceHandle);
            data.writeInt(0);
            data.writeInt(0);
            data.writeInt(1);
            data.writeByteArray(bmpData);
            transact(TX_MONOCHROME_BMP, data, reply);
            checkCode(reply.readInt(), "impression image");
        } finally {
            data.recycle();
            reply.recycle();
        }
    }

    private void printSeparator() throws PrinterException {
        printAligned(ALIGN_LEFT, "--------------------------------\n");
    }

    private void startPrint() throws PrinterException {
        Parcel data = Parcel.obtain();
        Parcel reply = Parcel.obtain();
        try {
            data.writeInt(deviceHandle);
            data.writeInt(CONTROL_START_PRINT);
            transact(TX_CONTROL, data, reply);
            checkCode(reply.readInt(), "demarrage impression");
        } finally {
            data.recycle();
            reply.recycle();
        }
    }

    private void transact(int code, Parcel data, Parcel reply) throws PrinterException {
        try {
            IBinder binder = getPrinterBinder();
            if (binder == null) {
                throw new PrinterException("Service landi.printer visible mais binder inaccessible. " + serviceLookupDiagnostic());
            }
            binder.transact(code, data, reply, 0);
        } catch (RemoteException exception) {
            printerBinder = null;
            throw new PrinterException("Erreur Binder LANDI", exception);
        } catch (ReflectiveOperationException exception) {
            throw new PrinterException("ServiceManager inaccessible", exception);
        }
    }

    private IBinder getPrinterBinder() throws ReflectiveOperationException {
        if (printerBinder != null && printerBinder.isBinderAlive()) {
            return printerBinder;
        }

        Class<?> serviceManager = Class.forName("android.os.ServiceManager");

        for (String serviceName : SERVICE_CANDIDATES) {
            IBinder binder = lookupService(serviceManager, "getService", serviceName);
            if (binder != null) {
                printerBinder = binder;
                return printerBinder;
            }
        }

        for (String serviceName : SERVICE_CANDIDATES) {
            IBinder binder = lookupService(serviceManager, "checkService", serviceName);
            if (binder != null) {
                printerBinder = binder;
                return printerBinder;
            }
        }

        for (String serviceName : SERVICE_CANDIDATES) {
            IBinder binder = lookupService(serviceManager, "waitForService", serviceName);
            if (binder != null) {
                printerBinder = binder;
                return printerBinder;
            }
        }

        return null;
    }

    private IBinder lookupService(Class<?> serviceManager, String methodName, String serviceName) {
        try {
            Object service = serviceManager
                .getMethod(methodName, String.class)
                .invoke(null, serviceName);
            return service instanceof IBinder ? (IBinder) service : null;
        } catch (Exception ignored) {
            return null;
        }
    }

    String serviceLookupDiagnostic() {
        StringBuilder builder = new StringBuilder();
        try {
            Class<?> serviceManager = Class.forName("android.os.ServiceManager");
            for (String serviceName : SERVICE_CANDIDATES) {
                appendLookup(builder, serviceManager, "getService", serviceName);
                appendLookup(builder, serviceManager, "checkService", serviceName);
            }
        } catch (Exception exception) {
            builder.append("ServiceManager diagnostic erreur: ")
                .append(exception.getClass().getSimpleName())
                .append(' ')
                .append(exception.getMessage());
        }
        return builder.toString();
    }

    private void appendLookup(StringBuilder builder, Class<?> serviceManager, String methodName, String serviceName) {
        if (builder.length() > 0) {
            builder.append(" | ");
        }

        builder.append(methodName).append('(').append(serviceName).append(")=");
        try {
            Object service = serviceManager
                .getMethod(methodName, String.class)
                .invoke(null, serviceName);
            if (service == null) {
                builder.append("null");
            } else {
                builder.append(service.getClass().getName());
            }
        } catch (Exception exception) {
            Throwable cause = exception.getCause() == null ? exception : exception.getCause();
            builder.append("ERR ")
                .append(cause.getClass().getSimpleName())
                .append(' ')
                .append(cause.getMessage());
        }
    }

    String visiblePrinterServices() {
        try {
            Class<?> serviceManager = Class.forName("android.os.ServiceManager");
            Object result = serviceManager.getMethod("listServices").invoke(null);
            if (!(result instanceof String[])) {
                return "listServices indisponible";
            }

            StringBuilder builder = new StringBuilder();
            for (String service : (String[]) result) {
                String lower = service.toLowerCase();
                if (lower.contains("print") || lower.contains("landi") || lower.contains("bt") || lower.contains("rockos")) {
                    if (builder.length() > 0) {
                        builder.append(", ");
                    }
                    builder.append(service);
                }
            }
            return builder.length() == 0 ? "aucun service printer visible" : builder.toString();
        } catch (Exception exception) {
            return "diagnostic services impossible: " + exception.getClass().getSimpleName() + " " + exception.getMessage();
        }
    }

    private void writeGbkString(Parcel parcel, String value) throws PrinterException {
        try {
            parcel.writeByteArray((value + "\0").getBytes("GBK"));
        } catch (UnsupportedEncodingException exception) {
            throw new PrinterException("Encodage GBK indisponible", exception);
        }
    }

    private void checkCode(int code, String action) throws PrinterException {
        if (code == 0 || code == 233) {
            return;
        }
        throw new PrinterException(action + " echouee: " + describeError(code) + " (" + code + ")");
    }

    private String describeError(int code) {
        switch (code) {
            case 176:
                return "couteau bloque";
            case 177:
                return "capot ouvert";
            case 184:
                return "imprimante absente";
            case 229:
                return "communication impossible";
            case 238:
                return "bourrage papier";
            case 240:
                return "papier manquant";
            case 243:
                return "surchauffe";
            case 244:
                return "papier presque vide";
            case 247:
                return "imprimante occupee";
            default:
                return "erreur " + code;
        }
    }

    private String readString(JSONObject object, String... keys) {
        for (String key : keys) {
            String value = object.optString(key, "");
            if (!value.trim().isEmpty()) {
                return value.trim();
            }
        }
        return "";
    }

    private String safe(String value, String fallback) {
        return value == null || value.trim().isEmpty() ? fallback : value.trim();
    }

    private String leftRight(String left, String right) {
        String safeLeft = safe(left, "");
        String safeRight = safe(right, "");
        if (safeRight.isEmpty()) {
            return safeLeft + "\n";
        }
        int width = 32;
        int spaces = Math.max(1, width - safeLeft.length() - safeRight.length());
        StringBuilder line = new StringBuilder(safeLeft);
        for (int i = 0; i < spaces; i++) {
            line.append(' ');
        }
        line.append(safeRight).append('\n');
        return line.toString();
    }

    private Bitmap renderDemoTicketBitmap() {
        int width = 384;
        int height = 760;
        Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        canvas.drawColor(Color.WHITE);

        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setColor(Color.BLACK);
        paint.setTextAlign(Paint.Align.CENTER);
        paint.setTypeface(Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD));

        int y = 28;
        drawBlackBar(canvas, paint, "PAIEMENT SUR PLACE", y, 48, 25);
        y += 80;

        paint.setTextSize(31);
        canvas.drawText("A EMPORTER", width / 2f, y, paint);
        y += 42;

        drawRoundedBadge(canvas, paint, "#TEST01", y);
        y += 44;

        paint.setTextSize(23);
        canvas.drawText("CLIENT TEST", width / 2f, y, paint);
        y += 28;
        paint.setTextSize(15);
        canvas.drawText("1ere commande", width / 2f, y, paint);
        y += 48;

        drawBlackBar(canvas, paint, "PRECOMMANDE POUR LE", y, 74, 20);
        paint.setTextSize(23);
        paint.setColor(Color.WHITE);
        canvas.drawText("Ven. 03/07 a 19:30", width / 2f, y + 52, paint);
        paint.setColor(Color.BLACK);
        y += 108;

        paint.setTextAlign(Paint.Align.LEFT);
        paint.setTypeface(Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD));
        paint.setTextSize(18);
        canvas.drawText("** PIZZAS BASE TOMATE (3)", 8, y, paint);
        y += 38;

        y = drawItem(canvas, paint, y, "1x Burger", "11,70 EUR");
        y = drawItem(canvas, paint, y, "1x Chevre", "9,50 EUR");
        y = drawItem(canvas, paint, y, "1x Bolognaise", "10,20 EUR");

        y += 16;
        paint.setTextAlign(Paint.Align.CENTER);
        paint.setTextSize(20);
        canvas.drawText("Total Produits : 3", width / 2f, y, paint);
        y += 34;

        paint.setStrokeWidth(4);
        canvas.drawLine(8, y, width - 8, y, paint);
        y += 40;
        paint.setTextAlign(Paint.Align.LEFT);
        paint.setTextSize(21);
        canvas.drawText("Sous-total", 8, y, paint);
        paint.setTextAlign(Paint.Align.RIGHT);
        canvas.drawText("31,40 EUR", width - 8, y, paint);
        y += 38;
        paint.setTextAlign(Paint.Align.LEFT);
        canvas.drawText("Total a encaisser", 8, y, paint);
        paint.setTextAlign(Paint.Align.RIGHT);
        canvas.drawText("31,40 EUR", width - 8, y, paint);
        y += 32;
        canvas.drawLine(8, y, width - 8, y, paint);
        y += 42;

        paint.setTextAlign(Paint.Align.LEFT);
        paint.setTextSize(17);
        canvas.drawText("CLIENT TEST", 8, y, paint);
        y += 25;
        paint.setTypeface(Typeface.create(Typeface.SANS_SERIF, Typeface.NORMAL));
        paint.setTextSize(16);
        canvas.drawText("Tel: +33600000000", 8, y, paint);
        y += 34;
        paint.setStrokeWidth(4);
        canvas.drawLine(8, y, width - 8, y, paint);
        y += 45;

        paint.setTextAlign(Paint.Align.CENTER);
        paint.setTextSize(15);
        canvas.drawText("Commande passee le", width / 2f, y, paint);
        y += 20;
        canvas.drawText("03/07/2026 a 15:55", width / 2f, y, paint);

        return bitmap;
    }

    private Bitmap renderOrderTicketBitmap(JSONObject order) {
        int width = 384;
        JSONArray categories = order.optJSONArray("categories");
        int itemCount = 0;
        int extraLines = 0;

        if (categories != null) {
            for (int categoryIndex = 0; categoryIndex < categories.length(); categoryIndex++) {
                JSONObject category = categories.optJSONObject(categoryIndex);
                JSONArray items = category == null ? null : category.optJSONArray("items");
                if (items == null) {
                    continue;
                }
                itemCount += items.length();
                for (int itemIndex = 0; itemIndex < items.length(); itemIndex++) {
                    JSONObject item = items.optJSONObject(itemIndex);
                    if (item == null) {
                        continue;
                    }
                    if (!item.optString("option_summary", "").trim().isEmpty()) {
                        extraLines++;
                    }
                    if (!item.optString("item_notes", "").trim().isEmpty()) {
                        extraLines++;
                    }
                }
            }
        }

        boolean hasCustomerNotes = !order.optString("customer_notes", "").trim().isEmpty();
        int height = 1080 + (categories == null ? 0 : categories.length() * 58) + itemCount * 96 + extraLines * 52 + (hasCustomerNotes ? 120 : 0);
        height = Math.max(1280, height);

        Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        canvas.drawColor(Color.WHITE);

        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setSubpixelText(false);
        paint.setFakeBoldText(false);
        paint.setColor(Color.BLACK);
        paint.setTextAlign(Paint.Align.CENTER);
        paint.setTypeface(Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD));

        int y = 18;
        if (order.optBoolean("payment_on_site", false)) {
            drawBlackBar(canvas, paint, "PAIEMENT SUR PLACE", y, 50, 25);
            y += 92;
        }

        paint.setColor(Color.BLACK);
        paint.setTextAlign(Paint.Align.CENTER);
        paint.setTypeface(Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD));
        paint.setTextSize(39);
        canvas.drawText("A EMPORTER", width / 2f, y, paint);
        y += 50;

        drawRoundedBadge(canvas, paint, order.optString("reference", "#------"), y);
        y += 54;

        paint.setTextSize(29);
        canvas.drawText(order.optString("customer_name", ""), width / 2f, y, paint);
        y += 34;
        paint.setTextSize(19);
        canvas.drawText(order.optString("order_count", ""), width / 2f, y, paint);
        y += 38;

        String readyLabel = order.optString("ready_label", "").trim();
        if (!readyLabel.isEmpty()) {
            drawFullRule(canvas, paint, y);
            y += 30;

            paint.setTextAlign(Paint.Align.CENTER);
            paint.setTypeface(Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD));
            paint.setTextSize(16);
            canvas.drawText("INDIQUEE PRETE LE", width / 2f, y, paint);
            y += 26;
            paint.setTextSize(23);
            canvas.drawText(cleanTicketText(readyLabel), width / 2f, y, paint);
            y += 28;
            drawFullRule(canvas, paint, y);
            y += 36;
        }

        drawBlackBarTwoLines(canvas, paint, "PRECOMMANDE POUR LE", cleanTicketText(order.optString("pickup_label", "")), y, 78);
        y += 116;

        if (categories != null) {
            for (int categoryIndex = 0; categoryIndex < categories.length(); categoryIndex++) {
                JSONObject category = categories.optJSONObject(categoryIndex);
                if (category == null) {
                    continue;
                }
                JSONArray items = category.optJSONArray("items");
                paint.setTextAlign(Paint.Align.LEFT);
                paint.setTypeface(Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD));
                paint.setTextSize(21);
                String categoryTitle = "** " + category.optString("name", "COMMANDE") + " (" + category.optInt("count", items == null ? 0 : items.length()) + ")";
                canvas.drawText(categoryTitle, 8, y, paint);
                y += 36;

                if (items == null) {
                    continue;
                }

                for (int itemIndex = 0; itemIndex < items.length(); itemIndex++) {
                    JSONObject item = items.optJSONObject(itemIndex);
                    if (item == null) {
                        continue;
                    }
                    String itemLabel = item.optInt("quantity", 1) + "x " + cleanTicketText(item.optString("name", "Produit"));
                    y = drawItem(canvas, paint, y, itemLabel, cleanTicketText(item.optString("price", "")));

                    String option = item.optString("option_summary", "").trim();
                    String itemNotes = item.optString("item_notes", "").trim();
                    if (!option.isEmpty() || !itemNotes.isEmpty()) {
                        y = drawProductOptions(canvas, paint, y - 18, option, itemNotes);
                    }
                }
                y += 12;
            }
        }

        y += 18;
        paint.setTextAlign(Paint.Align.CENTER);
        paint.setTypeface(Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD));
        paint.setFakeBoldText(false);
        paint.setTextSize(25);
        canvas.drawText("Total Produits : " + order.optInt("product_count", itemCount), width / 2f, y, paint);
        y += 60;

        String customerNotes = order.optString("customer_notes", "").trim();
        if (!customerNotes.isEmpty()) {
            paint.setTextAlign(Paint.Align.LEFT);
            paint.setFakeBoldText(false);
            paint.setTextSize(22);
            canvas.drawText("** INSTRUCTIONS", 8, y, paint);
            y += 38;
            y = drawSmallLine(canvas, paint, y, cleanTicketText(customerNotes));
            y += 44;
        }

        paint.setStrokeWidth(4);
        canvas.drawLine(8, y, width - 8, y, paint);
        y += 50;

        paint.setTextAlign(Paint.Align.LEFT);
        paint.setTypeface(Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD));
        paint.setFakeBoldText(false);
        paint.setTextSize(28);
        canvas.drawText("Sous-total", 8, y, paint);
        paint.setTextAlign(Paint.Align.RIGHT);
        canvas.drawText(cleanTicketText(order.optString("subtotal", "")), width - 8, y, paint);
        y += 50;

        String discount = order.optString("discount", "").trim();
        if (!discount.isEmpty()) {
            String promoCode = cleanTicketText(order.optString("promo_code", "")).trim();
            paint.setTextAlign(Paint.Align.LEFT);
            canvas.drawText(promoCode.isEmpty() ? "Promo" : "Promo " + promoCode, 8, y, paint);
            paint.setTextAlign(Paint.Align.RIGHT);
            canvas.drawText(cleanTicketText(discount), width - 8, y, paint);
            y += 50;
        }

        paint.setTextAlign(Paint.Align.LEFT);
        canvas.drawText("Total a encaisser", 8, y, paint);
        paint.setTextAlign(Paint.Align.RIGHT);
        canvas.drawText(cleanTicketText(order.optString("total_to_collect", "")), width - 8, y, paint);
        y += 46;

        paint.setStrokeWidth(4);
        canvas.drawLine(8, y, width - 8, y, paint);
        y += 50;

        paint.setTextAlign(Paint.Align.LEFT);
        paint.setTypeface(Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD));
        paint.setFakeBoldText(false);
        paint.setTextSize(23);
        canvas.drawText(cleanTicketText(order.optString("customer_name", "")), 8, y, paint);
        y += 36;

        String phone = order.optString("customer_phone", "").trim();
        if (!phone.isEmpty()) {
            paint.setTypeface(Typeface.create(Typeface.SANS_SERIF, Typeface.NORMAL));
            paint.setFakeBoldText(false);
            paint.setTextSize(21);
            canvas.drawText("Tel: " + cleanTicketText(phone), 8, y, paint);
            y += 46;
        }

        paint.setStrokeWidth(4);
        canvas.drawLine(8, y, width - 8, y, paint);
        y += 52;

        paint.setTextAlign(Paint.Align.CENTER);
        paint.setTypeface(Typeface.create(Typeface.SANS_SERIF, Typeface.NORMAL));
        paint.setFakeBoldText(false);
        paint.setTextSize(19);
        canvas.drawText("Commande passee le", width / 2f, y, paint);
        y += 29;
        canvas.drawText(cleanTicketText(order.optString("created_label", "")), width / 2f, y, paint);
        y += 42;

        paint.setTypeface(Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD));
        paint.setTextSize(26);
        canvas.drawText("v", width / 2f, y, paint);
        y += 35;

        paint.setTypeface(Typeface.create(Typeface.SANS_SERIF, Typeface.NORMAL));
        paint.setFakeBoldText(false);
        paint.setTextSize(18);
        canvas.drawText("commandes.pizzexpress.fr", width / 2f, y, paint);
        y += 24;
        y = drawLogo(canvas, y);
        y += 8;

        int croppedHeight = Math.min(height, Math.max(260, y + 25));
        return Bitmap.createBitmap(bitmap, 0, 0, width, croppedHeight);
    }

    private void drawBlackBar(Canvas canvas, Paint paint, String text, int y, int height, int textSize) {
        paint.setStyle(Paint.Style.FILL);
        paint.setColor(Color.BLACK);
        canvas.drawRect(0, y, 384, y + height, paint);
        paint.setColor(Color.WHITE);
        paint.setTextAlign(Paint.Align.CENTER);
        paint.setTypeface(Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD));
        paint.setTextSize(textSize);
        canvas.drawText(text, 192, y + (height / 2f) + (textSize / 3f), paint);
        paint.setColor(Color.BLACK);
    }

    private void drawBlackBarTwoLines(Canvas canvas, Paint paint, String firstLine, String secondLine, int y, int height) {
        paint.setStyle(Paint.Style.FILL);
        paint.setColor(Color.BLACK);
        canvas.drawRect(0, y, 384, y + height, paint);
        paint.setColor(Color.WHITE);
        paint.setTextAlign(Paint.Align.CENTER);
        paint.setTypeface(Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD));
        paint.setTextSize(20);
        canvas.drawText(firstLine, 192, y + 28, paint);
        paint.setTextSize(24);
        canvas.drawText(secondLine, 192, y + 58, paint);
        paint.setColor(Color.BLACK);
    }

    private void drawFullRule(Canvas canvas, Paint paint, int y) {
        paint.setColor(Color.BLACK);
        paint.setStrokeWidth(4);
        canvas.drawLine(0, y, 384, y, paint);
    }

    private void drawRoundedBadge(Canvas canvas, Paint paint, String text, int y) {
        paint.setTextSize(34);
        paint.setTypeface(Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD));
        float textWidth = paint.measureText(text);
        float left = (384 - textWidth - 34) / 2f;
        float right = left + textWidth + 34;
        paint.setColor(Color.BLACK);
        canvas.drawRoundRect(left, y - 40, right, y + 10, 10, 10, paint);
        paint.setColor(Color.WHITE);
        paint.setTextAlign(Paint.Align.CENTER);
        canvas.drawText(text, 192, y - 5, paint);
        paint.setColor(Color.BLACK);
    }

    private int drawItem(Canvas canvas, Paint paint, int y, String label, String price) {
        paint.setTextAlign(Paint.Align.LEFT);
        paint.setTypeface(Typeface.create(Typeface.SANS_SERIF, Typeface.NORMAL));
        paint.setFakeBoldText(false);
        paint.setTextSize(26);
        canvas.drawText(label, 8, y, paint);
        paint.setTextAlign(Paint.Align.RIGHT);
        paint.setTextSize(22);
        canvas.drawText(price, 376, y, paint);
        return y + 74;
    }

    private int drawSmallLine(Canvas canvas, Paint paint, int y, String text) {
        paint.setTextAlign(Paint.Align.LEFT);
        paint.setTypeface(Typeface.create(Typeface.SANS_SERIF, Typeface.NORMAL));
        paint.setFakeBoldText(false);
        paint.setTextSize(19);
        canvas.drawText(text, 8, y, paint);
        return y + 36;
    }

    private int drawProductOptions(Canvas canvas, Paint paint, int y, String optionSummary, String itemNotes) {
        int startY = y - 15;
        int currentY = y;
        paint.setTextAlign(Paint.Align.LEFT);
        paint.setTypeface(Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD));
        paint.setFakeBoldText(false);
        paint.setTextSize(16);

        if (!optionSummary.isEmpty()) {
            canvas.drawText("Supplement :", 30, currentY, paint);
            currentY += 22;
            String[] options = optionSummary.split(",");
            for (String option : options) {
                String value = cleanTicketText(option.trim());
                if (!value.isEmpty()) {
                    canvas.drawText("- " + value, 30, currentY, paint);
                    currentY += 22;
                }
            }
            currentY += 14;
        }

        if (!itemNotes.isEmpty()) {
            canvas.drawText("Commentaire : " + cleanTicketText(itemNotes), 30, currentY, paint);
            currentY += 26;
        }

        paint.setStrokeWidth(5);
        canvas.drawLine(8, startY, 8, currentY - 8, paint);
        return currentY + 20;
    }

    private int drawLogo(Canvas canvas, int y) {
        Bitmap logo = BitmapFactory.decodeResource(context.getResources(), fr.pizzexpress.landitest.R.drawable.logo_pizzexpress);
        if (logo == null) {
            Paint fallback = new Paint(Paint.ANTI_ALIAS_FLAG);
            fallback.setColor(Color.BLACK);
            fallback.setTextAlign(Paint.Align.CENTER);
            fallback.setTypeface(Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD));
            fallback.setTextSize(24);
            canvas.drawText("PIZZ'EXPRESS", 192, y + 28, fallback);
            return y + 42;
        }

        Rect source = findLogoContentBounds(logo);
        int targetWidth = 256;
        int targetHeight = Math.max(1, Math.round((float) source.height() * targetWidth / source.width()));
        Rect destination = new Rect((384 - targetWidth) / 2, y, (384 + targetWidth) / 2, y + targetHeight);
        Paint imagePaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
        canvas.drawBitmap(logo, source, destination, imagePaint);
        return y + targetHeight;
    }

    private Rect findLogoContentBounds(Bitmap bitmap) {
        int left = bitmap.getWidth();
        int top = bitmap.getHeight();
        int right = 0;
        int bottom = 0;

        for (int y = 0; y < bitmap.getHeight(); y++) {
            for (int x = 0; x < bitmap.getWidth(); x++) {
                int pixel = bitmap.getPixel(x, y);
                int alpha = Color.alpha(pixel);
                int luminance = (Color.red(pixel) + Color.green(pixel) + Color.blue(pixel)) / 3;
                if (alpha > 12 && luminance < 248) {
                    left = Math.min(left, x);
                    top = Math.min(top, y);
                    right = Math.max(right, x);
                    bottom = Math.max(bottom, y);
                }
            }
        }

        if (right <= left || bottom <= top) {
            return new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
        }

        return new Rect(left, top, right + 1, bottom + 1);
    }

    private String cleanTicketText(String value) {
        if (value == null) {
            return "";
        }

        return value
            .replace("\u20AC", "EUR")
            .replace("\u00E2\u201A\u00AC", "EUR")
            .replace("\u00C3\u00A2\u00E2\u201A\u00AC\u00C2\u00AC", "EUR")
            .replace("\u00C3\u00A0", "a")
            .replace("\u00C3\u20AC", "A")
            .replace("\u00E2\u20AC\u2122", "'")
            .replace("\u00C5\u2019", "OE")
            .replace("\u00C5\u201C", "oe")
            .replace("\u00E0", "a")
            .replace("\u00C0", "A")
            .replace("\u0152", "OE")
            .replace("\u0153", "oe")
            .replaceAll("[\\x{1F000}-\\x{1FAFF}]", "")
            .trim();
    }
    private byte[] convert1BitBmp(Bitmap bitmap) throws PrinterException {
        int width = bitmap.getWidth();
        int height = bitmap.getHeight();
        int padding = width % 32;
        if (padding > 0) {
            padding = 32 - padding;
        }
        int paddedWidth = width + padding;
        int imageSize = (height * paddedWidth) / 8;
        byte[] image = new byte[imageSize];

        int threshold = 180;
        for (int y = 0; y < height; y++) {
            for (int x = 0; x < paddedWidth; x++) {
                boolean black = false;
                if (x < width) {
                    int pixel = bitmap.getPixel(x, y);
                    int luminance = ((Color.red(pixel) * 38) + (Color.green(pixel) * 75) + (Color.blue(pixel) * 15)) >> 7;
                    black = luminance < threshold;
                }
                if (black) {
                    int row = height - y - 1;
                    int offset = (row * (paddedWidth / 8)) + (x / 8);
                    image[offset] |= (byte) (0x80 >> (x % 8));
                }
            }
        }

        try {
            ByteArrayOutputStream output = new ByteArrayOutputStream(imageSize + 62);
            writeWord(output, 19778);
            writeDword(output, imageSize + 62L);
            writeWord(output, 0);
            writeWord(output, 0);
            writeDword(output, 62L);
            writeDword(output, 40L);
            writeDword(output, width);
            writeDword(output, height);
            writeWord(output, 1);
            writeWord(output, 1);
            writeDword(output, 0L);
            writeDword(output, imageSize);
            writeDword(output, 0L);
            writeDword(output, 0L);
            writeDword(output, 0L);
            writeDword(output, 2L);
            output.write(new byte[]{-1, -1, -1, 0, 0, 0, 0, 0});
            output.write(image);
            return output.toByteArray();
        } catch (IOException exception) {
            throw new PrinterException("Conversion image impossible", exception);
        }
    }

    private void writeWord(OutputStream output, int value) throws IOException {
        output.write(new byte[]{(byte) (value & 255), (byte) ((value >> 8) & 255)});
    }

    private void writeDword(OutputStream output, long value) throws IOException {
        output.write(new byte[]{
            (byte) (value & 255),
            (byte) ((value >> 8) & 255),
            (byte) ((value >> 16) & 255),
            (byte) ((value >> 24) & 255)
        });
    }

    private byte[] bitmapToEscPosRaster(Bitmap bitmap) {
        int width = bitmap.getWidth();
        int height = bitmap.getHeight();
        int widthBytes = (width + 7) / 8;
        byte[] image = new byte[widthBytes * height];
        int threshold = 180;

        for (int y = 0; y < height; y++) {
            for (int x = 0; x < width; x++) {
                int pixel = bitmap.getPixel(x, y);
                int luminance = ((Color.red(pixel) * 38) + (Color.green(pixel) * 75) + (Color.blue(pixel) * 15)) >> 7;
                if (luminance < threshold) {
                    int offset = y * widthBytes + (x / 8);
                    image[offset] |= (byte) (0x80 >> (x % 8));
                }
            }
        }

        ByteArrayOutputStream output = new ByteArrayOutputStream(image.length + 32);
        output.write(0x1B);
        output.write('@');
        output.write(0x1D);
        output.write('v');
        output.write('0');
        output.write(0);
        output.write(widthBytes & 0xFF);
        output.write((widthBytes >> 8) & 0xFF);
        output.write(height & 0xFF);
        output.write((height >> 8) & 0xFF);
        output.write(image, 0, image.length);
        for (int i = 0; i < 7; i++) {
            output.write('\n');
        }
        output.write(0x1D);
        output.write('V');
        output.write(0x42);
        output.write(0);
        return output.toByteArray();
    }

    static final class PrinterException extends Exception {
        PrinterException(String message) {
            super(message);
        }

        PrinterException(String message, Throwable cause) {
            super(message, cause);
        }
    }
}
