package com.foxx.androidcast.dev;

import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkCapabilities;
import android.net.TrafficStats;
import android.os.Build;
import android.os.Process;

import com.foxx.androidcast.AppPreferences;
import com.foxx.androidcast.crash.CrashReporter;
import com.foxx.androidcast.remoteaccess.RemoteAccessFailureReason;
import com.foxx.androidcast.remoteaccess.RemoteAccessMode;
import com.foxx.androidcast.remoteaccess.RemoteAccessStatusStore;
import com.foxx.androidcast.remoteaccess.RemoteAccessVpnRouting;
import com.foxx.androidcast.remoteaccess.ReverseSshTunnelBridge;
import com.foxx.androidcast.vpn.VpnServiceClient;
import com.foxx.androidcast.vpn.VpnState;

import java.io.BufferedReader;
import java.io.FileReader;
import java.util.Locale;
import java.util.concurrent.TimeUnit;

/** VPN / remote-access snapshot for diagnostics and network self-test. */
public final class DevVpnStatusProbe {
    private DevVpnStatusProbe() {}

    /** Plain-text VPN section for the diagnostics network card. */
    public static String formatForDiagnostics(Context context) {
        DevNetworkSelfTestReport r = new DevNetworkSelfTestReport();
        probe(context, r);
        return r.build();
    }

    public static void probe(Context context, DevNetworkSelfTestReport r) {
        r.section("VPN", "Remote access / VPN status");
        RemoteAccessMode mode = AppPreferences.getDevRemoteAccessMode(context);
        RemoteAccessStatusStore.Snapshot last = RemoteAccessStatusStore.load(context);
        boolean mainProcess = CrashReporter.isMainProcess(context, CrashReporter.currentProcessName());
        boolean osVpn = hasOsVpnTransport(context);

        String typeLabel = mode == RemoteAccessMode.DISABLED ? "off" : mode.name().toLowerCase(Locale.US);
        r.itemPass("type", typeLabel);

        if (mode != RemoteAccessMode.DISABLED) {
            r.itemPass(
                    "vpn-route-scope",
                    RemoteAccessVpnRouting.routeScopeLabel(
                            AppPreferences.getDevRemoteAccessVpnRouteScope(context)));
            r.itemPass(
                    "vpn-app-scope",
                    RemoteAccessVpnRouting.appScopeLabel(
                            AppPreferences.getDevRemoteAccessVpnAppScope(context)));
        }

        String vpnDetail = "";
        if (mainProcess && mode == RemoteAccessMode.WIREGUARD) {
            vpnDetail = VpnServiceClient.get(context).getStateDetailIfBound();
        }

        String tunnelState = resolveTunnelState(context, mode, last, mainProcess, osVpn);
        String controlPlane = resolveControlPlaneState(last);

        DevNetworkSelfTestReport.Level tunnelLevel;
        if (mode == RemoteAccessMode.DISABLED) {
            tunnelLevel = DevNetworkSelfTestReport.Level.NA;
        } else if (RemoteAccessStatusStore.STATE_SUCCESS.equals(tunnelState)) {
            tunnelLevel = DevNetworkSelfTestReport.Level.PASS;
        } else if (RemoteAccessStatusStore.STATE_FAILURE.equals(tunnelState)) {
            tunnelLevel = DevNetworkSelfTestReport.Level.FAIL;
        } else if (RemoteAccessStatusStore.STATE_WAIT.equals(last.state)
                && isAwaitingWhitelist(last.detail)) {
            tunnelLevel = DevNetworkSelfTestReport.Level.PASS;
        } else {
            tunnelLevel = DevNetworkSelfTestReport.Level.WARN;
        }
        String tunnelLabel = formatTunnelStateLabel(tunnelState, last);
        r.item(tunnelLevel, "tunnel-state", tunnelLabel);

        if (mode != RemoteAccessMode.DISABLED
                && !RemoteAccessStatusStore.STATE_OFF.equals(controlPlane)) {
            DevNetworkSelfTestReport.Level cpLevel;
            if (controlPlane.startsWith("ok (")) {
                cpLevel = DevNetworkSelfTestReport.Level.PASS;
            } else if (RemoteAccessStatusStore.STATE_FAILURE.equals(last.state)
                    && RemoteAccessFailureReason.isControlPlaneMethod(last.method)) {
                cpLevel = DevNetworkSelfTestReport.Level.FAIL;
            } else if (RemoteAccessStatusStore.STATE_SUCCESS.equals(last.state)) {
                cpLevel = DevNetworkSelfTestReport.Level.PASS;
            } else {
                cpLevel = DevNetworkSelfTestReport.Level.WARN;
            }
            r.item(cpLevel, "control-plane", controlPlane);
        }

        r.itemPass("os-vpn-transport", osVpn ? "active" : "none");
        if (mainProcess && mode == RemoteAccessMode.WIREGUARD) {
            boolean bound = VpnServiceClient.get(context).isBound();
            r.itemPass("vpn-process-bound", bound ? "yes (:vpn AIDL)" : "no (not bound — normal if idle)");
        }
        if (!vpnDetail.isEmpty()) {
            r.itemPass("detail", vpnDetail);
        }

        String method = last.method;
        if (method.isEmpty() && mode == RemoteAccessMode.WIREGUARD) {
            method = "VpnServiceClient.applyWireGuardConfig";
        } else if (method.isEmpty() && mode == RemoteAccessMode.RSSH) {
            method = "ReverseSshTunnelBridge.applyConfig";
        } else if (method.isEmpty()) {
            method = "n/a";
        }
        r.itemPass("method", method);
        r.itemPass("initiator", formatInitiator(last.initiator, mode));

        if (!last.sessionId.isEmpty()) {
            r.itemPass("session", last.sessionId);
        }

        String failReason = resolveDisplayedFailReason(context, mode, last, tunnelState, controlPlane);
        if (failReason != null && !failReason.isEmpty()) {
            r.itemFail("fail-reason", failReason);
        }

        if (RemoteAccessFailureReason.isControlPlaneMethod(last.method)
                && RemoteAccessStatusStore.STATE_FAILURE.equals(last.state)) {
            r.itemPass("heartbeat-url", RemoteAccessFailureReason.heartbeatUrlForDiagnostics(context));
        }

        appendTraffic(r, context);
        if (last.updatedMs > 0L) {
            r.itemPass("last-event-age", formatAge(last.updatedMs));
            r.itemPass("last-event-ms", Long.toString(last.updatedMs));
        }
        if (!mainProcess) {
            r.itemPass("probe-process", CrashReporter.currentProcessName() + " (status store + OS VPN)");
        }
    }

    /** Tunnel only — not heartbeat poll failures. */
    private static String resolveTunnelState(
            Context context,
            RemoteAccessMode mode,
            RemoteAccessStatusStore.Snapshot last,
            boolean mainProcess,
            boolean osVpn) {
        if (mode == RemoteAccessMode.DISABLED) {
            return RemoteAccessStatusStore.STATE_OFF;
        }
        if (mode == RemoteAccessMode.WIREGUARD) {
            int live = mainProcess ? VpnServiceClient.get(context).getStateIfBound() : VpnState.DOWN;
            if (live == VpnState.UP || (osVpn && RemoteAccessStatusStore.STATE_SUCCESS.equals(last.state)
                    && RemoteAccessFailureReason.isTunnelMethod(last.method))) {
                return RemoteAccessStatusStore.STATE_SUCCESS;
            }
            if (live == VpnState.ERROR) {
                return RemoteAccessStatusStore.STATE_FAILURE;
            }
            if (live == VpnState.CONNECTING) {
                return RemoteAccessStatusStore.STATE_CONNECTING;
            }
            if (RemoteAccessFailureReason.isTunnelMethod(last.method)
                    && RemoteAccessStatusStore.STATE_FAILURE.equals(last.state)) {
                return RemoteAccessStatusStore.STATE_FAILURE;
            }
            if (RemoteAccessFailureReason.isTunnelMethod(last.method)
                    && RemoteAccessStatusStore.STATE_CONNECTING.equals(last.state)) {
                return RemoteAccessStatusStore.STATE_CONNECTING;
            }
            return osVpn ? RemoteAccessStatusStore.STATE_SUCCESS : RemoteAccessStatusStore.STATE_IDLE;
        }

        boolean rsshUp;
        if (mainProcess) {
            rsshUp = ReverseSshTunnelBridge.isConnected();
        } else {
            rsshUp = RemoteAccessStatusStore.STATE_SUCCESS.equals(last.state)
                    && RemoteAccessFailureReason.isTunnelMethod(last.method);
        }
        if (rsshUp) {
            return RemoteAccessStatusStore.STATE_SUCCESS;
        }
        if (RemoteAccessFailureReason.isTunnelMethod(last.method)
                && RemoteAccessStatusStore.STATE_FAILURE.equals(last.state)) {
            return RemoteAccessStatusStore.STATE_FAILURE;
        }
        if (RemoteAccessFailureReason.isTunnelMethod(last.method)
                && RemoteAccessStatusStore.STATE_CONNECTING.equals(last.state)) {
            return RemoteAccessStatusStore.STATE_CONNECTING;
        }
        return RemoteAccessStatusStore.STATE_IDLE;
    }

    private static String resolveControlPlaneState(RemoteAccessStatusStore.Snapshot last) {
        if (RemoteAccessStatusStore.STATE_WAIT.equals(last.state)) {
            if (isAwaitingWhitelist(last.detail)) {
                return "ok (awaiting whitelist)";
            }
            if ("no_pending_session".equals(last.detail)) {
                return "ok (awaiting operator session)";
            }
            if ("rate_limited".equals(last.detail)) {
                return "ok (poll rate limited)";
            }
            return last.detail.isEmpty() ? RemoteAccessStatusStore.STATE_WAIT : last.detail;
        }
        if (!RemoteAccessFailureReason.isControlPlaneMethod(last.method)) {
            if (RemoteAccessStatusStore.STATE_IDLE.equals(last.state)
                    || RemoteAccessStatusStore.STATE_SUCCESS.equals(last.state)) {
                return last.state;
            }
            return RemoteAccessStatusStore.STATE_IDLE;
        }
        return last.state;
    }

    private static boolean isAwaitingWhitelist(String detail) {
        return "not_whitelisted".equals(detail);
    }

    private static String formatTunnelStateLabel(String tunnelState, RemoteAccessStatusStore.Snapshot last) {
        if (RemoteAccessStatusStore.STATE_WAIT.equals(last.state)) {
            if (isAwaitingWhitelist(last.detail)) {
                return "idle (awaiting whitelist — no tunnel expected)";
            }
            if ("no_pending_session".equals(last.detail)) {
                return "idle (awaiting operator open session)";
            }
        }
        return tunnelState;
    }

    private static String resolveDisplayedFailReason(
            Context context,
            RemoteAccessMode mode,
            RemoteAccessStatusStore.Snapshot last,
            String tunnelState,
            String controlPlane) {
        if (mode == RemoteAccessMode.DISABLED) {
            return "";
        }
        if (!RemoteAccessStatusStore.STATE_FAILURE.equals(last.state)) {
            return "";
        }
        String expanded = RemoteAccessFailureReason.expandForDisplay(context, last.method, last.failReason);

        if (RemoteAccessFailureReason.isControlPlaneMethod(last.method)) {
            if (!RemoteAccessStatusStore.STATE_SUCCESS.equals(tunnelState)) {
                return "[heartbeat] " + expanded;
            }
            return "[heartbeat] " + expanded + " (tunnel not affected)";
        }
        if (RemoteAccessFailureReason.isTunnelMethod(last.method)
                && RemoteAccessStatusStore.STATE_FAILURE.equals(tunnelState)) {
            return "[tunnel] " + expanded;
        }
        if (!expanded.isEmpty()) {
            return "[heartbeat] " + expanded;
        }
        return "";
    }

    private static String formatAge(long updatedMs) {
        long ago = System.currentTimeMillis() - updatedMs;
        if (ago < 0) {
            return "0s ago";
        }
        if (ago < TimeUnit.MINUTES.toMillis(1)) {
            return (ago / 1000L) + "s ago";
        }
        if (ago < TimeUnit.HOURS.toMillis(1)) {
            return (ago / TimeUnit.MINUTES.toMillis(1)) + "m ago";
        }
        return (ago / TimeUnit.HOURS.toMillis(1)) + "h ago";
    }

    private static String formatInitiator(String stored, RemoteAccessMode mode) {
        if (!stored.isEmpty()) {
            switch (stored) {
                case RemoteAccessStatusStore.INITIATOR_DEVICE:
                    return "device (polls BE heartbeat)";
                case RemoteAccessStatusStore.INITIATOR_BE:
                    return "backend (connect action)";
                case RemoteAccessStatusStore.INITIATOR_USER:
                    return "user (dev settings)";
                default:
                    return stored;
            }
        }
        if (mode == RemoteAccessMode.DISABLED) {
            return "n/a";
        }
        return "device (heartbeat poll → BE connect)";
    }

    private static boolean hasOsVpnTransport(Context context) {
        ConnectivityManager cm = context.getSystemService(ConnectivityManager.class);
        if (cm == null) {
            return false;
        }
        android.net.Network net = cm.getActiveNetwork();
        NetworkCapabilities caps = net != null ? cm.getNetworkCapabilities(net) : null;
        return caps != null && caps.hasTransport(NetworkCapabilities.TRANSPORT_VPN);
    }

    private static void appendTraffic(DevNetworkSelfTestReport r, Context context) {
        int uid = Process.myUid();
        long rx = TrafficStats.getUidRxBytes(uid);
        long tx = TrafficStats.getUidTxBytes(uid);
        if (rx == TrafficStats.UNSUPPORTED || tx == TrafficStats.UNSUPPORTED) {
            r.itemNa("traffic", "uid stats unsupported");
        } else {
            r.itemPass("uid-rx-tx", formatBytes(rx) + " / " + formatBytes(tx));
            if (Build.VERSION.SDK_INT >= 31) {
                long rxPkt = TrafficStats.getUidRxPackets(uid);
                long txPkt = TrafficStats.getUidTxPackets(uid);
                if (rxPkt != TrafficStats.UNSUPPORTED && txPkt != TrafficStats.UNSUPPORTED) {
                    r.itemPass("uid-rx-tx-pkts", rxPkt + " / " + txPkt);
                }
            }
        }
        String tun = readTunIfaceStats();
        if (!tun.isEmpty()) {
            r.itemPass("tun-iface", tun);
        }
    }

    private static String formatBytes(long bytes) {
        if (bytes < 0) {
            return "?";
        }
        if (bytes < 1024) {
            return bytes + " B";
        }
        if (bytes < 1024 * 1024) {
            return String.format(Locale.US, "%.1f KiB", bytes / 1024.0);
        }
        return String.format(Locale.US, "%.1f MiB", bytes / (1024.0 * 1024.0));
    }

    private static String readTunIfaceStats() {
        try (BufferedReader br = new BufferedReader(new FileReader("/proc/net/dev"))) {
            String line;
            while ((line = br.readLine()) != null) {
                String trimmed = line.trim();
                if (!trimmed.contains(":")) {
                    continue;
                }
                String name = trimmed.split(":", 2)[0].trim();
                if (!name.startsWith("tun") && !name.startsWith("wg")) {
                    continue;
                }
                String[] parts = trimmed.split(":\\s*");
                if (parts.length < 2) {
                    continue;
                }
                String[] cols = parts[1].trim().split("\\s+");
                if (cols.length >= 16) {
                    return name + " rx=" + cols[0] + " tx=" + cols[8] + " bytes";
                }
            }
        } catch (Exception ignored) {
        }
        return "";
    }
}
