package cn.waterpolo.network;

import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.wifi.WifiManager;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;

import com.getcapacitor.Bridge;
import com.getcapacitor.JSObject;
import com.getcapacitor.Plugin;

import org.json.JSONObject;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.Collections;
import java.util.Iterator;
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.AtomicInteger;

public class NetWork {
    private final String TAG = "NetWork";

    // TCP
    private Socket tcpSocket;
    private PrintWriter tcpWriter;
    private BufferedReader tcpReader;
    private String tcpIp;
    private int tcpPort = -1;
    private boolean tcpReconnect = false;
    private int tcpReconnectCount = 0;
    private int tcpMaxReconnect = 10;
    private int tcpHeartbeatInterval = 30;
    private ScheduledFuture<?> heartbeatFuture;

    private final ScheduledExecutorService scheduledExecutor = Executors.newScheduledThreadPool(2);
    private final ExecutorService tcpStateExecutor = Executors.newSingleThreadExecutor();

    private enum ConnectionState { DISCONNECTED, CONNECTING, CONNECTED }
    private volatile ConnectionState tcpState = ConnectionState.DISCONNECTED;
    private final AtomicInteger socketGeneration = new AtomicInteger(0);

    // UDP
    private DatagramSocket udpSocket;
    private int udpPort = -1;
    private volatile boolean udpRunning = false;
    private final int udpRepeat = 7;
    private final Set<String> udpMsgIdCache = Collections.newSetFromMap(new ConcurrentHashMap<>());
    private final int UDP_MSG_CACHE_MAX = 2000;
    private final int UDP_MSG_CACHE_CLEAN_INTERVAL = 60;
    private final int UDP_BUFFER_SIZE = 65507;

    private Bridge bridge;
    private Plugin plugin;
    private final Handler mainHandler = new Handler(Looper.getMainLooper());
    // 使用弱引用持有监听器，防止内存泄漏
    private WeakReference<NetWorkListener> listener;
    private final Context appContext; // [修复] 持有ApplicationContext
    public NetWork(Context context) {
        this.appContext = context;
        scheduledExecutor.scheduleWithFixedDelay(this::cleanUdpMsgCache, UDP_MSG_CACHE_CLEAN_INTERVAL,
                UDP_MSG_CACHE_CLEAN_INTERVAL, TimeUnit.SECONDS);
    }
    public interface NetWorkListener {
        void onEvent(String event, JSObject data);
    }
    public void setListener(NetWorkListener listener) {
        this.listener = new WeakReference<>(listener);
    }
    public void destroy() {
        Log.d(TAG, "Destroying NetWork instance and shutting down resources.");
        closeTcp();
        closeUdp();
        if (scheduledExecutor != null && !scheduledExecutor.isShutdown()) {
            scheduledExecutor.shutdownNow();
        }
        if (tcpStateExecutor != null && !tcpStateExecutor.isShutdown()) {
            tcpStateExecutor.shutdownNow();
        }
    }

    // --- TCP ---

    public void createTcpConnection(String ip, int port) {
        tcpStateExecutor.execute(() -> {
            if (tcpState != ConnectionState.DISCONNECTED) {
                closeTcpInternal();
            }
            // 确保UDP也在后台关闭
            closeUdpInternal();
//            this.bridge = bridge;
//            this.plugin = plugin;
            this.tcpIp = ip;
            this.tcpPort = port;
            this.tcpReconnect = true;
            this.tcpReconnectCount = 0;
            Log.i(TAG, "Requesting TCP connection to " + ip + ":" + port);
            connectTcpInternal();
        });
    }

    private void connectTcpInternal() {
        // 此方法必须在 tcpStateExecutor 中调用
        if (tcpState != ConnectionState.DISCONNECTED) {
            Log.w(TAG, "Connection attempt ignored, not in DISCONNECTED state: " + tcpState);
            return;
        }
        tcpState = ConnectionState.CONNECTING;
        Log.d(TAG, "TCP state changed to CONNECTING");

        try {
            Socket newSocket = new Socket();
            newSocket.setKeepAlive(true);
            newSocket.setSoTimeout(tcpHeartbeatInterval * 1000 * 2);
            Log.d(TAG, "Attempting to connect to " + tcpIp + ":" + tcpPort);
            newSocket.connect(new InetSocketAddress(tcpIp, tcpPort), 5000);
            onTcpConnected(newSocket);
        } catch (IOException e) {
            Log.e(TAG, "TCP connection failed: " + e.getMessage());
            onTcpDisconnected();
        }
    }

    private void onTcpConnected(Socket connectedSocket) {
        // 此方法必须在 tcpStateExecutor 中调用
        tcpState = ConnectionState.CONNECTED;
        Log.d(TAG, "TCP state changed to CONNECTED");

        final int currentGeneration = socketGeneration.incrementAndGet();

        try {
            // 所有流的创建也在状态机线程中
            this.tcpSocket = connectedSocket;
            this.tcpWriter = new PrintWriter(this.tcpSocket.getOutputStream(), true);
            final BufferedReader readerForThread = new BufferedReader(new InputStreamReader(this.tcpSocket.getInputStream()));

            Thread receiveThread = new Thread(() -> startReceiveLoop(readerForThread, currentGeneration));
            receiveThread.setDaemon(true);
            receiveThread.start();

            this.tcpReconnectCount = 0;
            startTcpHeartbeat();

            Log.i(TAG, "TCP connection successful to " + tcpIp + ":" + tcpPort);
            notifyListeners("connected", makeConnectionInfo("tcp", tcpIp, tcpPort));

        } catch (IOException e) {
            Log.e(TAG, "Failed to create streams", e);
            onTcpDisconnected();
        }
    }

    private void startReceiveLoop(final BufferedReader reader, final int generation) {
        Log.d(TAG, "Receive thread started for socket generation " + generation);
        try {
            String line;
            while ((line = reader.readLine()) != null) {
                if (socketGeneration.get() != generation) {
                    Log.w(TAG, "Stale receive thread (gen " + generation + ") is stopping.");
                    break;
                }
                line = line.trim();
                if (line.isEmpty()) continue;

                JSObject data = parseJsonOrError(line);
                Log.i(TAG, "TCP Data Received: " + data);
                notifyListeners("data", data);
            }
            Log.d(TAG, "Receive loop exited normally (stream closed by peer). Generation: " + generation);

        } catch (IOException e) {
            if (socketGeneration.get() == generation) {
                Log.e(TAG, "SocketException on active thread (gen " + generation + "): " + e.getMessage());
            } else {
                Log.d(TAG, "SocketException on stale thread (gen " + generation + "), ignoring.");
            }
        } finally {
            Log.d(TAG, "Receive thread finished for socket generation " + generation);
            if (socketGeneration.get() == generation) {
                Log.i(TAG, "Active thread (gen " + generation + ") is triggering disconnection handler.");
                tcpStateExecutor.execute(this::onTcpDisconnected);
            }
        }
    }

    private void onTcpDisconnected() {
        // 此方法必须在 tcpStateExecutor 中调用
        if (tcpState == ConnectionState.DISCONNECTED) return;
        Log.d(TAG, "TCP state changing to DISCONNECTED");
        tcpState = ConnectionState.DISCONNECTED;
        socketGeneration.incrementAndGet();

        closeTcpInternal();
        stopTcpHeartbeat();

        notifyListeners("disconnected", makeConnectionInfo("tcp", tcpIp, tcpPort));

        if (tcpReconnect && tcpReconnectCount < tcpMaxReconnect) {
            tcpReconnectCount++;
            long delay = Math.min(1000L * (1L << tcpReconnectCount), 30000L);
            Log.i(TAG, "Scheduling reconnect in " + delay + "ms. Attempt " + tcpReconnectCount);
            scheduledExecutor.schedule(() -> tcpStateExecutor.execute(this::connectTcpInternal), delay, TimeUnit.MILLISECONDS);
        } else {
            Log.i(TAG, "TCP permanently disconnected. No more reconnect attempts.");
        }
    }

    public void sendTcpData(String data) {
        tcpStateExecutor.execute(() -> {
            if (tcpState == ConnectionState.CONNECTED && tcpWriter != null) {
                tcpWriter.println(ensureJsonString(data));
                if(tcpWriter.checkError()) {
                    Log.e(TAG, "TCP send error occurred.");
                    onTcpDisconnected();
                }
            }
        });
    }

    public void closeTcp() {
        tcpStateExecutor.execute(() -> {
            this.tcpReconnect = false;
            if(tcpState != ConnectionState.DISCONNECTED) {
                onTcpDisconnected();
            }
        });
    }

    private void closeTcpInternal() {
        // 此方法必须在 tcpStateExecutor 中调用
        try { if (tcpWriter != null) tcpWriter.close(); } catch (Exception ignored) {}
        try { if (tcpReader != null) tcpReader.close(); } catch (Exception ignored) {}
        try { if (tcpSocket != null) tcpSocket.close(); } catch (Exception ignored) {}
        tcpWriter = null;
        tcpReader = null;
        tcpSocket = null;
    }

    private void startTcpHeartbeat() {
        // 此方法在 tcpStateExecutor 中被调用，是安全的
        stopTcpHeartbeat();
        heartbeatFuture = scheduledExecutor.scheduleWithFixedDelay(() -> {
            // 心跳的发送也通过 sendTcpData 提交到状态机线程
            if (tcpState == ConnectionState.CONNECTED) {
                try {
                    JSONObject obj = new JSONObject();
                    obj.put("type", "heartbeat");
                    sendTcpData(obj.toString());
                } catch (Exception e) {
                    Log.e(TAG, "TCP heartbeat creation failed: " + e.getMessage(), e);
                }
            }
        }, tcpHeartbeatInterval, tcpHeartbeatInterval, TimeUnit.SECONDS);
    }

    private void stopTcpHeartbeat() {
        if (heartbeatFuture != null && !heartbeatFuture.isDone()) {
            heartbeatFuture.cancel(true);
        }
    }

    // --- UDP ---

    public void createUdpConnection(int port) {
        // UDP操作相对独立，但创建也放入后台，避免阻塞调用者
        scheduledExecutor.execute(() -> {
            closeUdpInternal();
            // 同时关闭TCP，保持原逻辑
            closeTcp();
//            this.bridge = bridge;
//            this.plugin = plugin;
            this.udpPort = port;
            this.udpRunning = true;
            startUdp();
            notifyListeners("connected", makeConnectionInfo("udp", null, port));
        });
    }

    private void startUdp() {
        // 此方法在后台线程中调用
        try {
            udpSocket = new DatagramSocket(udpPort);
            udpSocket.setBroadcast(true);
            udpSocket.setReceiveBufferSize(UDP_BUFFER_SIZE);
            udpSocket.setSendBufferSize(UDP_BUFFER_SIZE);
            Thread receiveThread = new Thread(this::startUdpReceiveLoop);
            receiveThread.setDaemon(true);
            receiveThread.start();
        } catch (IOException e) {
            Log.e(TAG, "UDP listener start failed: " + e.getMessage());
            udpRunning = false;
        }
    }

    private void startUdpReceiveLoop() {
        byte[] buffer = new byte[UDP_BUFFER_SIZE];
        while (udpRunning) {
            try {
                // 局部变量，防止在循环中被外部修改为null
                DatagramSocket socket = this.udpSocket;
                if (socket == null || socket.isClosed()) {
                    break;
                }
                DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
                socket.receive(packet);
                String dataStr = new String(packet.getData(), 0, packet.getLength()).trim();
                if (dataStr.isEmpty()) continue;

                JSObject obj = parseJsonOrError(dataStr);

                String msgId = obj.has("msgId") ? obj.getString("msgId") : null;
                if (msgId != null && !udpMsgIdCache.add(msgId)) {
                    continue;
                }

                obj.put("ip", packet.getAddress().getHostAddress());
                obj.put("port", packet.getPort());
                Log.i(TAG, "UDP Data Received: " + obj);
                notifyListeners("data", obj);

            } catch (IOException e) {
                if (udpRunning) {
                    Log.e(TAG, "UDP receive error: " + e.getMessage());
                }
            }
        }
        Log.d(TAG, "UDP receive loop finished.");
    }

    public void sendUdpData(String data, String ip, String type) {
        scheduledExecutor.execute(() -> {
            DatagramSocket socket = this.udpSocket;
            if (socket == null || socket.isClosed()) return;
            try {
                JSONObject obj;
                try {
                    obj = new JSONObject(data);
                } catch (Exception e) {
                    obj = new JSONObject();
                    obj.put("value", data);
                }
                String msgId = UUID.randomUUID().toString();
                obj.put("msgId", msgId);
                byte[] sendData = obj.toString().getBytes();

                InetAddress targetAddress;
                if ("multicast".equalsIgnoreCase(type)) {
                    targetAddress = InetAddress.getByName(ip != null && ip.matches("^2(2[4-9]|3[0-9])\\..*") ? ip : "224.0.0.1");
                } else if ("broadcast".equalsIgnoreCase(type)) {
                    targetAddress = InetAddress.getByName("255.255.255.255");
                } else {
                    if (ip == null || ip.isEmpty()) {
                        Log.e(TAG, "IP address is required for unicast UDP send.");
                        return;
                    }
                    targetAddress = InetAddress.getByName(ip);
                }

                DatagramPacket packet = new DatagramPacket(sendData, sendData.length, targetAddress, udpPort);

                for (int i = 0; i < udpRepeat; i++) {
                    socket.send(packet);
                    if (i < udpRepeat - 1) {
                        try {
                            Thread.sleep(10);
                        } catch (InterruptedException ignored) {
                            Thread.currentThread().interrupt();
                            break;
                        }
                    }
                }
            } catch (Exception e) {
                Log.e(TAG, "UDP send error: " + e.getMessage(), e);
            }
        });
    }

    public void closeUdp() {
        scheduledExecutor.execute(this::closeUdpInternal);
    }

    private void closeUdpInternal() {
        udpRunning = false;
        if (udpSocket != null) {
            udpSocket.close();
            udpSocket = null;
        }
        notifyListeners("disconnected", makeConnectionInfo("udp", null, udpPort));
    }

    // --- Utility Methods ---

    public String getIpInfo() {
        ConnectivityManager connMgr = (ConnectivityManager) appContext.getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo networkInfo = connMgr.getActiveNetworkInfo();
        if (networkInfo != null && networkInfo.getType() == ConnectivityManager.TYPE_WIFI) {
            WifiManager wifiManager = (WifiManager) appContext.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
            if (wifiManager != null && wifiManager.isWifiEnabled()) {
                int ipAddress = wifiManager.getConnectionInfo().getIpAddress();
                if (ipAddress != 0) {
                    return intToIp(ipAddress);
                }
            }
        }
        return "0.0.0.0";
    }

    public void setTcpMaxReconnect(int max) { this.tcpMaxReconnect = max; }
    public void setTcpHeartbeatInterval(int seconds) { this.tcpHeartbeatInterval = seconds; }

    private void notifyListeners(String event, JSObject data) {
//        if (plugin != null) {
//            mainHandler.post(() -> ((cn.waterpolo.network.NetWorkPlugin) plugin).notifyEvent(event, data));
//        }
        if (listener != null && listener.get() != null) {
            mainHandler.post(() -> {
                if (listener != null && listener.get() != null) {
                    listener.get().onEvent(event, data);
                }
            });
        }
    }

    private String ensureJsonString(String data) {
        try {
            new JSONObject(data);
            return data;
        } catch (Exception e) {
            return data;
        }
    }

    private JSObject parseJsonOrError(String str) {
        try {
            return JSObject.fromJSONObject(new JSONObject(str));
        } catch (Exception e) {
            JSObject err = new JSObject();
            err.put("error", "Invalid JSON");
            err.put("raw", str);
            return err;
        }
    }

    private void cleanUdpMsgCache() {
        if (udpMsgIdCache.size() > UDP_MSG_CACHE_MAX) {
            int removeCount = udpMsgIdCache.size() - UDP_MSG_CACHE_MAX;
            Log.d(TAG, "Cleaning UDP message cache. Removing " + removeCount + " items.");
            Iterator<String> iterator = udpMsgIdCache.iterator();
            for (int i = 0; i < removeCount && iterator.hasNext(); i++) {
                iterator.next();
                iterator.remove();
            }
        }
    }

    private JSObject makeConnectionInfo(String type, String ip, int port) {
        JSObject obj = new JSObject();
        obj.put("type", type);
        if (ip != null) obj.put("ip", ip);
        obj.put("port", port);
        return obj;
    }

    private String intToIp(int ipAddress) {
        return ((ipAddress) & 0xFF) + "." + ((ipAddress >> 8) & 0xFF) + "." + ((ipAddress >> 16) & 0xFF) + "." + ((ipAddress >> 24) & 0xFF);
    }
}