package com.qsjnic.mcu.service;

import android.app.ActivityManager;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.Service;
import android.content.Intent;
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Process;
import android.os.RemoteCallbackList;
import android.util.Log;

import androidx.annotation.Nullable;

import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.List;

import com.qsjnic.mcu.contract.IMcuService;
import com.qsjnic.mcu.contract.IMcuFrameListener;

import com.qsjnic.mcu.service.feature.DomainBus;
import com.qsjnic.mcu.service.feature.KeyFeature;
import com.qsjnic.mcu.service.feature.VehicleFeature;
import com.qsjnic.mcu.service.feature.RadioFeature;
import com.qsjnic.mcu.service.feature.UpgradeFeature;

/**
 * Stage A2 skeleton: empty long-running service that only proves the process
 * is started and stays alive. Native (B1) and Binder (C2) come later.
 */
public class QsMcuSystemService extends Service {

    private static final String TAG = "QsMcuSystemService";
    private static QsMcuSystemService sInstance;

    private static final String SERIAL_PORT = "/dev/ttyS7";
    private static final int    SERIAL_BAUD = 460800;

    private final Handler mainHandler = new Handler(Looper.getMainLooper());

    private static final String NOTIF_CHANNEL_ID = "qsjnic_mcu_service";
    private static final int    NOTIF_ID          = 1;

    private final RemoteCallbackList<IMcuFrameListener> frameListeners = new RemoteCallbackList<>();

    private volatile boolean probePaused = false;

    private DomainBus bus;
    private VehicleFeature vehicle;
    private KeyFeature key;
    private RadioFeature radio;
    private UpgradeFeature upgrade;

    private IMcuService.Stub binder;

    @Override
    public void onCreate() {
        super.onCreate();
        sInstance = this;

        vehicle = new VehicleFeature(frameListeners);
        key     = new KeyFeature();
        radio   = new RadioFeature(frameListeners);
        upgrade = new UpgradeFeature(frameListeners);
        bus = new DomainBus();
        bus.register(vehicle);
        bus.register(key);
        bus.register(radio);
        bus.register(upgrade);
        FrameRouter.setBus(bus);
        Log.i(TAG, "onCreate"
                + " pid=" + Process.myPid()
                + " uid=" + Process.myUid()
                + " thread=" + Thread.currentThread().getName());

        binder = new McuBinder(vehicle, key, radio, upgrade, frameListeners);

        // Foreground service notification (required on Android 8+ to stay alive).
        startForeground(NOTIF_ID, buildForegroundNotification());

        // B1: open serial port (with exponential-backoff retry).
        tryOpenSerial(/*attempt=*/0);
    }

    private static final long[] RETRY_DELAYS_MS = {1000, 2000, 4000, 8000, 16000, 30000, 60000};

    /**
     * Uses ActivityManager to detect another running instance of this service.
     * If one is found (different PID, same process name), we skip serial open
     * to avoid /dev/ttyS7 contention between user 0 and user 10.
     */
    private boolean tryClaimSerialOwnership() {
        try {
            int myPid = Process.myPid();
            ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
            List<ActivityManager.RunningAppProcessInfo> processes = am.getRunningAppProcesses();
            if (processes == null) return true;
            for (ActivityManager.RunningAppProcessInfo info : processes) {
                if (info.pid != myPid
                        && "com.qsjnic.mcu.service".equals(info.processName)) {
                    Log.w(TAG, "Another service instance already running (pid=" + info.pid
                            + ", myPid=" + myPid
                            + "). Skipping serial open — will serve as passthrough stub.");
                    return false;
                }
            }
            return true;
        } catch (Exception e) {
            Log.w(TAG, "Failed to check for duplicate instances, proceeding anyway", e);
            return true;
        }
    }

    private void tryOpenSerial(int attempt) {
        if (!tryClaimSerialOwnership()) {
            Log.i(TAG, "Serial port init skipped — another instance already owns /dev/ttyS7");
            return;
        }
        boolean ok = McuLink.get().open(SERIAL_PORT, SERIAL_BAUD);
        Log.i(TAG, "McuLink.open " + SERIAL_PORT + " @ " + SERIAL_BAUD
                + " -> " + (ok ? "OK fd=" + McuLink.get().getFd() : "FAILED"));

        if (ok) {
            // B2: start link state machine (Setup + heartbeat + RX).
            McuLink.get().start();
            // B3: once link is alive, prod MCU with Public 0x81 REQ_INIT_INFO
            // so it starts broadcasting voltage / line-detect / version.
            scheduleInitProbe(0);
            // Dev-only: also probe 0x86 (MCU_VERSION) and re-probe 0x81 every
            // 10s so we can observe routing even if MCU is sluggish.
            scheduleRepeatProbe();
            return;
        }

        long delay = RETRY_DELAYS_MS[Math.min(attempt, RETRY_DELAYS_MS.length - 1)];
        Log.w(TAG, "Serial open failed, retrying in " + delay + "ms (attempt=" + attempt + ")");
        mainHandler.postDelayed(() -> tryOpenSerial(attempt + 1), delay);
    }

    private final Runnable repeatProbeRunnable = new Runnable() {
        @Override public void run() {
            if (!probePaused && McuLink.get().isLinkAlive()) {
                Log.i(TAG, "B3 repeat probe: 0x81 + 0x86 type=02");
                McuLink.get().sendNormal(0x01, 0x81, null);
                McuLink.get().sendNormal(0x01, 0x86, new byte[] { 0x02 });
            }
            mainHandler.postDelayed(this, 10000);
        }
    };

    private void scheduleRepeatProbe() {
        mainHandler.postDelayed(repeatProbeRunnable, 10000);
    }

    /** Pause periodic probes while OTA is in progress (prevents UART collisions). */
    public static void pauseProbe() {
        QsMcuSystemService svc = sInstance;
        if (svc != null) svc.probePaused = true;
    }

    /** Resume periodic probes after OTA completes or aborts. */
    public static void resumeProbe() {
        QsMcuSystemService svc = sInstance;
        if (svc != null) svc.probePaused = false;
    }

    /** Polls for link readiness then sends Public 0x81 REQ_INIT_INFO. */
    private void scheduleInitProbe(final int attempt) {
        if (attempt > 30) {
            Log.w(TAG, "init probe gave up after " + attempt + " polls (link still not alive)");
            return;
        }
        mainHandler.postDelayed(() -> {
            if (McuLink.get().isLinkAlive()) {
                Log.i(TAG, "B3 init probe: sending Public 0x81 REQ_INIT_INFO");
                McuLink.get().sendNormal(0x01, 0x81, null);
            } else {
                scheduleInitProbe(attempt + 1);
            }
        }, 200);
    }

    @Override
    public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
        Log.i(TAG, "onStartCommand flags=" + flags + " startId=" + startId
                + " intent=" + intent);
        return START_STICKY;
    }

    /** Broadcast raw frame to all registered IMcuFrameListener clients. */
    public static void broadcastRawFrame(int ft, int mt, int cmd, byte[] body) {
        // Called from FrameRouter; sFrameListeners is static in the service instance.
        // We access via the singleton reference because this is a static method.
        if (sInstance == null) return;
        int n = sInstance.frameListeners.beginBroadcast();
        for (int i = 0; i < n; i++) {
            try { sInstance.frameListeners.getBroadcastItem(i).onMcuFrame(ft, mt, cmd, body); }
            catch (android.os.RemoteException ignored) {}
        }
        sInstance.frameListeners.finishBroadcast();
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.i(TAG, "onBind " + intent + " -> IMcuService.Stub");
        return binder;
    }

    @Override
    public void onDestroy() {
        Log.i(TAG, "onDestroy");
        sInstance = null;
        stopForeground(true);
        McuLink.get().stop();
        McuLink.get().close();
        super.onDestroy();
    }

    /** Builds a minimal foreground notification for the MCU service. */
    private Notification buildForegroundNotification() {
        NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && nm != null) {
            NotificationChannel ch = new NotificationChannel(
                    NOTIF_CHANNEL_ID, "MCU Service", NotificationManager.IMPORTANCE_LOW);
            nm.createNotificationChannel(ch);
        }
        Notification.Builder builder = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
                ? new Notification.Builder(this, NOTIF_CHANNEL_ID)
                : new Notification.Builder(this);
        builder.setContentTitle("MCU Service")
               .setContentText("Running")
               .setSmallIcon(android.R.drawable.stat_sys_data_bluetooth)
               .setOngoing(true);
        return builder.build();
    }

    @Override
    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
        pw.println("===== QSJNIC MCU Service =====");
        pw.println("PID: " + Process.myPid());
        pw.println("UID: " + Process.myUid());
        pw.println("Link alive: " + McuLink.get().isLinkAlive());
        pw.println("Link open:  " + McuLink.get().isOpen());
        pw.println("Serial owner: " + isSerialOwner());

        long[] s = McuLink.get().getStats();
        pw.println("----- Latency (us) -----");
        pw.println("TX count:   " + s[0]);
        pw.println("ACK count:  " + s[1]);
        pw.println("Samples:    " + s[2]);
        pw.println("Average:    " + s[3] + " us");
        pw.println("Max:        " + s[4] + " us");
        pw.println("P95:        " + s[5] + " us");

        pw.println("----- Listeners -----");
        pw.println("Vehicle listeners: " + vehicle.listenerCount());
        pw.println("Key listeners:     " + key.listenerCount());
        pw.println("Radio listeners:   " + radio.listenerCount());
        pw.println("Upgrade listeners: " + upgrade.listenerCount());
    }

    private boolean isSerialOwner() {
        try {
            int myPid = Process.myPid();
            ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
            List<ActivityManager.RunningAppProcessInfo> processes = am.getRunningAppProcesses();
            if (processes == null) return McuLink.get().isLinkAlive();
            for (ActivityManager.RunningAppProcessInfo info : processes) {
                if (info.pid < myPid
                        && "com.qsjnic.mcu.service".equals(info.processName)) {
                    return false; // An older instance exists — it owns the serial port
                }
            }
            return true;
        } catch (Exception e) {
            return McuLink.get().isLinkAlive();
        }
    }
}
