package com.okithaki.autoapp;

import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;

import androidx.core.app.NotificationCompat;

import com.google.android.gms.common.api.Status;
import com.google.android.gms.nearby.Nearby;
import com.google.android.gms.nearby.connection.AdvertisingOptions;
import com.google.android.gms.nearby.connection.ConnectionInfo;
import com.google.android.gms.nearby.connection.ConnectionLifecycleCallback;
import com.google.android.gms.nearby.connection.ConnectionResolution;
import com.google.android.gms.nearby.connection.ConnectionsClient;
import com.google.android.gms.nearby.connection.ConnectionsStatusCodes;
import com.google.android.gms.nearby.connection.DiscoveredEndpointInfo;
import com.google.android.gms.nearby.connection.DiscoveryOptions;
import com.google.android.gms.nearby.connection.EndpointDiscoveryCallback;
import com.google.android.gms.nearby.connection.Payload;
import com.google.android.gms.nearby.connection.PayloadCallback;
import com.google.android.gms.nearby.connection.PayloadTransferUpdate;
import com.google.android.gms.nearby.connection.Strategy;

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

import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;

/**
 * V249442 emergency neighborhood transport.
 * Runs in the isolated :neighborhood process and starts only after a user action.
 * Nearby Connections uses Bluetooth/BLE/Wi-Fi and works without internet.
 */
public class EmergencyNeighborhoodService extends Service {
    public static final String ACTION_START = "com.okithaki.autoapp.NEIGHBORHOOD_START_V249442";
    public static final String ACTION_STOP = "com.okithaki.autoapp.NEIGHBORHOOD_STOP_V249442";
    public static final String EXTRA_NICKNAME = "nickname";

    private static final String SERVICE_ID = "com.okithaki.autoapp.emergency.neighborhood.v249442";
    private static final Strategy STRATEGY = Strategy.P2P_CLUSTER;
    private static final String PREFS = "ok_ithaki_neighborhood_v249442";
    private static final String PREF_MESSAGES = "messages";
    private static final String PREF_PENDING = "pending";
    private static final String PREF_NODE_ID = "node_id";
    private static final String CHANNEL_ID = "ok_ithaki_neighborhood_v249442";
    private static final int NOTIFICATION_ID = 249442;
    private static final long MAX_AGE_SECONDS = 24L * 60L * 60L;
    private static final int MAX_MESSAGES = 500;
    private static final int MAX_PENDING = 300;

    public interface Listener {
        void onNeighborhoodChanged();
    }

    public final class LocalBinder extends Binder {
        public EmergencyNeighborhoodService getService() {
            return EmergencyNeighborhoodService.this;
        }
    }

    private final IBinder binder = new LocalBinder();
    private final Set<String> connectedEndpoints = Collections.synchronizedSet(new LinkedHashSet<String>());
    private final Set<String> requestedEndpoints = Collections.synchronizedSet(new LinkedHashSet<String>());
    private final Map<String, String> endpointNames = Collections.synchronizedMap(new LinkedHashMap<String, String>());
    private final List<JSONObject> messages = Collections.synchronizedList(new ArrayList<JSONObject>());
    private final List<JSONObject> pending = Collections.synchronizedList(new ArrayList<JSONObject>());
    private final Set<String> seen = Collections.synchronizedSet(new LinkedHashSet<String>());
    private final Set<Listener> listeners = Collections.synchronizedSet(new LinkedHashSet<Listener>());

    private ConnectionsClient client;
    private volatile boolean running = false;
    private volatile String lastError = "";
    private volatile String nickname = "Μέλος";
    private String nodeId = "";

    @Override
    public void onCreate() {
        super.onCreate();
        createNotificationChannel();
        loadState();
        try {
            client = Nearby.getConnectionsClient(this);
        } catch (Throwable t) {
            lastError = "nearby_unavailable: " + safeMessage(t);
        }
    }

    @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())) {
            stopNeighborhood();
            return START_NOT_STICKY;
        }
        if (intent != null && ACTION_START.equals(intent.getAction())) {
            String n = intent.getStringExtra(EXTRA_NICKNAME);
            try {
                startNeighborhood(n);
            } catch (Throwable t) {
                running = false;
                lastError = "start_failed: " + safeMessage(t);
                stopTransportOnly();
                try { stopForeground(true); } catch (Exception ignored) { }
                notifyChanged();
                stopSelf();
            }
        }
        return START_NOT_STICKY;
    }

    @Override
    public void onDestroy() {
        stopTransportOnly();
        super.onDestroy();
    }

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

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

    public synchronized void startNeighborhood(String requestedNickname) {
        if (requestedNickname != null && !requestedNickname.trim().isEmpty()) {
            nickname = requestedNickname.trim();
        }
        if (client == null) {
            lastError = "Google Play services Nearby δεν είναι διαθέσιμο.";
            notifyChanged();
            return;
        }
        if (running) {
            restartDiscovery();
            return;
        }
        running = true;
        lastError = "";
        try {
            startForeground(NOTIFICATION_ID, buildNotification("Γειτονιά ενεργή · χωρίς internet"));
            startAdvertising();
            startDiscovery();
        } catch (Throwable t) {
            running = false;
            lastError = "start_failed: " + safeMessage(t);
            stopTransportOnly();
            try { stopForeground(true); } catch (Exception ignored) { }
            notifyChanged();
            stopSelf();
            return;
        }
        notifyChanged();
    }

    public synchronized void stopNeighborhood() {
        running = false;
        stopTransportOnly();
        persistState();
        try { stopForeground(true); } catch (Exception ignored) { }
        stopSelf();
        notifyChanged();
    }

    private void stopTransportOnly() {
        if (client == null) return;
        try { client.stopAdvertising(); } catch (Exception ignored) { }
        try { client.stopDiscovery(); } catch (Exception ignored) { }
        try { client.stopAllEndpoints(); } catch (Exception ignored) { }
        connectedEndpoints.clear();
        requestedEndpoints.clear();
        endpointNames.clear();
    }

    public synchronized void restartDiscovery() {
        if (!running || client == null) return;
        try { client.stopDiscovery(); } catch (Exception ignored) { }
        startDiscovery();
        notifyChanged();
    }

    private void startAdvertising() {
        AdvertisingOptions options = new AdvertisingOptions.Builder().setStrategy(STRATEGY).build();
        client.startAdvertising(nickname, SERVICE_ID, connectionLifecycleCallback, options)
                .addOnFailureListener(e -> {
                    lastError = "advertising_failed: " + safeMessage(e);
                    notifyChanged();
                });
    }

    private void startDiscovery() {
        DiscoveryOptions options = new DiscoveryOptions.Builder().setStrategy(STRATEGY).build();
        client.startDiscovery(SERVICE_ID, endpointDiscoveryCallback, options)
                .addOnFailureListener(e -> {
                    lastError = "discovery_failed: " + safeMessage(e);
                    notifyChanged();
                });
    }

    private final EndpointDiscoveryCallback endpointDiscoveryCallback = new EndpointDiscoveryCallback() {
        @Override
        public void onEndpointFound(String endpointId, DiscoveredEndpointInfo info) {
            if (!running || endpointId == null) return;
            endpointNames.put(endpointId, info == null ? "Κοντινό κινητό" : info.getEndpointName());
            synchronized (requestedEndpoints) {
                if (connectedEndpoints.contains(endpointId) || requestedEndpoints.contains(endpointId)) return;
                requestedEndpoints.add(endpointId);
            }
            client.requestConnection(nickname, endpointId, connectionLifecycleCallback)
                    .addOnFailureListener(e -> {
                        requestedEndpoints.remove(endpointId);
                        lastError = "connection_request_failed: " + safeMessage(e);
                        notifyChanged();
                    });
            notifyChanged();
        }

        @Override
        public void onEndpointLost(String endpointId) {
            requestedEndpoints.remove(endpointId);
            if (!connectedEndpoints.contains(endpointId)) endpointNames.remove(endpointId);
            notifyChanged();
        }
    };

    private final ConnectionLifecycleCallback connectionLifecycleCallback = new ConnectionLifecycleCallback() {
        @Override
        public void onConnectionInitiated(String endpointId, ConnectionInfo connectionInfo) {
            if (connectionInfo != null) endpointNames.put(endpointId, connectionInfo.getEndpointName());
            try {
                client.acceptConnection(endpointId, payloadCallback)
                        .addOnFailureListener(e -> {
                            requestedEndpoints.remove(endpointId);
                            lastError = "accept_failed: " + safeMessage(e);
                            notifyChanged();
                        });
            } catch (Exception e) {
                requestedEndpoints.remove(endpointId);
                lastError = "accept_failed: " + safeMessage(e);
                notifyChanged();
            }
        }

        @Override
        public void onConnectionResult(String endpointId, ConnectionResolution result) {
            requestedEndpoints.remove(endpointId);
            int code = result == null || result.getStatus() == null
                    ? ConnectionsStatusCodes.STATUS_ERROR
                    : result.getStatus().getStatusCode();
            if (code == ConnectionsStatusCodes.STATUS_OK) {
                connectedEndpoints.add(endpointId);
                lastError = "";
                flushPendingTo(endpointId);
            } else {
                Status status = result == null ? null : result.getStatus();
                lastError = "connection_failed: " + (status == null ? String.valueOf(code) : status.getStatusMessage());
            }
            notifyChanged();
        }

        @Override
        public void onDisconnected(String endpointId) {
            connectedEndpoints.remove(endpointId);
            requestedEndpoints.remove(endpointId);
            notifyChanged();
        }
    };

    private final PayloadCallback payloadCallback = new PayloadCallback() {
        @Override
        public void onPayloadReceived(String endpointId, Payload payload) {
            if (payload == null || payload.getType() != Payload.Type.BYTES || payload.asBytes() == null) return;
            try {
                String json = new String(payload.asBytes(), StandardCharsets.UTF_8);
                receivePacket(new JSONObject(json), endpointId);
            } catch (Exception e) {
                lastError = "invalid_packet: " + safeMessage(e);
                notifyChanged();
            }
        }

        @Override
        public void onPayloadTransferUpdate(String endpointId, PayloadTransferUpdate update) {
            // Byte payloads are complete when delivered to onPayloadReceived.
        }
    };

    public synchronized String sendMessage(String text, String requestedNickname) {
        JSONObject out = new JSONObject();
        try {
            if (!running) {
                out.put("ok", false).put("reason", "node_not_started");
                return out.toString();
            }
            String clean = text == null ? "" : text.trim();
            if (clean.isEmpty()) {
                out.put("ok", false).put("reason", "empty_message");
                return out.toString();
            }
            if (clean.length() > 4000) clean = clean.substring(0, 4000);
            if (requestedNickname != null && !requestedNickname.trim().isEmpty()) nickname = requestedNickname.trim();
            long now = System.currentTimeMillis() / 1000L;
            JSONObject packet = new JSONObject();
            packet.put("protocol", "ITHAKI-NEARBY-V249442");
            packet.put("packet_id", nodeId + "_" + UUID.randomUUID());
            packet.put("from_id", nodeId);
            packet.put("nickname", nickname);
            packet.put("text", clean);
            packet.put("created_at", now);
            packet.put("expires_at", now + MAX_AGE_SECONDS);
            packet.put("ttl", 8);
            packet.put("hops", 0);
            packet.put("route", new JSONArray().put(nodeId));
            remember(packet.optString("packet_id"));
            addMessage(packet, true);
            addPending(packet);
            sendPacketToConnected(packet, null);
            persistState();
            notifyChanged();
            out.put("ok", true).put("packet_id", packet.optString("packet_id"));
            out.put("connected", connectedEndpoints.size());
        } catch (Exception e) {
            try { out.put("ok", false).put("reason", "send_failed").put("error", safeMessage(e)); } catch (Exception ignored) { }
        }
        return out.toString();
    }

    private void receivePacket(JSONObject incoming, String sourceEndpoint) throws Exception {
        if (!"ITHAKI-NEARBY-V249442".equals(incoming.optString("protocol"))) return;
        String id = incoming.optString("packet_id", "").trim();
        if (id.isEmpty() || seen.contains(id)) return;
        long now = System.currentTimeMillis() / 1000L;
        if (incoming.optLong("expires_at", 0L) < now) return;
        int ttl = incoming.optInt("ttl", 0);
        int hops = incoming.optInt("hops", 0);
        if (ttl <= 0 || hops > 8) return;
        remember(id);
        JSONObject stored = new JSONObject(incoming.toString());
        addMessage(stored, false);
        if (ttl > 1 && hops < 8) {
            JSONObject forwarded = new JSONObject(incoming.toString());
            forwarded.put("ttl", ttl - 1);
            forwarded.put("hops", hops + 1);
            JSONArray route = forwarded.optJSONArray("route");
            if (route == null) route = new JSONArray();
            route.put(nodeId);
            forwarded.put("route", route);
            addPending(forwarded);
            sendPacketToConnected(forwarded, sourceEndpoint);
        }
        persistState();
        notifyChanged();
    }

    private void sendPacketToConnected(JSONObject packet, String exceptEndpoint) {
        if (!running || client == null || packet == null) return;
        List<String> targets = new ArrayList<String>();
        synchronized (connectedEndpoints) {
            for (String endpoint : connectedEndpoints) {
                if (endpoint != null && !endpoint.equals(exceptEndpoint)) targets.add(endpoint);
            }
        }
        if (targets.isEmpty()) return;
        try {
            byte[] bytes = packet.toString().getBytes(StandardCharsets.UTF_8);
            client.sendPayload(targets, Payload.fromBytes(bytes))
                    .addOnFailureListener(e -> {
                        lastError = "send_payload_failed: " + safeMessage(e);
                        notifyChanged();
                    });
        } catch (Exception e) {
            lastError = "send_payload_failed: " + safeMessage(e);
        }
    }

    private void flushPendingTo(String endpointId) {
        if (endpointId == null || client == null) return;
        trimState();
        List<JSONObject> snapshot;
        synchronized (pending) { snapshot = new ArrayList<JSONObject>(pending); }
        for (JSONObject packet : snapshot) {
            try {
                client.sendPayload(endpointId, Payload.fromBytes(packet.toString().getBytes(StandardCharsets.UTF_8)));
            } catch (Exception ignored) { }
        }
    }

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

    private void addPending(JSONObject packet) throws Exception {
        String id = packet.optString("packet_id", "");
        synchronized (pending) {
            for (JSONObject old : pending) if (id.equals(old.optString("packet_id"))) return;
            pending.add(new JSONObject(packet.toString()));
            while (pending.size() > MAX_PENDING) pending.remove(0);
        }
    }

    private void remember(String id) {
        if (id == null || id.trim().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.trim().isEmpty()) {
            nodeId = "ithaki_" + UUID.randomUUID().toString().replace("-", "").substring(0, 16);
            sp.edit().putString(PREF_NODE_ID, nodeId).apply();
        }
        try {
            JSONArray m = new JSONArray(sp.getString(PREF_MESSAGES, "[]"));
            for (int i = 0; i < m.length(); i++) {
                JSONObject row = m.optJSONObject(i);
                if (row != null) {
                    messages.add(row);
                    remember(row.optString("packet_id", ""));
                }
            }
            JSONArray p = new JSONArray(sp.getString(PREF_PENDING, "[]"));
            for (int i = 0; i < p.length(); i++) {
                JSONObject row = p.optJSONObject(i);
                if (row != null) pending.add(row);
            }
        } catch (Exception ignored) { }
        trimState();
    }

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

    private void trimState() {
        long now = System.currentTimeMillis() / 1000L;
        synchronized (messages) {
            for (int i = messages.size() - 1; i >= 0; i--) {
                long exp = messages.get(i).optLong("expires_at", now + MAX_AGE_SECONDS);
                if (exp < now) messages.remove(i);
            }
            while (messages.size() > MAX_MESSAGES) messages.remove(0);
        }
        synchronized (pending) {
            for (int i = pending.size() - 1; i >= 0; i--) {
                long exp = pending.get(i).optLong("expires_at", 0L);
                if (exp > 0L && exp < now) pending.remove(i);
            }
            while (pending.size() > MAX_PENDING) pending.remove(0);
        }
    }

    public String getStatusJson() {
        JSONObject out = new JSONObject();
        try {
            out.put("ok", true);
            out.put("version", "249442");
            out.put("node_id", nodeId);
            out.put("running", running);
            out.put("transport", "Nearby Connections P2P_CLUSTER");
            out.put("internet_required", false);
            out.put("connected", connectedEndpoints.size());
            out.put("discovered_or_requested", requestedEndpoints.size());
            out.put("messages", messages.size());
            out.put("pending", pending.size());
            out.put("last_error", lastError == null ? "" : lastError);
            out.put("main_app_process_isolated", true);
        } catch (Exception ignored) { }
        return out.toString();
    }

    public String getPeersJson() {
        JSONArray arr = new JSONArray();
        synchronized (endpointNames) {
            for (Map.Entry<String, String> e : endpointNames.entrySet()) {
                JSONObject o = new JSONObject();
                try {
                    o.put("endpoint_id", e.getKey());
                    o.put("name", e.getValue());
                    o.put("connected", connectedEndpoints.contains(e.getKey()));
                    arr.put(o);
                } catch (Exception ignored) { }
            }
        }
        return arr.toString();
    }

    public String getMessagesJson() {
        JSONArray arr = new JSONArray();
        synchronized (messages) {
            for (JSONObject row : messages) arr.put(row);
        }
        return arr.toString();
    }

    public void clearMessages() {
        messages.clear();
        pending.clear();
        seen.clear();
        persistState();
        notifyChanged();
    }

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

    private void createNotificationChannel() {
        if (Build.VERSION.SDK_INT < 26) return;
        NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
        if (nm == null) return;
        NotificationChannel ch = new NotificationChannel(CHANNEL_ID, "Γειτονιά έκτακτης ανάγκης", NotificationManager.IMPORTANCE_LOW);
        ch.setDescription("Τοπική επικοινωνία χωρίς internet");
        nm.createNotificationChannel(ch);
    }

    private android.app.Notification buildNotification(String text) {
        Intent open = new Intent(this, EmergencyNeighborhoodActivity.class);
        PendingIntent pi = PendingIntent.getActivity(this, 249442, open,
                PendingIntent.FLAG_UPDATE_CURRENT | (Build.VERSION.SDK_INT >= 23 ? PendingIntent.FLAG_IMMUTABLE : 0));
        return new NotificationCompat.Builder(this, CHANNEL_ID)
                .setSmallIcon(android.R.drawable.stat_sys_warning)
                .setContentTitle("OK ITHAKI · Γειτονιά")
                .setContentText(text)
                .setOngoing(true)
                .setContentIntent(pi)
                .build();
    }

    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;
    }
}
