package com.unibuy.smartdevice;

import android.os.Build;
import android.app.Application;
import android.content.Context;
import android.content.Intent;
import android.util.DisplayMetrics;
import android.view.WindowManager;

import androidx.work.BackoffPolicy;
import androidx.work.ExistingPeriodicWorkPolicy;
import androidx.work.ExistingWorkPolicy;
import androidx.work.OneTimeWorkRequest;
import androidx.work.PeriodicWorkRequest;
import androidx.work.WorkManager;

import com.unibuy.smartdevice.controller.DevController;
import com.unibuy.smartdevice.controller.DevXinYuanController;
import com.unibuy.smartdevice.database.MemoDao;
import com.unibuy.smartdevice.database.ProductsDao;
import com.unibuy.smartdevice.database.SettingsDao;
import com.unibuy.smartdevice.database.SlotsDao;
import com.unibuy.smartdevice.devices.ComPort;
import com.unibuy.smartdevice.devices.DevXinYuan;
import com.unibuy.smartdevice.devices.Rs232Dev;
import com.unibuy.smartdevice.devices.SlotField;
import com.unibuy.smartdevice.devices.UsbDev;
import com.unibuy.smartdevice.exception.ErrorCode;
import com.unibuy.smartdevice.exception.Logs;
import com.unibuy.smartdevice.exception.LogsEmptyException;
import com.unibuy.smartdevice.exception.LogsNoSuchElementException;
import com.unibuy.smartdevice.exception.LogsUnsupportedOperationException;
import com.unibuy.smartdevice.structure.AccessCodeStructure;
import com.unibuy.smartdevice.structure.CashSetStructure;
import com.unibuy.smartdevice.structure.CreditCardReceiptStructure;
import com.unibuy.smartdevice.structure.FreeGiftStructure;
import com.unibuy.smartdevice.structure.MemberVerificationStructure;
import com.unibuy.smartdevice.structure.MemoStructure;
import com.unibuy.smartdevice.structure.ReportAccessCodeStructure;
import com.unibuy.smartdevice.structure.BuyStructure;
import com.unibuy.smartdevice.structure.DevSetStructure;
import com.unibuy.smartdevice.structure.EsunpayStructure;
import com.unibuy.smartdevice.structure.GreenInvoiceStructure;
import com.unibuy.smartdevice.structure.GreenpayMerchantStructure;
import com.unibuy.smartdevice.structure.GreenpaySettingStructure;
import com.unibuy.smartdevice.structure.InvoiceItemStructure;
import com.unibuy.smartdevice.structure.InvoiceStructure;
import com.unibuy.smartdevice.structure.MachineStructure;
import com.unibuy.smartdevice.structure.MarketingPlanStructure;
import com.unibuy.smartdevice.structure.PostInvoiceByGreenStructure;
import com.unibuy.smartdevice.structure.ProductStructure;
import com.unibuy.smartdevice.structure.ReportDispatchInfoStructure;
import com.unibuy.smartdevice.structure.ReportFlowInfoStructure;
import com.unibuy.smartdevice.structure.ReportFreeGiftCodeStructure;
import com.unibuy.smartdevice.structure.ReportInvoiceInfoStructure;
import com.unibuy.smartdevice.structure.ReportSlotListStructure;
import com.unibuy.smartdevice.structure.SettingStructure;
import com.unibuy.smartdevice.structure.SlotStructure;
import com.unibuy.smartdevice.tools.HandlerMain;
import com.unibuy.smartdevice.tools.ToastHandlerMain;
import com.unibuy.smartdevice.tools.Tools;
import com.unibuy.smartdevice.ui.FontendActivity;
import com.unibuy.smartdevice.ui.tools.VideoEoxPlayer;
import com.unibuy.smartdevice.worker.AppCrashHandler;
import com.unibuy.smartdevice.worker.UploadLogOneTimeWorker;
import com.unibuy.smartdevice.worker.UploadLogPeriodicWorker;

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

import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

public class MyApp extends Application {
    private Logs logs;
    private static MyApp instance;
    public static MyApp getInstance() {
        return instance;
    }
    private int screenWidthPx;
    private int screenHeightPx;
    private int closeSlot;
    private float density;
    private ComPort comPort1ByXinYuan;
    private ComPort comPort2ByNFCPay;
    private List<ProductStructure> uploadProductList;
    private List<ProductStructure> productList;
    private List<String> fontendAdUrlString;
    private Map<Integer, List<SlotStructure>> slotMap;
    private List<MarketingPlanStructure> marketingPlanList;
    private List<BuyStructure> buyList;
    private String decryptedData;
    private String cmuhPickupErrorMessage;
    private Map<String, List<MemoStructure>> memoMap;
    private CashSetStructure cashSetStructure;
    private AccessCodeStructure accessCodeStructure;
    private FreeGiftStructure freeGiftStructure;
    private MemberVerificationStructure memberVerificationStructure;
    private ReportAccessCodeStructure reportAccessCodeStructure;
    private ReportFreeGiftCodeStructure reportFreeGiftCodeStructure;
    private ReportSlotListStructure reportSlotListStructure;
    private EsunpayStructure esunpay;
    private MachineStructure machine;
    private DevSetStructure devSet;
    private GreenInvoiceStructure greenInvoice;
    private GreenpaySettingStructure greenpaySetting;
    private GreenpayMerchantStructure greenpayMerchant;
    private static DevXinYuanController devXinYuanController;
    public ReportFlowInfoStructure reportFlowInfoData;
    public PostInvoiceByGreenStructure postInvoiceByGreenData;
    public ReportInvoiceInfoStructure reportInvoiceInfoData;
    public InvoiceStructure invoiceData;
    public List<InvoiceItemStructure> invoiceItems;
    public ReportDispatchInfoStructure reportDispatchInfoData;
    public CreditCardReceiptStructure creditCardReceiptInfoData;
    public volatile boolean isNfcPaymentActive; // 用於控制 NFC 支付感應器的活動門禁

    public boolean isDetectHost;
    public String suAccess;

    // ─── 全域閒置計時器 ───────────────────────────────────────
    // 記錄使用者最後一次觸碰螢幕的時間 (ms)
    private volatile long lastInteractionTime = System.currentTimeMillis();

    /** 任何 Activity 偵測到使用者互動時呼叫此方法，讓計時器歸零 */
    public void updateLastInteraction() {
        lastInteractionTime = System.currentTimeMillis();
    }

    /** 回傳距離上次互動已過了幾秒 */
    public long getIdleSeconds() {
        return (System.currentTimeMillis() - lastInteractionTime) / 1000;
    }
    // ─────────────────────────────────────────────────────────

    @Override
    public void onCreate() {
        super.onCreate();
        logs = new Logs(this.getClass());

        UsbDev.setUsbManager(getApplicationContext());
        Rs232Dev.setSuPath(getApplicationContext());
        Tools.setExternalFilesDir(getApplicationContext());

        instance = this;
        isShowDialog = false;
        suAccess = "";

        File crashLogFile = new File(getExternalFilesDir(null), "crash_pending.txt");
        if (crashLogFile.exists()) {
            try {
                String content = Tools.readFile(crashLogFile); // 使用上面方法讀檔

                logs.info("============================ crash ============================");
                logs.update("lastCrashInfo", content);

                // 上傳或排程任務
                OneTimeWorkRequest request = new OneTimeWorkRequest.Builder(UploadLogOneTimeWorker.class).build();
                WorkManager.getInstance(this).enqueueUniqueWork(
                        "UploadCrashLog",
                        ExistingWorkPolicy.REPLACE,
                        request
                );

                crashLogFile.delete(); // 清除
            } catch (IOException e) {
                logs.warning(e);
            }
        }

        logs.info("============================ start ============================");

        try {
            UsbDev.getDeviceInfo(getApplicationContext());
        } catch (LogsUnsupportedOperationException e) {
            logs.warning(e);
        }

        this.comPort1ByXinYuan = ComPort.COM_PORT_1;
        this.comPort2ByNFCPay = ComPort.COM_PORT_2;

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
            this.comPort1ByXinYuan = ComPort.COM_PORT_S1;
            this.comPort2ByNFCPay = ComPort.COM_PORT_S2;
        }

        this.devSet = new DevSetStructure(true,   false, false, true, true,
                true, true, false, false,true,true,true,true,true);
        this.machine = new MachineStructure("");
        this.esunpay = new EsunpayStructure("", "", "");
        this.greenInvoice = new GreenInvoiceStructure("", "", "", "");
        this.greenpaySetting = new GreenpaySettingStructure(
                "",
                "",
                "",
                "",
                "",
                "",
                "",
                "");
        this.greenpayMerchant = new GreenpayMerchantStructure(
                "",
                "",
                "",
                "",
                "",
                "",
                "",
                "",
                "",
                "");
        this.cashSetStructure = new CashSetStructure(true, true, true, true, true, true, true);
        this.accessCodeStructure = new AccessCodeStructure("");
        this.freeGiftStructure = new FreeGiftStructure("");

        this.memberVerificationStructure = new MemberVerificationStructure("", 0, "", 0);
        this.reportAccessCodeStructure = new ReportAccessCodeStructure("", 0, 0, "", "");
        this.reportFreeGiftCodeStructure = new ReportFreeGiftCodeStructure("", 0, "", 0, 0, "");
        this.reportSlotListStructure = new ReportSlotListStructure("","","","");
        this.fontendAdUrlString = new ArrayList<>();

        ToastHandlerMain toastHandlerMain = new ToastHandlerMain(getApplicationContext(), logs);

        if (this.devXinYuanController != null) {
            this.devXinYuanController.shutdown();
        }

        this.devXinYuanController = new DevXinYuanController(toastHandlerMain);
        devXinYuanController.start();

        devXinYuanController.addSendBuffer(devXinYuanController.getDevXinYuan().syncPackNo((byte)0x01));

        devXinYuanController.getReadBufferByNow(new DevController.ReadBufferOnScheduler(toastHandlerMain) {
            @Override
            protected HandlerMain setHandlerMain() {
                return getSrcHandlerMain();
            }

            @Override
            protected Class<?> setCls() {
                return getClass();
            }

            @Override
            public void readBufferOnScheduler(DevController devController, HandlerMain handlerMain) {
                int moneySwitchOkCount = 0;
                for(byte[] buffer: devController.getReadBufferByMax()) {
                    DevXinYuan.VMC vmc = DevXinYuanController.vmcMap.get(buffer[0]);
                    switch (vmc) {
                        case VMC_29_MONEY_SWITCH:
                            if (buffer[1] == 0x00) {
                                moneySwitchOkCount++;
                            }
                            break;
                    }
                }
            }
        });
        devXinYuanController.addSendBuffer(devXinYuanController.getDevXinYuan().inComeByIPC((byte) 0x08, 9999));
        devXinYuanController.addSendBuffer(devXinYuanController.getDevXinYuan().setMoneyType((byte) 0x01, (byte) 0x00, (byte) 0x00));
        devXinYuanController.addSendBuffer(devXinYuanController.getDevXinYuan().setMoneyType((byte) 0x02, (byte) 0x00, (byte) 0x00));

        initialSettingData();
        initialSystemSet(getApplicationContext());
        initialReportData();
        calculateScreenSize();

        this.uploadProductList = new ArrayList<>();
        this.productList = new ArrayList<>();
        initialProductData();
        this.slotMap = new HashMap<>();
        initialSlotData();
        this.marketingPlanList = new ArrayList<>();
        this.buyList = new ArrayList<>();
        this.memoMap = new HashMap<>();
        initialMemoData();

        PeriodicWorkRequest periodicWorkRequest = new PeriodicWorkRequest.Builder(
                UploadLogPeriodicWorker.class,
                UploadLogPeriodicWorker.getTimeinByMinute(),
                TimeUnit.MINUTES)
                .setBackoffCriteria(
                        BackoffPolicy.EXPONENTIAL,
                        2,
                        TimeUnit.MINUTES)
                .build();
        WorkManager.getInstance(this).enqueueUniquePeriodicWork(
                "PeriodicWorkRequest",  // 確保這個名稱的 WorkRequest 只存在一個
                ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE,
                periodicWorkRequest
        );

        Thread.setDefaultUncaughtExceptionHandler(new AppCrashHandler(this));
    }

    public void initialReportData() {
        setReportFlowInfoData(new ReportFlowInfoStructure("",0, "", "", "", 0, "", "", 0, 0, 0, new JSONArray()));
        setPostInvoiceByGreenData(new PostInvoiceByGreenStructure("","", 0, new InvoiceStructure("", "", "", ""), new ArrayList<>()));
        setReportInvoiceInfoData(new ReportInvoiceInfoStructure("", "", "", "", "", "", ""));
        setInvoiceData(new InvoiceStructure("", "", "", ""));
        setInvoiceItems(new ArrayList<>());
        setReportDispatchInfoData(new ReportDispatchInfoStructure("", 0, "", 0, 0, "", 0, "", "", 0, 0, 0));
        setReportAccessCodeStructure(new ReportAccessCodeStructure("", 0, 0, "", ""));
        setAccessCodeStructure(new AccessCodeStructure(""));
        setFreeGiftStructure(new FreeGiftStructure(""));
//        setReportFreeGiftCodeStructure(new ReportFreeGiftCodeStructure("", 0, "", 0, 0, ""));
        setMemberVerificationStructure(new MemberVerificationStructure("", 0, "", 0));
        //addReportDispatchInfoData(new ReportDispatchInfoStructure("", 0, "", 0, 0, "", 0, "", "", 0, 0, 0));
    }

    private void initialSystemSet(Context context) {
        MemoDao memoDao = new MemoDao(context);

        if (memoDao.existOnlyOne(MyApp.getInstance().getDevSet().getClass().getSimpleName())) {
            JSONObject jsonObject = memoDao.getOnlyOne(MyApp.getInstance().getDevSet().getClass().getSimpleName()).getData();
            logs.debug("initialSystemSet:" + jsonObject.toString());
            try {
                MyApp.getInstance().getDevSet().setVMC(jsonObject.getBoolean("VMC"));
                MyApp.getInstance().getDevSet().setElectic(jsonObject.getBoolean("Electic"));
                MyApp.getInstance().getDevSet().setShoppingCar(jsonObject.getBoolean("ShoppingCar"));
                MyApp.getInstance().getDevSet().setInvoice(jsonObject.getBoolean("Invoice"));
                MyApp.getInstance().getDevSet().setDevNFCPay(jsonObject.getBoolean("DevNFCPay"));
                MyApp.getInstance().getDevSet().setDevEsunPay(jsonObject.getBoolean("DevEsunPay"));
                MyApp.getInstance().getDevSet().setDevTapPay(jsonObject.getBoolean("DevTapPay"));
                MyApp.getInstance().getDevSet().setDevCash(jsonObject.getBoolean("DevCash"));
                MyApp.getInstance().getDevSet().setDevLinePay(jsonObject.getBoolean("DevLinePay"));
            } catch (JSONException e) {
                new Logs(MyApp.class).warning(e);
            }
        }

        if (memoDao.existOnlyOne(MyApp.getInstance().getCashSetStructure().getClass().getSimpleName())) {
            JSONObject jsonObject = memoDao.getOnlyOne(MyApp.getInstance().getCashSetStructure().getClass().getSimpleName()).getData();
            logs.debug("initialSystemSet:" + jsonObject.toString());
            try {
                MyApp.getInstance().getCashSetStructure().setBillF1000(jsonObject.getBoolean("BillF1000"));
                MyApp.getInstance().getCashSetStructure().setBillE500(jsonObject.getBoolean("BillE500"));
                MyApp.getInstance().getCashSetStructure().setBillD100(jsonObject.getBoolean("BillD100"));
                MyApp.getInstance().getCashSetStructure().setCoinF50(jsonObject.getBoolean("CoinF50"));
                MyApp.getInstance().getCashSetStructure().setCoinE10(jsonObject.getBoolean("CoinE10"));
                MyApp.getInstance().getCashSetStructure().setCoinD5(jsonObject.getBoolean("CoinD5"));
                MyApp.getInstance().getCashSetStructure().setCoinC1(jsonObject.getBoolean("CoinC1"));
            } catch (JSONException e) {
                new Logs(MyApp.class).warning(e);
            }
        }
    }
    public static void saveSystemSet(Context context) {
        MemoDao memoDao = new MemoDao(context);

        try {
            JSONObject systemSetByJsonObject = new JSONObject();
            systemSetByJsonObject.put("VMC", MyApp.getInstance().getDevSet().isVMC());
            systemSetByJsonObject.put("Electic", MyApp.getInstance().getDevSet().isElectic());
            systemSetByJsonObject.put("ShoppingCar", MyApp.getInstance().getDevSet().isShoppingCar());
            systemSetByJsonObject.put("Invoice", MyApp.getInstance().getDevSet().isInvoice());
            systemSetByJsonObject.put("DevNFCPay", MyApp.getInstance().getDevSet().isDevNFCPay());
            systemSetByJsonObject.put("DevEsunPay", MyApp.getInstance().getDevSet().isDevEsunPay());
            systemSetByJsonObject.put("DevTapPay", MyApp.getInstance().getDevSet().isDevTapPay());
            systemSetByJsonObject.put("DevCash", MyApp.getInstance().getDevSet().isDevCash());
            systemSetByJsonObject.put("DevLinePay", MyApp.getInstance().getDevSet().isDevLinePay());

            JSONObject cashSetByJsonObject = new JSONObject();
            cashSetByJsonObject.put("BillF1000", MyApp.getInstance().getCashSetStructure().isBillF1000());
            cashSetByJsonObject.put("BillE500", MyApp.getInstance().getCashSetStructure().isBillE500());
            cashSetByJsonObject.put("BillD100", MyApp.getInstance().getCashSetStructure().isBillD100());
            cashSetByJsonObject.put("CoinF50", MyApp.getInstance().getCashSetStructure().isCoinF50());
            cashSetByJsonObject.put("CoinE10", MyApp.getInstance().getCashSetStructure().isCoinE10());
            cashSetByJsonObject.put("CoinD5", MyApp.getInstance().getCashSetStructure().isCoinD5());
            cashSetByJsonObject.put("CoinC1", MyApp.getInstance().getCashSetStructure().isCoinC1());

            memoDao.insertOnlyOne(new MemoStructure(
                    MyApp.getInstance().getDevSet().getClass().getSimpleName(),
                    systemSetByJsonObject
                    , new JSONObject()));

            memoDao.mapUpdate();

            new Logs(MyApp.class).info("saveSystemSet count:" + memoDao.getCount(MyApp.getInstance().getDevSet().getClass().getSimpleName()));

            memoDao.insertOnlyOne(new MemoStructure(
                    MyApp.getInstance().getCashSetStructure().getClass().getSimpleName(),
                    cashSetByJsonObject
                    , new JSONObject()));

            memoDao.mapUpdate();

            new Logs(MyApp.class).info("saveSystemSet count:" + memoDao.getCount(MyApp.getInstance().getCashSetStructure().getClass().getSimpleName()));
        } catch (JSONException e) {
            new Logs(MyApp.class).warning(e);
        }
    }

    public static void updateLog(Context context) {
        OneTimeWorkRequest oneTimeWorkRequest = new OneTimeWorkRequest.Builder(UploadLogOneTimeWorker.class)
                .setInitialDelay(5, TimeUnit.SECONDS)
                .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 5, TimeUnit.MINUTES)
                .build();
        WorkManager.getInstance(context).enqueueUniqueWork(
                "oneTimeWorkRequest",  // 設定唯一名稱，避免重複
                ExistingWorkPolicy.REPLACE,
                oneTimeWorkRequest
        );
    }

    public static boolean checkAndReconnectDev() {
        boolean isAlive = false;
        Logs staticLogs = new Logs(MyApp.class);

        if (devXinYuanController != null) {
            try {
                // 發送同步包測試
                devXinYuanController.addSendBuffer(
                        devXinYuanController.getDevXinYuan().syncPackNo((byte) 0x01)
                );
                Thread.sleep(100);
                if (!devXinYuanController.getReadBufferByMax().isEmpty()) {
                    isAlive = true;
                }
            } catch (Exception e) {
                isAlive = false;
            }
        }

        // 若不活躍則重連
        if (!isAlive) {
            staticLogs.info("下位機失連，重新連線中...");
            if (devXinYuanController != null) {
                devXinYuanController.shutdown();
            }
            devXinYuanController = new DevXinYuanController(devXinYuanController.getHandlerMain());
            devXinYuanController.start();
            devXinYuanController.addSendBuffer(
                    devXinYuanController.getDevXinYuan().syncPackNo((byte) 0x01)
            );
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        return true;
    }

    public void initMqtt() {
        String machineId = MyApp.getInstance().getMachine().getMachineID();
        String apiKey = MyApp.getInstance().getMachine().getApiKey();

        if (machineId != null && !machineId.isEmpty() && apiKey != null && !apiKey.isEmpty()) {
            logs.info("Starting MQTT Service for machine: " + machineId);
            
            Intent serviceIntent = new Intent(this, com.unibuy.smartdevice.service.MqttService.class);
            serviceIntent.putExtra("machineId", machineId);
            serviceIntent.putExtra("apiToken", apiKey);
            
            try {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                    startForegroundService(serviceIntent);
                } else {
                    startService(serviceIntent);
                }
            } catch (Exception e) {
                // Android 12+ 在背景狀態下（如安裝後自動重啟）不允許啟動前台/背景服務
                // 這裡只記錄日誌，不再嘗試 fallback，等使用者開啟 App 進前台後再由 Activity 觸發
                logs.info("MQTT Service start blocked (app in background): " + e.getMessage());
            }
        }
    }

    public void initialSettingData() {
        SettingsDao settingsDao = new SettingsDao(getApplicationContext());

        if (MyApp.getInstance().getMachine().getMachineID().isEmpty()) {
            SettingStructure setting = settingsDao.getOne(MyApp.getInstance().getMachine().getClass().getSimpleName());
            if (setting != null) {
                logs.info("initialSettingData Machine:" + setting.toString());
                MyApp.getInstance().getMachine().setApiKey(setting.getData(0));
                MyApp.getInstance().getMachine().setMachineID(setting.getData(1));
                if (setting.getDataCount() >= 3 && setting.getData(2) != null && !setting.getData(2).isEmpty()) {
                    MyApp.getInstance().getMachine().setMqttEnv(setting.getData(2));
                }
            }
        }

        initMqtt();

        if (MyApp.getInstance().getGreenInvoice().getGreenMerchantID().isEmpty()) {
            SettingStructure setting = settingsDao.getOne(MyApp.getInstance().getGreenInvoice().getClass().getSimpleName());
            MyApp.getInstance().getGreenInvoice().setGreenMerchantID(setting.getData(0));
            MyApp.getInstance().getGreenInvoice().setGreenHashKey(setting.getData(1));
            MyApp.getInstance().getGreenInvoice().setGreenHashIV(setting.getData(2));
            MyApp.getInstance().getGreenInvoice().setGreenEmail(setting.getData(3));
        }

        if (MyApp.getInstance().getEsunpay().getStoreID().isEmpty()) {
            SettingStructure setting = settingsDao.getOne(MyApp.getInstance().getEsunpay().getClass().getSimpleName());
            MyApp.getInstance().getEsunpay().setStoreID(setting.getData(0));
            MyApp.getInstance().getEsunpay().setTermID(setting.getData(1));
            MyApp.getInstance().getEsunpay().setHash(setting.getData(2));
        }

        if (MyApp.getInstance().getGreenpaySetting().getAppId().isEmpty()) {
            SettingStructure setting = settingsDao.getOne(MyApp.getInstance().getGreenpaySetting().getClass().getSimpleName());
            MyApp.getInstance().getGreenpaySetting().setAppId(setting.getData(0));
            MyApp.getInstance().getGreenpaySetting().setAppKey(setting.getData(1));
            MyApp.getInstance().getGreenpaySetting().setPartnerKey(setting.getData(2));
            MyApp.getInstance().getGreenpaySetting().setCtbcMerchantId(setting.getData(3));
            MyApp.getInstance().getGreenpaySetting().setNcccMerchantId(setting.getData(4));
            MyApp.getInstance().getGreenpaySetting().setTaishinMerchantId(setting.getData(5));
            MyApp.getInstance().getGreenpaySetting().setEsunMerchantId(setting.getData(6));
            MyApp.getInstance().getGreenpaySetting().setGpuseMerchantId(setting.getData(7));

            SettingStructure setting2 = settingsDao.getOne(MyApp.getInstance().getGreenpayMerchant().getClass().getSimpleName());
            MyApp.getInstance().getGreenpayMerchant().setLineMerchantId(setting2.getData(0));
            MyApp.getInstance().getGreenpayMerchant().setPsMerchantId(setting2.getData(1));
            MyApp.getInstance().getGreenpayMerchant().setEasyMerchantId(setting2.getData(2));
            MyApp.getInstance().getGreenpayMerchant().setPiMerchantId(setting2.getData(3));
            MyApp.getInstance().getGreenpayMerchant().setJkoMerchantId(setting2.getData(4));
            MyApp.getInstance().getGreenpayMerchant().setDirectMerchantId(setting2.getData(5));
            MyApp.getInstance().getGreenpayMerchant().setAppleMerchantId(setting2.getData(6));
            MyApp.getInstance().getGreenpayMerchant().setGoogleMerchantId(setting2.getData(7));
            MyApp.getInstance().getGreenpayMerchant().setSamsungMerchantId(setting2.getData(8));
            MyApp.getInstance().getGreenpayMerchant().setAtomeMerchantId(setting2.getData(9));
        }

        if (MyApp.getInstance().getFontendAdUrlString().isEmpty()) {
            List<SettingStructure> settingList = settingsDao.getWhere(VideoEoxPlayer.class.getSimpleName());
            for (SettingStructure setting: settingList) {
                MyApp.getInstance().getFontendAdUrlString().add(setting.getData(0));
            }
        }

        settingsDao.close();
    }
    private void initialProductData() {
        if (MyApp.getInstance().getProductList().isEmpty()) {
            ProductsDao productsDao = new ProductsDao(this);
            for (ProductStructure product: productsDao.getAll()) {
                MyApp.getInstance().getProductList().add(product);
            }
            productsDao.close();
            logs.info("product count:"+ MyApp.getInstance().getProductList().size());
        }
    }

    public void initialSlotData() {
        logs.debug("initialSlotData");

        //開架構
        for (SlotField slotField: SlotField.values()) {
            if (!getInstance().getSlotMap().containsKey(slotField.getField())) {
                getInstance().getSlotMap().put(slotField.getField(), new ArrayList<>());
                logs.info(slotField.getField()+" slot db open");
            }
        }

        //查詢資料庫
        SlotsDao slotsDao = new SlotsDao(this);
        Map<Integer, List<SlotStructure>> slotMapNew = slotsDao.getAll();
        for (SlotField slotField: SlotField.values()) {
            if (getInstance().getSlotList(slotField.getField()).isEmpty() && slotMapNew.get(slotField.getField()) != null && !slotMapNew.get(slotField.getField()).isEmpty()) {
                List<SlotStructure> slotList = slotMapNew.get(slotField.getField());
                for (SlotStructure slot : slotList) {
                    getInstance().getSlotList(slotField.getField()).add(slot);
                }
                logs.info(slotField.getField()+" slot db count:"+getInstance().getSlotList(slotField.getField()).size());
            }
        }
        slotsDao.close();

        //無資料就初始化
        if (getInstance().getSlotList(SlotField.VMC.getField()).isEmpty()) {
            for (int i = 1; i <= 60; i++) {
                getInstance().getSlotList(SlotField.VMC.getField()).add(new SlotStructure(SlotField.VMC.getField(), i,
                        new ProductStructure("?", "", "", 0, 0, 0, 0, "", "","",""),
                        0));
            }
            logs.info(SlotField.VMC.getField()+" slot init count:" + getInstance().getSlotList(SlotField.VMC.getField()).size());
        }

        if (getInstance().getSlotList(SlotField.ELECTRIC.getField()).isEmpty()) {
            for (int i = 1; i <= 10; i++) {
                getInstance().getSlotList(SlotField.ELECTRIC.getField()).add(new SlotStructure(SlotField.ELECTRIC.getField(), i,
                        new ProductStructure("?", "", "", 0, 0, 0, 0, "", "","",""),
                        0));
            }
            logs.info(SlotField.ELECTRIC.getField()+" slot init count:" + getInstance().getSlotList(SlotField.ELECTRIC.getField()).size());
        }
    }

    private void calculateScreenSize() {
        // 取得 WindowManager
        WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
        DisplayMetrics metrics = new DisplayMetrics();

        // 取得螢幕尺寸
        if (wm != null) {
            wm.getDefaultDisplay().getMetrics(metrics);
            this.screenWidthPx = metrics.widthPixels;
            this.screenHeightPx = metrics.heightPixels;
            density = metrics.density;  // 保存密度值
        }
    }

    // 新增 px 轉 dp 的函數
    public int pxToDp(int px) {
        return Math.round(px / density);
    }

    // 新增 dp 轉 px 的函數（可選）
    public int dpToPx(int dp) {
        return Math.round(dp * density);
    }

    public int getScreenWidthPx() {
        return screenWidthPx;
    }

    public int getScreenHeightPx() {
        return screenHeightPx;
    }

    public ComPort getComPort1ByXinYuan() {
        return comPort1ByXinYuan;
    }

    public ComPort getComPort2ByNFCPay() {
        return comPort2ByNFCPay;
    }

    public List<ProductStructure> getUploadProductList() {
        return uploadProductList;
    }

    public List<ProductStructure> getProductList() {
        return productList;
    }

    public List<String> getFontendAdUrlString() {
        return fontendAdUrlString;
    }

    public ProductStructure getProduct(String productId) throws LogsNoSuchElementException {
        for (ProductStructure product: getProductList()) {
            if (product.getProductID().equals(productId)) {
                return product;
            }
        }

        throw new LogsNoSuchElementException(logs, "get product "+productId+" not found");
    }

    public Map<Integer, List<SlotStructure>> getSlotMap() {
        return slotMap;
    }

    public List<SlotStructure> getSlotList(int field) {
        return getSlotMap().get(field);
    }

    public SlotStructure getSlotData(int field, int slot) throws LogsEmptyException {
        for (SlotStructure slotData: getSlotList(field)) {
            if (slotData.getSlot() == slot) {
                return slotData;
            }
        }

        throw new LogsEmptyException(logs, "Slot data not found");
    }

    public SlotStructure getSlotDataByPid(int field, String pid) throws LogsEmptyException {
        int i = 0;
        for (SlotStructure slotData: getSlotList(field)) {
            i += 1;
            logs.info("AAAAAAAAAAAAAAAAAAAAAAAAAAAA0"+i);
            if (slotData.getProduct().getProductID().equals(String.valueOf(pid))) {
                if(slotData.getCount() > 0) {
                    return slotData;
                }
            }
        }

        throw new LogsEmptyException(logs, "Slot data not found");
    }

    public SlotStructure getSlotDataByPid(String pid) throws LogsEmptyException {
        for (SlotField slotField: SlotField.values()) {
            try {
                return getSlotDataByPid(slotField.getField(), pid);
            } catch (LogsEmptyException e) {

            }
        }

        throw new LogsEmptyException(logs, "Slot data not found");
    }

    public List<MarketingPlanStructure> getMarketingPlanList() {
        return marketingPlanList;
    }

    public List<BuyStructure> getBuyList() {
        return buyList;
    }

    public Logs getLogs() {
        return logs;
    }

    public void setDecryptedData(String decryptedData) {
        this.decryptedData = decryptedData;
    }

    public String getDecryptedData() {
        return decryptedData;
    }

    public void setCmuhPickupErrorMessage(String cmuhPickupErrorMessage) {
        this.cmuhPickupErrorMessage = cmuhPickupErrorMessage;
    }

    public String getCmuhPickupErrorMessage() {
        return cmuhPickupErrorMessage;
    }

    public Map<String, List<MemoStructure>> getMemoMap() {
        return memoMap;
    }

    private void initialMemoData() {
        logs.debug("initialMemoData");

        // 確保 memoMap 已初始化
        if (getInstance().getMemoMap() == null) {
            logs.info("memoMap初始化");
            getInstance().memoMap = new HashMap<>();
        }
        
        // 開架構
        if (!getInstance().getMemoMap().containsKey("CMUH")) {
            getInstance().getMemoMap().put("CMUH", new ArrayList<>());
            logs.info("CMUH memo db open");
        }

        // 查詢資料庫
        MemoDao memoDao = new MemoDao(this);
        try {
            Map<String, List<MemoStructure>> memoMapNew = memoDao.getAll();
            if (memoMapNew != null && memoMapNew.get("CMUH") != null && !memoMapNew.get("CMUH").isEmpty()) {
                getInstance().getMemoMap().get("CMUH").addAll(memoMapNew.get("CMUH"));
            }
        } catch (Exception e) {
            logs.info("📛 讀取 memo 資料庫失敗或無資料，跳過。");
        } finally {
            memoDao.close();
        }

        // 無資料就初始化一筆資料
        if (getInstance().getMemoMap().get("CMUH").isEmpty()) {
            logs.info("初始化CMUH Memo資料表");
            String memoStr = "{\"ITEM\": [{\"SN\": \"00000000\", \"DRGNO\": \"0000\"}]}";
            try {
                getInstance().getMemoMap().get("CMUH").add(new MemoStructure("CMUH", new JSONObject(memoStr), new JSONObject()));
            } catch (JSONException e) {
                throw new RuntimeException(e);
            }
        }
    }

    public EsunpayStructure getEsunpay() {
        return esunpay;
    }

    public MachineStructure getMachine() {
        return machine;
    }

    public GreenInvoiceStructure getGreenInvoice() {
        return greenInvoice;
    }

    public GreenpaySettingStructure getGreenpaySetting() {
        return greenpaySetting;
    }

    public GreenpayMerchantStructure getGreenpayMerchant() {
        return greenpayMerchant;
    }

    public DevXinYuanController getDevXinYuanController() {
        return devXinYuanController;
    }

    public void setReportFlowInfoData(ReportFlowInfoStructure reportFlowInfoData) {
        this.reportFlowInfoData = reportFlowInfoData;
    }

    public ReportFlowInfoStructure getReportFlowInfoData() {
        return reportFlowInfoData;
    }

    public void setPostInvoiceByGreenData(PostInvoiceByGreenStructure postInvoiceByGreenData) {
        this.postInvoiceByGreenData = postInvoiceByGreenData;
    }

    public PostInvoiceByGreenStructure getPostInvoiceByGreenData() {
        return postInvoiceByGreenData;
    }

    public void setReportInvoiceInfoData(ReportInvoiceInfoStructure reportInvoiceInfoData) {
        this.reportInvoiceInfoData = reportInvoiceInfoData;
    }

    public void setReportAccessCodeStructure(ReportAccessCodeStructure reportAccessCodeStructure) {
        this.reportAccessCodeStructure = reportAccessCodeStructure;
    }

    public void setReportFreeGiftCodeStructure(ReportFreeGiftCodeStructure reportFreeGiftCodeStructure) {
        this.reportFreeGiftCodeStructure = reportFreeGiftCodeStructure;
    }

    public ReportFreeGiftCodeStructure getReportFreeGiftCodeStructure() {
        return reportFreeGiftCodeStructure;
    }

    public ReportInvoiceInfoStructure getReportInvoiceInfoData() {
        return reportInvoiceInfoData;
    }

    public void setInvoiceData(InvoiceStructure invoiceData) {
        this.invoiceData = invoiceData;
    }

    public InvoiceStructure getInvoiceData() {
        return invoiceData;
    }

    public List<InvoiceItemStructure> getInvoiceItems() {
        return invoiceItems;
    }

    public void setInvoiceItems(List<InvoiceItemStructure> invoiceItems) {
        this.invoiceItems = invoiceItems;
    }

    public void setReportDispatchInfoData(ReportDispatchInfoStructure reportDispatchInfoData) {
        this.reportDispatchInfoData = reportDispatchInfoData;
    }

    public ReportDispatchInfoStructure getReportDispatchInfoData() {
        return reportDispatchInfoData;
    }

    public void setCreditCardReceiptInfoData(CreditCardReceiptStructure creditCardReceiptInfoData){
        this.creditCardReceiptInfoData = creditCardReceiptInfoData;
    }

    public CreditCardReceiptStructure getCreditCardReceiptInfoData() {
        return creditCardReceiptInfoData;
    }


    public DevSetStructure getDevSet() {
        return devSet;
    }


    public ReportAccessCodeStructure getReportAccessCodeStructure() {
        return reportAccessCodeStructure;
    }

    public ReportSlotListStructure getReportSlotListStructure(){
        return reportSlotListStructure;
    }

    public CashSetStructure getCashSetStructure() {
        return cashSetStructure;
    }

    public AccessCodeStructure getAccessCodeStructure() {
        return accessCodeStructure;
    }

    public void setAccessCodeStructure(AccessCodeStructure accessCodeStructure) {
        this.accessCodeStructure = accessCodeStructure;
    }

    public FreeGiftStructure getFreeGiftStructure() {
        return freeGiftStructure;
    }

    public void setFreeGiftStructure(FreeGiftStructure freeGiftStructure) {
        this.freeGiftStructure = freeGiftStructure;
    }

    public MemberVerificationStructure getMemberVerificationStructure() {
        return memberVerificationStructure;
    }

    public void setMemberVerificationStructure(MemberVerificationStructure memberVerificationStructure) {
        this.memberVerificationStructure = memberVerificationStructure;
    }

    public int getCloseSlot() {
        return closeSlot;
    }

    public void setCloseSlot(int closeSlot) {
        this.closeSlot = closeSlot;
    }

    private volatile boolean isShowDialog;

    public boolean isShowDialog() {
        return isShowDialog;
    }

    public void setShowDialog(boolean showDialog) {
        if (this.isShowDialog != showDialog) {
            logs.info("setShowDialog: " + isShowDialog + " -> " + showDialog);
        }
        isShowDialog = showDialog;
    }

    public boolean isMachineBusy() {
        return !getBusyReason().isEmpty();
    }

    /**
     * 取得機台忙碌的原因，若不忙碌則回傳空字串
     * @return 忙碌原因描述
     */
    public String getBusyReason() {
        StringBuilder reason = new StringBuilder();
        if (isShowDialog()) {
            reason.append("[Dialog Open] ");
        }
        if (!getBuyList().isEmpty()) {
            reason.append("[Cart not empty: ").append(getBuyList().size()).append(" items] ");
        }
        if (isNfcPaymentActive()) {
            reason.append("[NFC Payment Active] ");
        }
        
        String result = reason.toString().trim();
        if (!result.isEmpty()) {
            logs.debug("Machine busy check: " + result);
        }
        return result;
    }

    public String getSuAccess() {
        return suAccess;
    }

    public void setSuAccess(String suAccess) {
        this.suAccess = suAccess;
    }

    public boolean isDetectHost() {
        return isDetectHost;
    }

    public void setDetectHost(boolean detectHost) {
        isDetectHost = detectHost;
    }

    public boolean isNfcPaymentActive() {
        return isNfcPaymentActive;
    }

    public void setNfcPaymentActive(boolean nfcPaymentActive) {
        isNfcPaymentActive = nfcPaymentActive;
    }



}
