package com.unibuy.smartdevice.devices;

import android.content.Context;
import android.serialport.SerialPort;
import android.util.Log;

import com.unibuy.smartdevice.exception.Logs;
import com.unibuy.smartdevice.exception.LogsIOException;
import com.unibuy.smartdevice.exception.LogsSecurityException;
import com.unibuy.smartdevice.exception.LogsUnsupportedOperationException;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;

public class Rs232Dev {
    private Logs logs;
    private boolean showLogs = false;
    private static boolean isMockMode = false;
    private static List<String> rs232DeviceList;
    private SerialPort serialPort;
    private InputStream inputStream;
    private OutputStream outputStream;
    private boolean isRead = false;
    public void setRead(boolean read) {
        isRead = read;
    }
    public boolean isRead() {
        return isRead;
    }

    public static String searchSuPath() throws LogsSecurityException {
        File fileSu = new File("/system/bin/su_root");

        if (!fileSu.exists()) {
            fileSu = new File("/system/bin/su");
            if (!fileSu.exists()) {
                fileSu = new File("/system/xbin/su");
            }
        }

        if (fileSu.exists()) {
            return fileSu.getAbsolutePath();
        } else {
            throw new LogsSecurityException(new Logs(Rs232Dev.class), "su file not found");
        }
    }

    private static String suPath;

    public static void setSuPath(Context context) {
        try {
            suPath = searchSuPath();
            SerialPort.setSuPath(suPath);
            isMockMode = false;
        } catch (LogsSecurityException e) {
            Log.e("Rs232Dev", "Root permission (su) not found, entering Mock Mode: " + e.getMessage());
            suPath = null;
            isMockMode = true;
        }
    }

    public Rs232Dev() {
        this.logs = new Logs(this.getClass());
        initDeviceList();
    }

    public Rs232Dev(ComPort comPort) throws LogsUnsupportedOperationException, LogsIOException {
        this();
        connectPort(comPort);
    }

    private void initDeviceList() {
        if (rs232DeviceList == null) rs232DeviceList = new ArrayList<>();
        if (rs232DeviceList.isEmpty()) {
            for (ComPort comPort : ComPort.values()) {
                if (chmod666File(comPort.getFilepath())) {
                    rs232DeviceList.add(comPort.getFilepath());
                }
            }
        }
        if (rs232DeviceList.isEmpty()) {
            isMockMode = true;
            Log.w("Rs232Dev", "No serial ports found, entering Mock Mode.");
        }
    }

    public void clearBuffer() throws LogsIOException {
        if (isMockMode || inputStream == null) {
            if (showLogs) logs.info(isMockMode ? "[MockMode] Skip clearBuffer" : "InputStream 為 null，無法清除緩衝區");
            return;
        }

        try {
            while (inputStream.available() > 0) {
                inputStream.read();
            }
        } catch (IOException e) {
            throw new LogsIOException(logs, "清除RS232緩衝區失敗", e);
        }
    }

    private boolean chmod666File(String comPortPath) {
        if (showLogs) this.logs.info("chmod666File getDevice:" + comPortPath);

        File fileComPort = new File(comPortPath);

        if (!fileComPort.exists()) {
            if (showLogs) this.logs.info(fileComPort.getAbsolutePath() + " not exists:" + fileComPort.exists());

            return false;
        }

        if (showLogs) this.logs.info(fileComPort.getAbsolutePath() + " exists:true canRead:" + fileComPort.canRead() + " canWrite:" + fileComPort.canWrite());

        if (fileComPort.canRead() && fileComPort.canWrite()) {
            return true;
        }

        try {
            if (showLogs) this.logs.info("suPath:" + suPath);

            Process su = Runtime.getRuntime().exec(suPath);
            String cmd = "chmod 666 " + fileComPort.getAbsolutePath() + "\nexit\n";
            su.getOutputStream().write(cmd.getBytes());
            su.getOutputStream().flush();

            // 使用執行緒處理標準輸出和錯誤輸出
            Thread stdoutThread = new Thread(() -> readStream(su.getInputStream(), showLogs, "OUTPUT"));
            Thread stderrThread = new Thread(() -> readStream(su.getErrorStream(), showLogs, "ERROR"));

            stdoutThread.start();
            stderrThread.start();

            // 等待子程序完成
            int exitCode = su.waitFor();

            // 等待輸出執行緒結束
            stdoutThread.join();
            stderrThread.join(); // 確保執行緒執行完畢

            if (exitCode != 0) {
                return false;
            }

            if (showLogs) this.logs.info(fileComPort.getAbsolutePath() + " set canRead:" + fileComPort.canRead() + " canWrite:" + fileComPort.canWrite());

            if (fileComPort.canRead() && fileComPort.canWrite()) {
                return true;
            } else {
                return false;
            }
        } catch (IOException | InterruptedException e) {
            this.logs.info(fileComPort.getAbsolutePath() + " error");
            return false;
        }
    }

    private void readStream(InputStream inputStream, boolean showLogs, String streamType) {
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
            String line;
            while ((line = reader.readLine()) != null) {
                if (showLogs) {
                    logs.info(streamType + ": " + line);
                }
            }
        } catch (IOException e) {
            logs.warning(e);
        }
    }

    public void connectPort(ComPort comPort) throws LogsUnsupportedOperationException, LogsIOException {
        if (isMockMode) {
            Log.i("Rs232Dev", "[MockMode] Faking connection to: " + comPort.getFilepath());
            return;
        }

        try {
            if (rs232DeviceList.contains(comPort.getFilepath())) {
                this.serialPort = SerialPort
                        .newBuilder(comPort.getFilepath(), comPort.getBaudRate())
                        .build();
                if (showLogs) Log.i("connect", "file:" + comPort.getFilepath() + " baudrate:" + comPort.getBaudRate());
                this.inputStream = this.serialPort.getInputStream();
                this.outputStream = this.serialPort.getOutputStream();
            } else {
                Log.w("Rs232Dev", "Serialport not found: " + comPort.getFilepath() + ", entering Mock Mode.");
                isMockMode = true;
            }
        } catch (IOException e) {
            Log.e("Rs232Dev", "Connect failed: " + e.getMessage() + ", entering Mock Mode.");
            isMockMode = true;
        }
    }

    public byte[] readToMatch(byte stx1, byte stx2, int moveLimit) throws LogsIOException {
        return readToMatch(new byte[]{stx1, stx2}, moveLimit);
    }

    public byte[] readToMatch(byte stx1, int moveLimit) throws LogsIOException {
        return readToMatch(new byte[]{stx1}, moveLimit);
    }

    public byte[] readToMatch(byte[] stx, int moveLimit) throws LogsIOException {
        byte[] buffer = new byte[0];
        try {
            InputStream inputStream = this.serialPort.getInputStream();
            int moveCount = 0;
            while (moveCount <= moveLimit || moveLimit == 0) {
                byte[] tmp = new byte[1];
                if (inputStream.read(tmp) >= 1) {
                    if (PortTools.match(tmp[0], stx)) {
                        buffer = PortTools.concatByteArrays(buffer, tmp);
                        return buffer;
                    } else {
                        buffer = PortTools.concatByteArrays(buffer, tmp);
                    }
                }
                moveCount++;
            }
            return buffer;
        } catch (IOException e) {
            throw new LogsIOException(logs, e.getLocalizedMessage());
        }
    }

    public byte[] readListen(int readMax, long delayMillis) throws LogsIOException {
        InputStream inputStream = this.serialPort.getInputStream();
        byte[] buffer = new byte[0];
        int readCount = 0;
        while (true) {
            try {
                int size = inputStream.available();
//                logs.info("size:" + size);
                if (size > 0) {
                    byte[] tmp = new byte[size];
                    inputStream.read(tmp);
                    buffer = PortTools.concatByteArrays(buffer, tmp);
                } else {
                    Thread.sleep(delayMillis);
                }
            } catch (IOException e) {
                throw new LogsIOException(logs, "readListen:" + e.getMessage());
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            readCount++;
            if (readCount >= readMax) break;
        }

        if (showLogs) logs.debug("read buffer:"+PortTools.showHex(buffer));
        return buffer;
    }

    public int read(byte[] buffer) throws LogsIOException {
        if (isMockMode || this.serialPort == null) return 0;
        try {
            InputStream inputStream = this.serialPort.getInputStream();
            int size = inputStream.read(buffer);
            if (showLogs) logs.debug("read buffer:"+PortTools.showHex(buffer));
            return size;
        } catch (IOException e) {
            throw new LogsIOException(logs, "read:" + e.getMessage());
        }
    }

    public void send(byte[] buffer) throws LogsIOException {
        if (isMockMode || this.serialPort == null) {
            Log.i("Rs232Dev", "[MockMode] send buffer: " + PortTools.showHex(buffer));
            return;
        }
        try {
            OutputStream outputStream = this.serialPort.getOutputStream();
            outputStream.write(buffer);
            outputStream.flush();
            if (showLogs) logs.debug("send buffer:"+PortTools.showHex(buffer));
        } catch (IOException e) {
            throw new LogsIOException(logs, "send:" + e.getMessage());
        }
    }

    public int transmission(byte[] sendBuffer, byte[] readBuffer) throws LogsIOException {
        if (isMockMode || this.serialPort == null) {
            Log.i("Rs232Dev", "[MockMode] send buffer: " + PortTools.showHex(sendBuffer));
            return 0;
        }
        try {
            OutputStream outputStream = this.serialPort.getOutputStream();
            InputStream inputStream = this.serialPort.getInputStream();
            outputStream.write(sendBuffer);
            outputStream.flush();
            if (showLogs) logs.debug("send buffer:"+PortTools.showHex(sendBuffer));
            int size = inputStream.read(readBuffer);
            if (showLogs) logs.debug("read buffer:"+PortTools.showHex(readBuffer));
            return size;
        } catch (IOException e) {
            throw new LogsIOException(logs, e.getLocalizedMessage());
        }
    }

    public void closePort() throws LogsIOException {
        if (this.serialPort != null) {
            try {
                if (inputStream != null) {
                    inputStream.close();
                    inputStream = null;
                }
                if (outputStream != null) {
                    outputStream.close();
                    outputStream = null;
                }
            } catch (IOException e) {
                // 忽略流關閉異常，繼續嘗試關閉 serialPort
            }

            this.serialPort.close();
            this.serialPort = null;
            Log.i("Rs232Dev", "Serial port closed successfully.");
        }
    }

    // ✅ 新增真正非阻塞讀取方法
    public byte[] readNonBlocking() throws LogsIOException {
        if (isMockMode || this.serialPort == null) return new byte[0];
        try {
            InputStream inputStream = serialPort.getInputStream();
            int available = inputStream.available();
            if (available > 0) {
                byte[] buffer = new byte[available];
                int size = inputStream.read(buffer);
                if (size > 0) {
                    if (showLogs) logs.debug("readNonBlocking buffer: " + PortTools.showHex(buffer));
                    return buffer;
                }
            }
            return new byte[0]; // 沒資料就回傳空陣列
        } catch (IOException e) {
            throw new LogsIOException(logs, "readNonBlocking: " + e.getMessage());
        }
    }

    public boolean isPortOpen() {
        return this.serialPort != null; // 或根據實作狀況判斷連線狀態
    }


    public static void getDeviceInfo() {
        System.out.println("----- 偵測Su檔 -----");
        try {
            System.out.println(searchSuPath());
        } catch (LogsSecurityException e) {
            System.out.println("找不到Su檔案");
        }

        System.out.println("----- 遍巡tty檔 -----");
        File devDir = new File("/dev");
        if (devDir.exists() && devDir.isDirectory()) {
            String[] files = devDir.list();
            if (files != null) {
                for (String file : files) {
                    if (file.contains("tty")) {
                        File ttyFile = new File("/dev/"+file);
                        System.out.println(ttyFile.getAbsolutePath() + " exists:true canRead:" + ttyFile.canRead() + " canWrite:" + ttyFile.canWrite());
                    }
                }
            } else {
                System.out.println("無法列出 /dev 目錄內容。");
            }
        } else {
            System.out.println("/dev 目錄不存在或無法訪問。");
        }
    }
}
