package com.okithaki.autoapp;

import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.nsd.NsdManager;
import android.net.nsd.NsdServiceInfo;
import android.net.wifi.WifiManager;
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
import android.util.Base64;

import androidx.core.app.NotificationCompat;

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

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;

/**
 * V249530 manual same-Wi-Fi cell. Every Android device is both a tiny TCP server and a client.
 * Discovery uses Android NSD plus a UDP radar fallback. Payloads are text/SOS only and AES-GCM encrypted.
 * The service runs in the isolated :neighborhood process and never changes the normal OK ITHAKI server URL.
 */
public final class NeighborhoodCellService extends Service {
    public static final String ACTION_START = "com.okithaki.autoapp.CELL_START_V249530";
    public static final String ACTION_STOP = "com.okithaki.autoapp.CELL_STOP_V249530";
    public static final String EXTRA_NICKNAME = "nickname";
    public static final String EXTRA_CELL_CODE = "cell_code";

    private static final String PROTOCOL = "ITHAKI-CELL-V249530";
    private static final String SERVICE_TYPE = "_ithaki-msg._tcp.";
    private static final String PREFS = "ok_ithaki_cell_v249530";
    private static final String PREF_NODE_ID = "node_id";
    private static final String PREF_MESSAGES = "messages";
    private static final String CHANNEL_STATUS = "ok_ithaki_cell_status_v249530";
    private static final String CHANNEL_MESSAGE = "ok_ithaki_cell_message_v249530";
    private static final String CHANNEL_SOS = "ok_ithaki_cell_sos_v249530";
    private static final int FOREGROUND_ID = 249530;
    private static final int UDP_PORT = 38429;
    private static final int MAX_FRAME = 64 * 1024;
    private static final int MAX_MESSAGES = 500;
    private static final long MAX_AGE_SECONDS = 24L * 60L * 60L;
    private static final long PEER_FRESH_MS = 30_000L;

    public interface Listener { void onCellChanged(); }
    public final class LocalBinder extends Binder { public NeighborhoodCellService getService(){ return NeighborhoodCellService.this; } }

    private static final class Peer {
        final String nodeId;
        volatile String host;
        volatile int port;
        volatile long lastSeen;
        volatile String source;
        Peer(String nodeId){ this.nodeId=nodeId; }
    }

    private final IBinder binder = new LocalBinder();
    private final Set<Listener> listeners = Collections.synchronizedSet(new LinkedHashSet<Listener>());
    private final List<JSONObject> messages = Collections.synchronizedList(new ArrayList<JSONObject>());
    private final Set<String> seen = Collections.synchronizedSet(new LinkedHashSet<String>());
    private final Map<String,Peer> peers = new ConcurrentHashMap<String,Peer>();
    private final ExecutorService io = Executors.newFixedThreadPool(6);
    private final ScheduledExecutorService timers = Executors.newScheduledThreadPool(2);
    private final AtomicBoolean resolving = new AtomicBoolean(false);
    private final List<NsdServiceInfo> resolveQueue = Collections.synchronizedList(new ArrayList<NsdServiceInfo>());
    private final SecureRandom random = new SecureRandom();

    private volatile boolean running=false;
    private volatile String lastError="";
    private volatile String nickname="Μέλος";
    private volatile String cellHash="";
    private volatile String serviceName="";
    private volatile SecretKey cellKey;
    private String nodeId="";
    private ServerSocket serverSocket;
    private DatagramSocket radarSocket;
    private WifiManager.MulticastLock multicastLock;
    private NsdManager nsd;
    private NsdManager.RegistrationListener registrationListener;
    private NsdManager.DiscoveryListener discoveryListener;
    private ScheduledFuture<?> radarFuture;
    private ScheduledFuture<?> watchdogFuture;

    @Override public void onCreate(){
        super.onCreate();
        createChannels();
        loadState();
        nsd=(NsdManager)getSystemService(Context.NSD_SERVICE);
    }

    @Override public IBinder onBind(Intent intent){ return binder; }

    @Override public int onStartCommand(Intent intent,int flags,int startId){
        if(intent!=null && ACTION_STOP.equals(intent.getAction())){ stopCell(); return START_NOT_STICKY; }
        if(intent!=null && ACTION_START.equals(intent.getAction())){
            String n=intent.getStringExtra(EXTRA_NICKNAME);
            String code=intent.getStringExtra(EXTRA_CELL_CODE);
            startForeground(FOREGROUND_ID,buildStatusNotification("Προετοιμασία χειροκίνητης Κυψέλης…"));
            io.execute(() -> startCell(n,code));
        }
        return START_NOT_STICKY;
    }

    @Override public void onDestroy(){ stopCellInternal(false); io.shutdownNow(); timers.shutdownNow(); super.onDestroy(); }

    public void addListener(Listener l){ if(l!=null)listeners.add(l); }
    public void removeListener(Listener l){ if(l!=null)listeners.remove(l); }

    public synchronized void startCell(String requestedNickname,String code){
        if(running){ restartRadar(); return; }
        String cleanCode=code==null?"":code.trim();
        if(cleanCode.length()<8){ failSafe("Ο κωδικός Κυψέλης χρειάζεται τουλάχιστον 8 χαρακτήρες."); return; }
        if(requestedNickname!=null&&!requestedNickname.trim().isEmpty())nickname=requestedNickname.trim();
        try{
            cellHash=shortHash(cleanCode);
            cellKey=deriveKey(cleanCode,cellHash);
            peers.clear();
            lastError="";
            serverSocket=new ServerSocket();
            serverSocket.setReuseAddress(true);
            serverSocket.bind(new InetSocketAddress(0));
            running=true;
            serviceName="ITHAKI-"+cellHash+"-"+nodeId.substring(Math.max(0,nodeId.length()-8));
            acquireMulticastLock();
            startAcceptLoop();
            startNsd();
            startRadar();
            watchdogFuture=timers.scheduleAtFixedRate(this::watchdog,4,4,TimeUnit.SECONDS);
            updateForeground("Κυψέλη ενεργή · "+serviceName+" · θύρα "+serverSocket.getLocalPort());
            emitCenterEvent("cell_started_"+cellHash,"Κυψέλη Wi‑Fi ενεργή","Κάθε κινητό λειτουργεί χειροκίνητα ως μικροserver. Μόνο κείμενο και SOS.",false);
            notifyChanged();
        }catch(Throwable t){ failSafe("Η Κυψέλη δεν ξεκίνησε: "+safeMessage(t)); }
    }

    public synchronized void stopCell(){
        stopCellInternal(true);
        try{stopForeground(true);}catch(Exception ignored){}
        stopSelf();
        notifyChanged();
    }

    private synchronized void stopCellInternal(boolean manual){
        boolean wasRunning=running;
        running=false;
        stopNsd();
        if(radarFuture!=null)radarFuture.cancel(true);radarFuture=null;
        if(watchdogFuture!=null)watchdogFuture.cancel(true);watchdogFuture=null;
        closeQuietly(radarSocket); radarSocket=null;
        closeQuietly(serverSocket); serverSocket=null;
        releaseMulticastLock();
        peers.clear();
        cellKey=null;
        if(manual&&wasRunning)emitCenterEvent("cell_stopped_"+System.currentTimeMillis(),"Κυψέλη Wi‑Fi διακόπηκε","Η εφαρμογή επέστρεψε στην κανονική σύνδεση OK ITHAKI.",false);
        persistState();
    }

    public void restartRadar(){
        if(!running)return;
        sendBeacon();
        try{if(discoveryListener!=null&&nsd!=null)nsd.stopServiceDiscovery(discoveryListener);}catch(Exception ignored){}
        startDiscovery();
        notifyChanged();
    }

    private void startAcceptLoop(){
        io.execute(() -> {
            while(running){
                try{
                    Socket socket=serverSocket.accept();
                    socket.setSoTimeout(5000);
                    io.execute(() -> handleIncoming(socket));
                }catch(SocketException e){ if(running)failSafe("Ο μικροserver σταμάτησε: "+safeMessage(e)); break; }
                catch(Throwable t){ if(running){lastError="accept_failed: "+safeMessage(t);notifyChanged();} }
            }
        });
    }

    private void handleIncoming(Socket socket){
        try(Socket s=socket;
            DataInputStream in=new DataInputStream(new BufferedInputStream(s.getInputStream()));
            DataOutputStream out=new DataOutputStream(new BufferedOutputStream(s.getOutputStream()))){
            int len=in.readInt();
            if(len<=0||len>MAX_FRAME)throw new IllegalArgumentException("invalid_frame");
            byte[] bytes=new byte[len];in.readFully(bytes);
            JSONObject frame=new JSONObject(new String(bytes,StandardCharsets.UTF_8));
            JSONObject packet=decryptFrame(frame);
            boolean accepted=receivePacket(packet,s.getInetAddress()==null?"":s.getInetAddress().getHostAddress());
            JSONObject ack=new JSONObject().put("ok",accepted).put("packet_id",packet.optString("packet_id",""));
            byte[] ackBytes=ack.toString().getBytes(StandardCharsets.UTF_8);
            out.writeInt(ackBytes.length);out.write(ackBytes);out.flush();
        }catch(Throwable t){ if(running){lastError="receive_failed: "+safeMessage(t);notifyChanged();} }
    }

    public String sendMessage(String text,String requestedNickname,String kind){
        JSONObject out=new JSONObject();
        try{
            String clean=text==null?"":text.trim();
            if(!running){return out.put("ok",false).put("reason","cell_not_running").toString();}
            if(clean.isEmpty()){return out.put("ok",false).put("reason","empty_message").toString();}
            if(clean.length()>4000)clean=clean.substring(0,4000);
            if(requestedNickname!=null&&!requestedNickname.trim().isEmpty())nickname=requestedNickname.trim();
            String safeKind="sos".equalsIgnoreCase(kind)?"sos":"text";
            long now=System.currentTimeMillis()/1000L;
            JSONObject packet=new JSONObject();
            packet.put("protocol",PROTOCOL);packet.put("packet_id",UUID.randomUUID().toString());packet.put("cell",cellHash);
            packet.put("from_node",nodeId);packet.put("nickname",nickname);packet.put("kind",safeKind);packet.put("text",clean);
            packet.put("created_at",now);packet.put("expires_at",now+MAX_AGE_SECONDS);
            List<Peer> targets=freshPeers();
            if(targets.isEmpty())return out.put("ok",false).put("reason","no_peers").put("message","Δεν βρέθηκε άλλο κινητό στην ίδια Κυψέλη.").toString();
            remember(packet.optString("packet_id"));addMessage(packet,true);persistState();notifyChanged();
            for(Peer peer:targets)io.execute(() -> sendToPeer(peer,packet));
            out.put("ok",true).put("packet_id",packet.optString("packet_id")).put("targets",targets.size()).put("queued",true).put("mode","wifi_cell_microserver");
        }catch(Throwable t){try{out.put("ok",false).put("reason","send_failed").put("error",safeMessage(t));}catch(Exception ignored){}}
        return out.toString();
    }

    private void sendToPeer(Peer peer,JSONObject packet){
        if(peer==null||peer.host==null||peer.port<=0||!running)return;
        try(Socket socket=new Socket()){
            socket.connect(new InetSocketAddress(peer.host,peer.port),2500);socket.setSoTimeout(3500);
            DataOutputStream out=new DataOutputStream(new BufferedOutputStream(socket.getOutputStream()));
            DataInputStream in=new DataInputStream(new BufferedInputStream(socket.getInputStream()));
            byte[] frame=encryptFrame(packet).toString().getBytes(StandardCharsets.UTF_8);
            if(frame.length>MAX_FRAME)throw new IllegalArgumentException("frame_too_large");
            out.writeInt(frame.length);out.write(frame);out.flush();
            int ackLen=in.readInt();if(ackLen<=0||ackLen>2048)throw new IllegalArgumentException("bad_ack");
            byte[] ack=new byte[ackLen];in.readFully(ack);
            JSONObject response=new JSONObject(new String(ack,StandardCharsets.UTF_8));
            if(!response.optBoolean("ok",false))throw new IllegalStateException("peer_rejected");
            peer.lastSeen=System.currentTimeMillis();
        }catch(Throwable t){
            if(System.currentTimeMillis()-peer.lastSeen>PEER_FRESH_MS)peers.remove(peer.nodeId);
            lastError="send_to_"+peer.nodeId+": "+safeMessage(t);notifyChanged();
        }
    }

    private boolean receivePacket(JSONObject packet,String host)throws Exception{
        if(packet==null||!PROTOCOL.equals(packet.optString("protocol")))return false;
        if(!cellHash.equals(packet.optString("cell")))return false;
        String id=packet.optString("packet_id","").trim();
        if(id.isEmpty()||seen.contains(id))return true;
        long now=System.currentTimeMillis()/1000L;
        if(packet.optLong("expires_at",0L)<now)return false;
        remember(id);addMessage(packet,false);persistState();
        String sender=packet.optString("nickname","Κοντινό μέλος");
        String kind=packet.optString("kind","text");
        String text=packet.optString("text","");
        postIncomingNotification(id,sender,text,"sos".equals(kind));
        emitCenterEvent("cell_msg_"+id,"sos".equals(kind)?"SOS από "+sender:"Μήνυμα Γειτονιάς από "+sender,text,"sos".equals(kind));
        notifyChanged();return true;
    }

    private JSONObject encryptFrame(JSONObject packet)throws Exception{
        byte[] iv=new byte[12];random.nextBytes(iv);
        Cipher c=Cipher.getInstance("AES/GCM/NoPadding");c.init(Cipher.ENCRYPT_MODE,cellKey,new GCMParameterSpec(128,iv));
        c.updateAAD((PROTOCOL+"|"+cellHash).getBytes(StandardCharsets.UTF_8));
        byte[] ct=c.doFinal(packet.toString().getBytes(StandardCharsets.UTF_8));
        return new JSONObject().put("v",1).put("cell",cellHash).put("iv",Base64.encodeToString(iv,Base64.NO_WRAP)).put("ct",Base64.encodeToString(ct,Base64.NO_WRAP));
    }

    private JSONObject decryptFrame(JSONObject frame)throws Exception{
        if(frame.optInt("v",0)!=1||!cellHash.equals(frame.optString("cell")))throw new SecurityException("wrong_cell");
        byte[] iv=Base64.decode(frame.optString("iv",""),Base64.NO_WRAP);byte[] ct=Base64.decode(frame.optString("ct",""),Base64.NO_WRAP);
        if(iv.length!=12||ct.length<16)throw new SecurityException("invalid_ciphertext");
        Cipher c=Cipher.getInstance("AES/GCM/NoPadding");c.init(Cipher.DECRYPT_MODE,cellKey,new GCMParameterSpec(128,iv));
        c.updateAAD((PROTOCOL+"|"+cellHash).getBytes(StandardCharsets.UTF_8));
        return new JSONObject(new String(c.doFinal(ct),StandardCharsets.UTF_8));
    }

    private SecretKey deriveKey(String code,String hash)throws Exception{
        PBEKeySpec spec=new PBEKeySpec(code.toCharArray(),("OK-ITHAKI-CELL-R9|"+hash).getBytes(StandardCharsets.UTF_8),150000,256);
        byte[] raw;try{raw=SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256").generateSecret(spec).getEncoded();}catch(Exception unavailable){raw=SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1").generateSecret(spec).getEncoded();}
        spec.clearPassword();return new SecretKeySpec(raw,"AES");
    }

    private void startNsd(){ registerService(); startDiscovery(); }

    private void registerService(){
        if(nsd==null)return;
        NsdServiceInfo info=new NsdServiceInfo();info.setServiceName(serviceName);info.setServiceType(SERVICE_TYPE);info.setPort(serverSocket.getLocalPort());
        try{info.setAttribute("cell",cellHash);info.setAttribute("node",nodeId);info.setAttribute("v","249530");}catch(Throwable ignored){}
        registrationListener=new NsdManager.RegistrationListener(){
            public void onRegistrationFailed(NsdServiceInfo s,int c){lastError="nsd_register_failed:"+c;notifyChanged();}
            public void onUnregistrationFailed(NsdServiceInfo s,int c){lastError="nsd_unregister_failed:"+c;notifyChanged();}
            public void onServiceRegistered(NsdServiceInfo s){if(s!=null)serviceName=s.getServiceName();notifyChanged();}
            public void onServiceUnregistered(NsdServiceInfo s){notifyChanged();}
        };
        try{nsd.registerService(info,NsdManager.PROTOCOL_DNS_SD,registrationListener);}catch(Throwable t){lastError="nsd_register_failed: "+safeMessage(t);}
    }

    private void startDiscovery(){
        if(nsd==null||!running)return;
        discoveryListener=new NsdManager.DiscoveryListener(){
            public void onDiscoveryStarted(String t){notifyChanged();}
            public void onStartDiscoveryFailed(String t,int c){lastError="nsd_discovery_start_failed:"+c;notifyChanged();}
            public void onStopDiscoveryFailed(String t,int c){lastError="nsd_discovery_stop_failed:"+c;notifyChanged();}
            public void onDiscoveryStopped(String t){notifyChanged();}
            public void onServiceFound(NsdServiceInfo s){
                if(!running||s==null||!SERVICE_TYPE.equals(s.getServiceType()))return;
                String name=s.getServiceName()==null?"":s.getServiceName();
                if(name.equals(serviceName)||!name.startsWith("ITHAKI-"+cellHash+"-"))return;
                queueResolve(s);
            }
            public void onServiceLost(NsdServiceInfo s){
                if(s==null)return;String n=nodeFromServiceName(s.getServiceName());if(!n.isEmpty())peers.remove(n);notifyChanged();
            }
        };
        try{nsd.discoverServices(SERVICE_TYPE,NsdManager.PROTOCOL_DNS_SD,discoveryListener);}catch(Throwable t){lastError="nsd_discovery_failed: "+safeMessage(t);}
    }

    private void queueResolve(NsdServiceInfo info){
        synchronized(resolveQueue){
            String name=info.getServiceName();for(NsdServiceInfo old:resolveQueue)if(String.valueOf(old.getServiceName()).equals(String.valueOf(name)))return;
            resolveQueue.add(info);
        }
        resolveNext();
    }

    @SuppressWarnings("deprecation")
    private void resolveNext(){
        if(!running||nsd==null||!resolving.compareAndSet(false,true))return;
        final NsdServiceInfo next;
        synchronized(resolveQueue){if(resolveQueue.isEmpty()){resolving.set(false);return;}next=resolveQueue.remove(0);}
        try{
            nsd.resolveService(next,new NsdManager.ResolveListener(){
                public void onResolveFailed(NsdServiceInfo s,int c){resolving.set(false);lastError="nsd_resolve_failed:"+c;resolveNext();}
                public void onServiceResolved(NsdServiceInfo s){
                    try{
                        String n=nodeFromServiceInfo(s);InetAddress h=s.getHost();int p=s.getPort();
                        if(!n.isEmpty()&&!n.equals(nodeId)&&h!=null&&p>0)upsertPeer(n,h.getHostAddress(),p,"NSD");
                    }catch(Throwable ignored){}
                    resolving.set(false);notifyChanged();resolveNext();
                }
            });
        }catch(Throwable t){resolving.set(false);lastError="nsd_resolve_failed: "+safeMessage(t);resolveNext();}
    }

    private String nodeFromServiceInfo(NsdServiceInfo s){
        try{byte[] b=s.getAttributes().get("node");if(b!=null){String n=new String(b,StandardCharsets.UTF_8).trim();if(!n.isEmpty())return n;}}catch(Throwable ignored){}
        return nodeFromServiceName(s==null?"":s.getServiceName());
    }

    private String nodeFromServiceName(String name){
        String n=name==null?"":name;String prefix="ITHAKI-"+cellHash+"-";if(!n.startsWith(prefix))return "";return "nsd_"+n.substring(prefix.length());
    }

    private void stopNsd(){
        if(nsd==null)return;
        try{if(discoveryListener!=null)nsd.stopServiceDiscovery(discoveryListener);}catch(Exception ignored){}
        try{if(registrationListener!=null)nsd.unregisterService(registrationListener);}catch(Exception ignored){}
        discoveryListener=null;registrationListener=null;resolveQueue.clear();resolving.set(false);
    }

    private void startRadar(){
        io.execute(() -> {
            try{
                DatagramSocket ds=new DatagramSocket(null);ds.setReuseAddress(true);ds.setBroadcast(true);ds.setSoTimeout(1500);ds.bind(new InetSocketAddress(UDP_PORT));radarSocket=ds;
                byte[] buf=new byte[2048];
                while(running&&!ds.isClosed()){
                    try{
                        DatagramPacket p=new DatagramPacket(buf,buf.length);ds.receive(p);
                        String json=new String(p.getData(),p.getOffset(),p.getLength(),StandardCharsets.UTF_8);JSONObject b=new JSONObject(json);
                        if(!PROTOCOL.equals(b.optString("protocol"))||!cellHash.equals(b.optString("cell")))continue;
                        String n=b.optString("node","");int port=b.optInt("port",0);if(n.isEmpty()||n.equals(nodeId)||port<=0)continue;
                        upsertPeer(n,p.getAddress().getHostAddress(),port,"Ραντάρ");
                    }catch(java.net.SocketTimeoutException ignored){}
                    catch(Throwable t){if(running)lastError="radar_receive: "+safeMessage(t);}
                }
            }catch(Throwable t){if(running)lastError="radar_start: "+safeMessage(t);notifyChanged();}
        });
        radarFuture=timers.scheduleAtFixedRate(this::sendBeacon,0,2500,TimeUnit.MILLISECONDS);
    }

    private void sendBeacon(){
        if(!running||serverSocket==null||serverSocket.isClosed())return;
        try{
            JSONObject b=new JSONObject().put("protocol",PROTOCOL).put("cell",cellHash).put("node",nodeId).put("port",serverSocket.getLocalPort()).put("ts",System.currentTimeMillis());
            byte[] data=b.toString().getBytes(StandardCharsets.UTF_8);
            DatagramSocket s=new DatagramSocket();s.setBroadcast(true);
            sendDatagram(s,data,InetAddress.getByName("255.255.255.255"));
            InetAddress localBroadcast=wifiBroadcastAddress();if(localBroadcast!=null)sendDatagram(s,data,localBroadcast);
            s.close();
        }catch(Throwable t){if(running)lastError="radar_send: "+safeMessage(t);}
    }

    private void sendDatagram(DatagramSocket s,byte[] data,InetAddress address){try{s.send(new DatagramPacket(data,data.length,address,UDP_PORT));}catch(Exception ignored){}}

    private InetAddress wifiBroadcastAddress(){
        try{
            WifiManager wm=(WifiManager)getApplicationContext().getSystemService(WIFI_SERVICE);if(wm==null||wm.getDhcpInfo()==null)return null;
            int ip=wm.getDhcpInfo().ipAddress,mask=wm.getDhcpInfo().netmask;if(ip==0||mask==0)return null;int broadcast=(ip&mask)|~mask;
            byte[] q={(byte)(broadcast&0xff),(byte)((broadcast>>8)&0xff),(byte)((broadcast>>16)&0xff),(byte)((broadcast>>24)&0xff)};return InetAddress.getByAddress(q);
        }catch(Throwable t){return null;}
    }

    private void upsertPeer(String n,String host,int port,String source){
        if(n==null||n.isEmpty()||n.equals(nodeId)||host==null||host.isEmpty()||port<=0)return;
        Peer p=peers.get(n);if(p==null){p=new Peer(n);peers.put(n,p);}p.host=host;p.port=port;p.source=source;p.lastSeen=System.currentTimeMillis();notifyChanged();
    }

    private List<Peer> freshPeers(){
        long now=System.currentTimeMillis();List<Peer> out=new ArrayList<Peer>();
        for(Peer p:peers.values()){if(now-p.lastSeen<=PEER_FRESH_MS&&p.port>0&&p.host!=null)out.add(p);else peers.remove(p.nodeId);}
        Collections.sort(out,Comparator.comparing(a -> a.nodeId));return out;
    }

    private void watchdog(){
        if(!running)return;
        if(serverSocket==null||serverSocket.isClosed()){failSafe("Ο μικροserver δεν είναι πλέον ενεργός.");return;}
        freshPeers();notifyChanged();
    }

    private synchronized void failSafe(String reason){
        if(reason==null||reason.trim().isEmpty())reason="Άγνωστο σφάλμα Κυψέλης.";
        lastError=reason;
        stopCellInternal(false);
        try{stopForeground(true);}catch(Exception ignored){}
        postSystemNotification("Η Κυψέλη σταμάτησε με ασφάλεια",reason,false);
        emitCenterEvent("cell_error_"+System.currentTimeMillis(),"Ασφαλής επιστροφή στην κανονική λειτουργία",reason,true);
        notifyChanged();stopSelf();
    }

    public String getStatusJson(){
        JSONObject o=new JSONObject();try{o.put("ok",true).put("version",249530).put("running",running).put("mode","wifi_cell_microserver").put("node_id",nodeId).put("cell_hash",cellHash).put("service_name",serviceName).put("port",serverSocket==null?0:serverSocket.getLocalPort()).put("connected",freshPeers().size()).put("messages",messages.size()).put("last_error",lastError).put("manual_only",true).put("text_only",true).put("normal_server_untouched",true).put("isolated_process",true);}catch(Exception ignored){}return o.toString();
    }

    public String getPeersJson(){
        JSONArray a=new JSONArray();for(Peer p:freshPeers()){try{a.put(new JSONObject().put("node_id",p.nodeId).put("name","Κινητό "+shortNode(p.nodeId)).put("host",p.host).put("port",p.port).put("source",p.source).put("connected",true).put("last_seen",p.lastSeen));}catch(Exception ignored){}}return a.toString();
    }

    public String getMessagesJson(){JSONArray a=new JSONArray();synchronized(messages){for(JSONObject m:messages)a.put(m);}return a.toString();}
    public void clearMessages(){messages.clear();seen.clear();persistState();notifyChanged();}

    private void addMessage(JSONObject packet,boolean own)throws Exception{
        JSONObject row=new JSONObject(packet.toString()).put("own",own);synchronized(messages){messages.add(row);while(messages.size()>MAX_MESSAGES)messages.remove(0);}
    }

    private void remember(String id){if(id==null||id.isEmpty())return;synchronized(seen){seen.add(id);while(seen.size()>3000){Iterator<String> it=seen.iterator();if(it.hasNext()){it.next();it.remove();}else break;}}}

    private void loadState(){
        SharedPreferences sp=getSharedPreferences(PREFS,MODE_PRIVATE);nodeId=sp.getString(PREF_NODE_ID,"");if(nodeId==null||nodeId.isEmpty()){nodeId="cell_"+UUID.randomUUID().toString().replace("-","").substring(0,16);sp.edit().putString(PREF_NODE_ID,nodeId).apply();}
        try{JSONArray a=new JSONArray(sp.getString(PREF_MESSAGES,"[]"));long now=System.currentTimeMillis()/1000L;for(int i=0;i<a.length();i++){JSONObject m=a.optJSONObject(i);if(m!=null&&m.optLong("expires_at",now+1)>=now){messages.add(m);remember(m.optString("packet_id"));}}}catch(Exception ignored){}
    }

    private void persistState(){JSONArray a=new JSONArray();synchronized(messages){for(JSONObject m:messages)a.put(m);}getSharedPreferences(PREFS,MODE_PRIVATE).edit().putString(PREF_MESSAGES,a.toString()).putString(PREF_NODE_ID,nodeId).apply();}

    private void acquireMulticastLock(){try{WifiManager wm=(WifiManager)getApplicationContext().getSystemService(WIFI_SERVICE);if(wm!=null){multicastLock=wm.createMulticastLock("ok_ithaki_cell_v249530");multicastLock.setReferenceCounted(false);multicastLock.acquire();}}catch(Throwable ignored){}}
    private void releaseMulticastLock(){try{if(multicastLock!=null&&multicastLock.isHeld())multicastLock.release();}catch(Throwable ignored){}multicastLock=null;}

    private void notifyChanged(){List<Listener> snapshot; synchronized(listeners){snapshot=new ArrayList<Listener>(listeners);}for(Listener l:snapshot)try{l.onCellChanged();}catch(Exception ignored){}}

    private void createChannels(){
        if(Build.VERSION.SDK_INT<26)return;NotificationManager nm=(NotificationManager)getSystemService(NOTIFICATION_SERVICE);if(nm==null)return;
        NotificationChannel st=new NotificationChannel(CHANNEL_STATUS,"OK ITHAKI · Κυψέλη Wi-Fi",NotificationManager.IMPORTANCE_LOW);st.setDescription("Χειροκίνητος μικροserver μηνυμάτων στο ίδιο Wi-Fi");nm.createNotificationChannel(st);
        NotificationChannel msg=new NotificationChannel(CHANNEL_MESSAGE,"OK ITHAKI · Μηνύματα Γειτονιάς",NotificationManager.IMPORTANCE_DEFAULT);nm.createNotificationChannel(msg);
        NotificationChannel sos=new NotificationChannel(CHANNEL_SOS,"OK ITHAKI · SOS Γειτονιάς",NotificationManager.IMPORTANCE_HIGH);sos.enableVibration(true);nm.createNotificationChannel(sos);
    }

    private android.app.Notification buildStatusNotification(String text){return new NotificationCompat.Builder(this,CHANNEL_STATUS).setSmallIcon(com.okithaki.autoapp.R.drawable.ic_launcher).setContentTitle("OK ITHAKI · Κυψέλη Wi‑Fi").setContentText(text).setOngoing(true).setContentIntent(openActivityIntent(FOREGROUND_ID)).build();}
    private void updateForeground(String text){try{NotificationManager nm=(NotificationManager)getSystemService(NOTIFICATION_SERVICE);if(nm!=null)nm.notify(FOREGROUND_ID,buildStatusNotification(text));}catch(Exception ignored){}}

    private void postIncomingNotification(String id,String sender,String text,boolean sos){
        String title=sos?"🆘 SOS Γειτονιάς · "+sender:"Μήνυμα Γειτονιάς · "+sender;
        postSystemNotification(title,text,sos,id.hashCode());
    }
    private void postSystemNotification(String title,String text,boolean important){postSystemNotification(title,text,important,(title+text).hashCode());}
    private void postSystemNotification(String title,String text,boolean important,int id){
        try{NotificationManager nm=(NotificationManager)getSystemService(NOTIFICATION_SERVICE);if(nm==null)return;NotificationCompat.Builder b=new NotificationCompat.Builder(this,important?CHANNEL_SOS:CHANNEL_MESSAGE).setSmallIcon(com.okithaki.autoapp.R.drawable.ic_launcher).setContentTitle(title).setContentText(text).setStyle(new NotificationCompat.BigTextStyle().bigText(text)).setAutoCancel(true).setContentIntent(openActivityIntent(id));if(important)b.setPriority(NotificationCompat.PRIORITY_HIGH);nm.notify(id,b.build());}catch(Exception ignored){}
    }
    private PendingIntent openActivityIntent(int id){Intent i=new Intent(this,EmergencyNeighborhoodActivity.class).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_SINGLE_TOP);return PendingIntent.getActivity(this,id,i,PendingIntent.FLAG_UPDATE_CURRENT|(Build.VERSION.SDK_INT>=23?PendingIntent.FLAG_IMMUTABLE:0));}

    private void emitCenterEvent(String eventId,String title,String body,boolean important){
        try{Intent i=new Intent(this,NeighborhoodEventReceiver.class);i.setAction(NeighborhoodEventReceiver.ACTION_EVENT);i.putExtra("event_id",eventId).putExtra("title",title).putExtra("body",body).putExtra("important",important).putExtra("created_at",System.currentTimeMillis());sendBroadcast(i);}catch(Exception ignored){}
    }

    private static String shortHash(String s)throws Exception{byte[] d=MessageDigest.getInstance("SHA-256").digest(s.getBytes(StandardCharsets.UTF_8));StringBuilder b=new StringBuilder();for(int i=0;i<5;i++)b.append(String.format(Locale.ROOT,"%02x",d[i]));return b.toString();}
    private static String shortNode(String n){String s=n==null?"":n;return s.substring(Math.max(0,s.length()-6));}
    private static String safeMessage(Throwable t){if(t==null)return"unknown";String m=t.getMessage();return m==null||m.trim().isEmpty()?t.getClass().getSimpleName():m;}
    private static void closeQuietly(java.io.Closeable c){try{if(c!=null)c.close();}catch(Exception ignored){}}
}
