package com.igled.led;

import android.Manifest;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothManager;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanResult;
import android.bluetooth.le.ScanSettings;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.widget.Button;
import android.widget.EditText;
import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.Toast;

import androidx.core.app.ActivityCompat;

import java.util.UUID;

/**
 * Native Android BLE controller for LEDDMX-03 / LEDBLE / LEDCAR / LEDPHO / LEDSUN /
 * LEDSMART / LEDLIKE / LEDLIGHT / LEDSTAGE LED strips.
 *
 * Protocol bytes extracted from com.ledlamp v1.x smali (Lcom/home/net/NetConnectBle;).
 * See references/leddmx-protocol-map.md for the full opcode table.
 *
 * To adapt: change the device name in scanCallback (line with .equals("LEDDMX-03-5A5B")),
 * adjust the model list in detectModel() if needed, and add new cmd methods using the
 * opcode table.
 */
public class MainActivity extends Activity {

    // ====== GATT UUIDs (Chinese LED BLE standard) ======
    private static final UUID SERVICE_UUID = UUID.fromString("0000fff0-0000-1000-8000-00805f9b34fb");
    private static final UUID WRITE_CHAR_UUID = UUID.fromString("0000fff2-0000-1000-8000-00805f9b34fb");

    // ====== Persisted device for instant reconnect ======
    private static final String PREFS = "led_prefs";
    private static final String KEY_DEVICE_ADDR = "device_addr";
    private static final String KEY_DEVICE_NAME = "device_name";

    // ====== State ======
    private BluetoothAdapter btAdapter;
    private BluetoothGatt gatt;
    private BluetoothGattCharacteristic writeCh;
    private BluetoothDevice bondedDevice;
    private SharedPreferences prefs;
    private final Handler handler = new Handler(Looper.getMainLooper());
    private String deviceName = "";

    // ====== Views ======
    private TextView statusView, logView, modelView, brightValue;
    private EditText pixelsInput;
    private SeekBar brightSlider;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);  // create your own layout, see below

        statusView = findViewById(R.id.status);
        logView    = findViewById(R.id.log);
        modelView  = findViewById(R.id.model);

        BluetoothManager mgr = (BluetoothManager) getSystemService(BLUETOOTH_SERVICE);
        btAdapter = mgr.getAdapter();
        prefs = getSharedPreferences(PREFS, MODE_PRIVATE);

        // Restore last-connected device for instant reconnect
        String savedAddr = prefs.getString(KEY_DEVICE_ADDR, null);
        if (savedAddr != null && btAdapter != null) {
            try {
                bondedDevice = btAdapter.getRemoteDevice(savedAddr);
                deviceName = prefs.getString(KEY_DEVICE_NAME, savedAddr);
                modelView.setText("Modelo: " + detectModel(deviceName) + " (guardado)");
                log("📌 Device guardado: " + deviceName + " [" + savedAddr + "]");
            } catch (Exception e) {
                log("⚠️ Saved device inválido: " + e.getMessage());
            }
        }

        if (!checkPerms()) {
            ActivityCompat.requestPermissions(this, new String[]{
                    Manifest.permission.BLUETOOTH_SCAN,
                    Manifest.permission.BLUETOOTH_CONNECT,
                    Manifest.permission.ACCESS_FINE_LOCATION
            }, 1);
        }

        // ====== Wire up views ======
        findViewById(R.id.btnConnect).setOnClickListener(v -> startScan());
        findViewById(R.id.btnForget).setOnClickListener(v -> forgetDevice());

        findViewById(R.id.btnOn).setOnClickListener(v -> cmdPower(true));
        findViewById(R.id.btnOff).setOnClickListener(v -> cmdPower(false));
        findViewById(R.id.btnRed).setOnClickListener(v -> cmdColor(0xFF, 0x00, 0x00));
        findViewById(R.id.btnGreen).setOnClickListener(v -> cmdColor(0x00, 0xFF, 0x00));
        findViewById(R.id.btnBlue).setOnClickListener(v -> cmdColor(0x00, 0x00, 0xFF));
        findViewById(R.id.btnWhite).setOnClickListener(v -> cmdColor(0xFF, 0xFF, 0xFF));

        findViewById(R.id.btnFx1).setOnClickListener(v -> cmdEffect(1));
        findViewById(R.id.btnFx2).setOnClickListener(v -> cmdEffect(2));
        findViewById(R.id.btnFx3).setOnClickListener(v -> cmdEffect(3));
        findViewById(R.id.btnFx4).setOnClickListener(v -> cmdEffect(4));

        findViewById(R.id.btnRaw).setOnClickListener(v -> runFullSequence());
        findViewById(R.id.btnFlag).setOnClickListener(v -> applyTricolorFlag());
        findViewById(R.id.btnStopLoop).setOnClickListener(v -> stopAllAnimations());

        pixelsInput = findViewById(R.id.editPixels);

        brightSlider = findViewById(R.id.brightSlider);
        brightValue = findViewById(R.id.brightValue);
        brightSlider.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override public void onProgressChanged(SeekBar sb, int progress, boolean fromUser) {
                if (fromUser) {
                    brightValue.setText(String.valueOf(progress));
                    cmdBrightLevel(progress);
                }
            }
            @Override public void onStartTrackingTouch(SeekBar sb) {}
            @Override public void onStopTrackingTouch(SeekBar sb) {}
        });
    }

    private boolean checkPerms() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            return checkSelfPermission(Manifest.permission.BLUETOOTH_SCAN) == PackageManager.PERMISSION_GRANTED
                && checkSelfPermission(Manifest.permission.BLUETOOTH_CONNECT) == PackageManager.PERMISSION_GRANTED;
        }
        return checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED;
    }

    // ====== Scan: STRICT filter on the actual device name ======
    private final ScanCallback scanCallback = new ScanCallback() {
        @Override
        public void onScanResult(int callbackType, ScanResult result) {
            BluetoothDevice dev = result.getDevice();
            String name;
            try { name = dev.getName(); } catch (Exception e) { return; }
            if (name == null) return;
            log("👀 " + name);
            // ★★★ Change this to your actual device name ★★★
            if (name.equals("LEDDMX-03-5A5B") || name.equalsIgnoreCase("LEDDMX-03-5A5B")) {
                try { btAdapter.getBluetoothLeScanner().stopScan(scanCallback); } catch (Exception ignored) {}
                deviceName = name;
                connect(dev);
            }
        }
    };

    private void startScan() {
        if (!checkPerms()) {
            Toast.makeText(this, "Otorgá permisos primero", Toast.LENGTH_SHORT).show();
            return;
        }
        if (btAdapter == null || !btAdapter.isEnabled()) {
            statusView.setText("BT apagado");
            return;
        }
        if (bondedDevice != null) {
            statusView.setText("Reconectando a " + deviceName + "...");
            log("🔗 Reconectando a " + bondedDevice.getAddress());
            gatt = bondedDevice.connectGatt(this, false, gattCallback);
            return;
        }
        statusView.setText("Escaneando...");
        log("🔍 Escaneando dispositivos BLE...");
        ScanSettings settings = new ScanSettings.Builder()
                .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
                .build();
        try {
            btAdapter.getBluetoothLeScanner().startScan(null, settings, scanCallback);
        } catch (Exception e) {
            log("❌ startScan: " + e.getMessage());
            return;
        }
        handler.postDelayed(() -> {
            try { btAdapter.getBluetoothLeScanner().stopScan(scanCallback); } catch (Exception ignored) {}
            if (gatt == null) statusView.setText("Nada encontrado, escaneá de nuevo");
        }, 10000);
    }

    private void connect(BluetoothDevice dev) {
        statusView.setText("Conectando a " + deviceName + "...");
        log("🔗 Conectando a " + dev.getAddress());
        modelView.setText("Modelo: " + detectModel(deviceName));
        prefs.edit().putString(KEY_DEVICE_ADDR, dev.getAddress())
                    .putString(KEY_DEVICE_NAME, deviceName).apply();
        bondedDevice = dev;
        gatt = dev.connectGatt(this, false, gattCallback);
    }

    private final BluetoothGattCallback gattCallback = new BluetoothGattCallback() {
        @Override
        public void onConnectionStateChange(BluetoothGatt g, int status, int newState) {
            if (newState == BluetoothGatt.STATE_CONNECTED) {
                log("✅ Conectado, descubriendo servicios...");
                g.discoverServices();
            } else if (newState == BluetoothGatt.STATE_DISCONNECTED) {
                log("❌ Desconectado");
            }
        }
        @Override
        public void onServicesDiscovered(BluetoothGatt g, int status) {
            for (BluetoothGattService svc : g.getServices()) {
                String u = svc.getUuid().toString();
                if (u.startsWith("0000fff0") || u.equals("0000ffe0-0000-1000-8000-00805f9b34fb")) {
                    for (BluetoothGattCharacteristic ch : svc.getCharacteristics()) {
                        if ((ch.getProperties() & BluetoothGattCharacteristic.PROPERTY_WRITE) != 0) {
                            writeCh = ch;
                            log("✅ Char de write OK: " + ch.getUuid());
                            runOnUiThread(() -> statusView.setText("Conectado a " + deviceName));
                            return;
                        }
                    }
                }
            }
            log("⚠️ No encontré char de write");
        }
    };

    // ====== Model detection from BLE name prefix ======
    private String detectModel(String name) {
        if (name == null) return "?";
        String n = name.toUpperCase();
        if (n.startsWith("LEDDMX")) return "LEDDMX-03";
        if (n.startsWith("LEDBLE")) return "LEDBLE";
        if (n.startsWith("LEDCAR")) return "LEDCAR";
        if (n.startsWith("LEDPHO")) return "LEDPHO";
        if (n.startsWith("LEDSTAGE")) return "LEDSTAGE";
        if (n.startsWith("LEDLIGHT")) return "LEDLIGHT";
        if (n.startsWith("LEDSMART")) return "LEDSMART";
        if (n.startsWith("LEDSUN")) return "LEDSUN";
        if (n.startsWith("LEDLIKE")) return "LEDLIKE";
        return "DEFAULT-7E";
    }

    // ====== Write helper ======
    private void writeBytes(byte[] data) {
        if (writeCh == null || gatt == null) {
            log("❌ Sin conexión");
            return;
        }
        StringBuilder sb = new StringBuilder();
        for (byte b : data) sb.append(String.format("%02X ", b & 0xFF));
        log("📤 " + sb.toString().trim());
        writeCh.setValue(data);
        gatt.writeCharacteristic(writeCh);
    }

    // ====== Commands (LEDDMX-family bytes) ======
    private void cmdPower(boolean on) {
        byte[] pkt = on
            ? new byte[]{(byte)0x7B, (byte)0x04, (byte)0x04, (byte)0x01, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xBF}
            : new byte[]{(byte)0x7B, (byte)0x04, (byte)0x04, (byte)0x00, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xBF};
        writeBytes(pkt);
    }

    private void cmdColor(int r, int g, int b) {
        byte[] pkt = new byte[]{(byte)0x7B, (byte)0xFF, (byte)0x07,
                                 (byte)(r&0xFF), (byte)(g&0xFF), (byte)(b&0xFF),
                                 (byte)0xFF, (byte)0xFF, (byte)0xBF};
        writeBytes(pkt);
    }

    private void cmdBrightLevel(int level) {
        // LEDDMX-03: 9 bytes, NOT 10. Opcode 01 with level in [2] and a 1/0 flag in [3].
        byte[] pkt = new byte[]{(byte)0x7B, (byte)0x01,
                                 (byte)(level & 0xFF), (byte)0x01,
                                 (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xBF};
        writeBytes(pkt);
    }

    private void cmdEffect(int idx) {
        // Empirical: try M = idx * 30 for LEDDMX-03
        byte mode = (byte) ((idx * 30) & 0x7F);
        byte[] pkt = new byte[]{(byte)0x7B, (byte)0xFF, (byte)0x13, mode,
                                 (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xBF};
        writeBytes(pkt);
    }

    private void cmdSubarea(int nZones) {
        byte[] pkt = new byte[]{(byte)0x7B, (byte)0xFF, (byte)0x14, (byte)(nZones & 0xFF),
                                 (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xBF};
        writeBytes(pkt);
    }

    private void cmdZoneRgb(int r, int g, int b, int zoneIdx) {
        byte[] pkt = new byte[]{(byte)0x7B, (byte)0x07,
                                 (byte)(r & 0xFF), (byte)(g & 0xFF), (byte)(b & 0xFF),
                                 (byte)(zoneIdx & 0xFF),
                                 (byte)0xFF, (byte)0xFF, (byte)0xBF};
        writeBytes(pkt);
    }

    // ====== Test sequence (full self-test) ======
    private void runFullSequence() {
        log("--- INICIO secuencia test ---");
        cmdPower(true);
        handler.postDelayed(() -> cmdPower(false), 1500);
        handler.postDelayed(() -> cmdPower(true), 3000);
        handler.postDelayed(() -> cmdColor(0xFF, 0, 0), 4500);
        handler.postDelayed(() -> cmdColor(0, 0xFF, 0), 6000);
        handler.postDelayed(() -> cmdColor(0, 0, 0xFF), 7500);
        handler.postDelayed(() -> cmdColor(0xFF, 0xFF, 0xFF), 9000);
        handler.postDelayed(() -> cmdEffect(1), 10500);
        handler.postDelayed(() -> cmdEffect(2), 12000);
        handler.postDelayed(() -> cmdEffect(3), 13500);
        handler.postDelayed(() -> cmdEffect(4), 15000);
        handler.postDelayed(() -> { log("--- FIN ---"); }, 16500);
    }

    // ====== Bandera tricolor (6 zones) ======
    private static final int[][] FLAG_ZONES = {
        {0x74, 0xAC, 0xDF},  // celeste AR
        {0xFF, 0xFF, 0xFF},  // blanco AR
        {0x74, 0xAC, 0xDF},  // celeste AR
        {0x10, 0x10, 0x10},  // negro DE
        {0xDD, 0x00, 0x00},  // rojo DE
        {0xFF, 0xCC, 0x00},  // dorado DE
    };

    private void applyTricolorFlag() {
        int pixels = 30;
        try {
            String s = pixelsInput.getText().toString().trim();
            if (!s.isEmpty()) pixels = Integer.parseInt(s);
        } catch (Exception e) {}
        log("🇦🇷🇩🇪 Bandera tricolor: " + pixels + " pixels, 6 zonas");
        cmdSubarea(6);
        for (int i = 0; i < 6; i++) {
            final int idx = i;
            final int rr = FLAG_ZONES[i][0];
            final int gg = FLAG_ZONES[i][1];
            final int bb = FLAG_ZONES[i][2];
            handler.postDelayed(() -> {
                cmdZoneRgb(rr, gg, bb, idx);
                log("   zona " + idx + " → #" + String.format("%02X%02X%02X", rr, gg, bb));
            }, 200L * (i + 1));
        }
    }

    // ====== Stop everything (test sequence, flag, all pending animations) ======
    private void stopAllAnimations() {
        handler.removeCallbacksAndMessages(null);
        log("⏹ Todas las animaciones detenidas");
    }

    private void forgetDevice() {
        prefs.edit().remove(KEY_DEVICE_ADDR).remove(KEY_DEVICE_NAME).apply();
        bondedDevice = null;
        deviceName = "";
        if (gatt != null) { gatt.disconnect(); gatt.close(); gatt = null; }
        modelView.setText("Modelo: ?");
        statusView.setText("Device olvidado. Escaneá de nuevo.");
        log("🗑 Device olvidado");
    }

    private void log(String s) {
        runOnUiThread(() -> logView.append(s + "\n"));
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (gatt != null) { gatt.disconnect(); gatt.close(); }
    }
}
