package com.foxx.androidcast.dev;

import android.content.Context;
import android.net.ConnectivityManager;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.ProxyInfo;
import android.os.Build;
import android.os.PowerManager;
import android.telephony.TelephonyManager;

import com.foxx.androidcast.AppPreferences;
import com.foxx.androidcast.BuildConfig;
import com.foxx.androidcast.crash.CrashSettingsStore;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.atomic.AtomicBoolean;

/** Focused network self-test for developer settings (compact structured report). */
public final class DevNetworkSelfTestProbe {
    private static final int HTTP_TIMEOUT_MS = 5_000;
    private static final String API10_NA = "requires Android API 10+";

    private DevNetworkSelfTestProbe() {}

    public static String run(Context context, AtomicBoolean cancel) {
        DevNetworkSelfTestReport report = new DevNetworkSelfTestReport();
        report.header();

        ConnectivityManager cm = context.getSystemService(ConnectivityManager.class);
        Network active = cm != null ? cm.getActiveNetwork() : null;
        NetworkCapabilities caps = (cm != null && active != null) ? cm.getNetworkCapabilities(active) : null;
        LinkProperties lp = (cm != null && active != null) ? cm.getLinkProperties(active) : null;

        probeOverall(context, caps, cancel, report);
        probeLink(context, caps, lp, report);
        probeCaptive(caps, cancel, report);
        probeIntraLan(lp, cancel, report);
        probeBackend(context, cancel, report);
        probeNtp(context, cancel, report);
        probeNatExternal(caps, context, cancel, report);
        probeMtu(lp, report);
        probeBluetooth(caps, cancel, report);

        if (cancel != null && cancel.get()) {
            report.itemWarn("run", "cancelled before completion");
        }
        return report.build();
    }

    private static void probeOverall(Context context, NetworkCapabilities caps, AtomicBoolean cancel,
            DevNetworkSelfTestReport r) {
        r.section("AVAIL", "Availability");
        boolean online = caps != null && caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
        boolean validated = caps != null && caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED);
        if (!online) {
            r.itemFail("internet", "no active route");
        } else if (!validated) {
            r.itemWarn("internet", "online, not validated");
        } else {
            r.itemPass("internet", "online + validated");
        }
        r.itemBool(DevNetworkSelfTestReport.Level.PASS, "wifi",
                hasTransport(caps, NetworkCapabilities.TRANSPORT_WIFI), "active", "off");
        r.itemBool(DevNetworkSelfTestReport.Level.PASS, "mobile",
                hasTransport(caps, NetworkCapabilities.TRANSPORT_CELLULAR), "active", "off");
        r.itemBool(DevNetworkSelfTestReport.Level.PASS, "ethernet",
                hasTransport(caps, NetworkCapabilities.TRANSPORT_ETHERNET), "active", "off");
        r.itemBool(DevNetworkSelfTestReport.Level.PASS, "bt-transport",
                hasTransport(caps, NetworkCapabilities.TRANSPORT_BLUETOOTH), "active", "off");
        r.itemBool(DevNetworkSelfTestReport.Level.PASS, "bt-tether",
                hasBtPanInterface(), "bt-pan up", "none");

        PowerManager pm = context.getSystemService(PowerManager.class);
        if (pm != null) {
            r.itemBool(pm.isPowerSaveMode() ? DevNetworkSelfTestReport.Level.WARN : DevNetworkSelfTestReport.Level.PASS,
                    "power-save", true, pm.isPowerSaveMode() ? "on" : "off", "off");
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                r.itemBool(pm.isDeviceIdleMode() ? DevNetworkSelfTestReport.Level.WARN : DevNetworkSelfTestReport.Level.PASS,
                        "doze", true, pm.isDeviceIdleMode() ? "active" : "off", "off");
            } else {
                r.itemNa("doze", API10_NA);
            }
        }
        if (cancel != null && cancel.get()) {
            r.itemWarn("status", "stopped");
        }
    }

    private static void probeLink(Context context, NetworkCapabilities caps, LinkProperties lp,
            DevNetworkSelfTestReport r) {
        r.section("LINK", "Link parameters");
        List<String> modes = new ArrayList<>();
        if (hasTransport(caps, NetworkCapabilities.TRANSPORT_WIFI)) {
            modes.add("wifi");
        }
        if (hasTransport(caps, NetworkCapabilities.TRANSPORT_CELLULAR)) {
            modes.add("mobile");
        }
        if (hasTransport(caps, NetworkCapabilities.TRANSPORT_ETHERNET)) {
            modes.add("ethernet");
        }
        if (hasTransport(caps, NetworkCapabilities.TRANSPORT_BLUETOOTH)) {
            modes.add("bluetooth");
        }
        r.itemPass("mode", modes.isEmpty() ? "other/unknown" : String.join("+", modes));
        r.itemBool(hasTransport(caps, NetworkCapabilities.TRANSPORT_VPN)
                        ? DevNetworkSelfTestReport.Level.WARN : DevNetworkSelfTestReport.Level.PASS,
                "vpn/tunnel", hasTransport(caps, NetworkCapabilities.TRANSPORT_VPN), "in use", "none");

        if (lp == null) {
            r.itemWarn("iface", "no link properties");
            return;
        }
        r.itemPass("iface", lp.getInterfaceName() != null ? lp.getInterfaceName() : "?");
        String ipv4 = firstIpv4Cidr(lp);
        if (ipv4.isEmpty()) {
            r.itemWarn("ipv4", "none");
        } else {
            r.itemPass("ipv4", ipv4);
        }
        if (lp.getLinkAddresses().size() > 1) {
            r.itemPass("cidr-all", joinCidrs(lp));
        }
        if (lp.getDnsServers().isEmpty()) {
            r.itemFail("dns", "none configured");
        } else {
            List<String> dns = new ArrayList<>();
            for (InetAddress addr : lp.getDnsServers()) {
                dns.add(addr.getHostAddress());
            }
            r.itemPass("dns", String.join(", ", dns));
        }
        ProxyInfo proxy = lp.getHttpProxy();
        if (proxy != null && proxy.getHost() != null) {
            r.itemWarn("http-proxy", proxy.getHost() + ":" + proxy.getPort());
        } else {
            r.itemPass("http-proxy", "none");
        }

        TelephonyManager tm = context.getSystemService(TelephonyManager.class);
        if (tm != null) {
            String operator = tm.getNetworkOperatorName();
            r.itemPass("operator", operator == null || operator.isEmpty() ? "unknown" : operator);
            r.itemNa("apn", API10_NA);
        }
    }

    private static void probeCaptive(NetworkCapabilities caps, AtomicBoolean cancel, DevNetworkSelfTestReport r) {
        r.section("PORTAL", "Captive portal");
        boolean declared = caps != null && caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL);
        if (declared) {
            r.itemFail("os-flag", "captive portal declared");
        } else {
            r.itemPass("os-flag", "clear");
        }
        if (cancel != null && cancel.get()) {
            r.itemWarn("probe", "stopped");
            return;
        }
        HttpResult probe = httpHead("http://connectivitycheck.gstatic.com/generate_204");
        if (probe.code == 204) {
            r.itemPass("generate_204", probe.summary);
            r.itemPass("heuristic", "no portal signs");
        } else if (probe.code > 0) {
            r.itemWarn("generate_204", probe.summary);
            r.itemWarn("heuristic", "possible captive portal");
        } else {
            r.itemFail("generate_204", probe.summary);
        }
    }

    private static void probeIntraLan(LinkProperties lp, AtomicBoolean cancel, DevNetworkSelfTestReport r) {
        r.section("LAN", "Intra-network");
        String ip = firstIpv4Host(lp);
        if (ip == null) {
            r.itemNa("peer-scan", "no local IPv4");
            return;
        }
        if (cancel != null && cancel.get()) {
            r.itemWarn("peer-scan", "stopped");
            return;
        }
        String[] parts = ip.split("\\.");
        if (parts.length != 4) {
            r.itemNa("peer-scan", "unsupported IPv4");
            return;
        }
        String prefix = parts[0] + "." + parts[1] + "." + parts[2] + ".";
        int self = Integer.parseInt(parts[3]);
        List<Long> rtts = new ArrayList<>();
        int peers = 0;
        long t0 = System.currentTimeMillis();
        for (int d = -8; d <= 8; d++) {
            if (cancel != null && cancel.get()) {
                break;
            }
            int host = self + d;
            if (host <= 1 || host >= 255 || host == self) {
                continue;
            }
            try {
                InetAddress addr = InetAddress.getByName(prefix + host);
                long p0 = System.currentTimeMillis();
                if (addr.isReachable(180)) {
                    peers++;
                    rtts.add(System.currentTimeMillis() - p0);
                }
            } catch (Exception ignored) {
            }
        }
        long elapsed = System.currentTimeMillis() - t0;
        r.itemPass("subnet", prefix + "x (self " + ip + ")");
        if (peers == 0) {
            r.itemWarn("peers", "0 reachable (+/-8 quick scan)");
            r.itemNa("avg-rtt", "n/a");
        } else {
            long sum = 0;
            for (Long v : rtts) {
                sum += v;
            }
            double avg = (double) sum / peers;
            r.itemPass("peers", peers + " reachable");
            r.itemPass("avg-rtt", String.format(Locale.US, "%.0f ms", avg));
        }
        r.itemPass("scan-ms", Long.toString(elapsed));
    }

    private static void probeBackend(Context context, AtomicBoolean cancel, DevNetworkSelfTestReport r) {
        r.section("BACKEND", "Backend reachability");
        String url = resolveBackendUrl(context);
        if (url.isEmpty()) {
            r.itemWarn("url", "not configured");
            return;
        }
        r.itemPass("url", shortenUrl(url));
        if (cancel != null && cancel.get()) {
            r.itemWarn("probe", "stopped");
            return;
        }
        HttpResult head = httpHead(url);
        if (head.code > 0 && head.code < 500) {
            r.itemPass("head", head.summary);
        } else {
            r.itemFail("head", head.summary);
        }
        HttpReadResult read = httpRead(url, 32 * 1024);
        if (read.summary.startsWith("fail")) {
            r.itemFail("downlink", read.summary);
        } else {
            r.itemPass("downlink", read.summary);
        }
        HttpResult uplink = httpPostJson(url, "{\"ping\":\"bandwidth\"}");
        if (uplink.code > 0) {
            r.itemPass("uplink", uplink.summary);
        } else {
            r.itemFail("uplink", uplink.summary);
        }
    }

    private static void probeNtp(Context context, AtomicBoolean cancel, DevNetworkSelfTestReport r) {
        r.section("NTP", "Time sync");
        r.itemPass("utc-epoch", Long.toString(System.currentTimeMillis() / 1000L));
        int tzMin = java.util.TimeZone.getDefault().getOffset(System.currentTimeMillis()) / 60000;
        r.itemPass("tz-offset", tzMin + " min");
        boolean enabled = AppPreferences.isDevBackendNtpSyncEnabled(context);
        r.itemBool(enabled ? DevNetworkSelfTestReport.Level.PASS : DevNetworkSelfTestReport.Level.NA,
                "be-ntp-sync", enabled, "enabled", "disabled (dev setting)");
        if (!enabled) {
            return;
        }
        String backend = resolveBackendUrl(context);
        if (backend.isEmpty()) {
            r.itemWarn("be-ntp", "backend URL missing");
            return;
        }
        String heartbeatUrl = toHeartbeatUrl(backend);
        if (cancel != null && cancel.get()) {
            r.itemWarn("be-ntp", "stopped");
            return;
        }
        long now = System.currentTimeMillis() / 1000L;
        String tz = new java.text.SimpleDateFormat("Z", Locale.US).format(new java.util.Date());
        String body = "{\"heartbeat\":{\"type\":\"ntp\",\"date\":" + now + ",\"tz\":\"" + tz + "\"}}";
        HttpTextResult text = httpPostJsonText(heartbeatUrl, body);
        if (text.summary.startsWith("HTTP") && !text.summary.startsWith("fail")) {
            r.itemPass("be-ntp", text.summary);
            if (text.body != null && !text.body.isEmpty()) {
                r.itemPass("be-payload", truncate(text.body, 120));
            }
        } else {
            r.itemFail("be-ntp", text.summary);
        }
    }

    private static void probeNatExternal(NetworkCapabilities caps, Context context, AtomicBoolean cancel,
            DevNetworkSelfTestReport r) {
        r.section("WAN", "NAT / external IP");
        String nat;
        if (hasTransport(caps, NetworkCapabilities.TRANSPORT_VPN)) {
            nat = "vpn/tunnel";
        } else if (hasTransport(caps, NetworkCapabilities.TRANSPORT_CELLULAR)) {
            nat = "likely CGNAT (mobile)";
        } else if (hasTransport(caps, NetworkCapabilities.TRANSPORT_WIFI)) {
            nat = "likely router NAT";
        } else {
            nat = "unknown";
        }
        r.itemPass("nat", nat);
        if (cancel != null && cancel.get()) {
            r.itemWarn("external-ip", "stopped");
            return;
        }
        String backend = resolveBackendUrl(context);
        if (!backend.isEmpty()) {
            HttpTextResult viaBackend = httpPostJsonText(toHeartbeatUrl(backend),
                    "{\"heartbeat\":{\"type\":\"ip\"}}");
            if (viaBackend.summary.startsWith("fail")) {
                r.itemFail("external-ip", viaBackend.summary);
            } else {
                r.itemPass("external-ip", viaBackend.summary);
                if (viaBackend.body != null && !viaBackend.body.isEmpty()) {
                    r.itemPass("be-ip-body", truncate(viaBackend.body, 80));
                }
            }
            return;
        }
        HttpTextResult web = httpGetText("https://api.ipify.org?format=json");
        if (web.summary.startsWith("fail")) {
            r.itemFail("external-ip", web.summary);
        } else {
            r.itemPass("external-ip", truncate(web.summary, 100));
        }
    }

    private static void probeMtu(LinkProperties lp, DevNetworkSelfTestReport r) {
        r.section("MTU", "MTU");
        if (lp != null) {
            r.itemPass("active-link", Integer.toString(lp.getMtu()));
        } else {
            r.itemNa("active-link", "n/a");
        }
        try {
            Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces();
            if (en == null) {
                r.itemNa("ifaces", "unavailable");
                return;
            }
            List<String> lines = new ArrayList<>();
            for (NetworkInterface nif : Collections.list(en)) {
                if (!nif.isUp()) {
                    continue;
                }
                lines.add(nif.getName() + "=" + nif.getMTU());
                if (lines.size() >= 6) {
                    break;
                }
            }
            if (lines.isEmpty()) {
                r.itemWarn("ifaces", "none up");
            } else {
                r.itemPass("ifaces", String.join(", ", lines));
            }
        } catch (Exception e) {
            r.itemFail("ifaces", e.getMessage());
        }
    }

    private static void probeBluetooth(NetworkCapabilities caps, AtomicBoolean cancel, DevNetworkSelfTestReport r) {
        r.section("BT", "Bluetooth");
        boolean btTransport = hasTransport(caps, NetworkCapabilities.TRANSPORT_BLUETOOTH);
        boolean btPan = hasBtPanInterface();
        if (!btTransport && !btPan) {
            r.itemNa("available", "no BT network iface");
            return;
        }
        r.itemPass("available", btTransport ? "transport+iface" : "bt-pan only");
        if (cancel != null && cancel.get()) {
            r.itemWarn("bw-sample", "stopped");
            return;
        }
        long t0 = System.currentTimeMillis();
        int bytes = 0;
        try {
            InetAddress addr = InetAddress.getByName("1.1.1.1");
            if (addr.isReachable(350)) {
                bytes = 512;
            }
        } catch (Exception ignored) {
        }
        long dt = Math.max(1L, System.currentTimeMillis() - t0);
        double kbps = (bytes * 8.0) / dt;
        r.itemPass("bw-sample", String.format(Locale.US, "~%.1f Kbit/s (rough)", kbps));
    }

    private static String firstIpv4Cidr(LinkProperties lp) {
        if (lp == null) {
            return "";
        }
        for (LinkAddress la : lp.getLinkAddresses()) {
            InetAddress addr = la.getAddress();
            if (addr instanceof Inet4Address && !addr.isLoopbackAddress()) {
                return la.toString();
            }
        }
        return "";
    }

    private static String firstIpv4Host(LinkProperties lp) {
        if (lp == null) {
            return null;
        }
        for (LinkAddress la : lp.getLinkAddresses()) {
            InetAddress addr = la.getAddress();
            if (addr instanceof Inet4Address && !addr.isLoopbackAddress()) {
                return addr.getHostAddress();
            }
        }
        return null;
    }

    private static String joinCidrs(LinkProperties lp) {
        List<String> out = new ArrayList<>();
        for (LinkAddress la : lp.getLinkAddresses()) {
            out.add(la.toString());
        }
        return String.join("; ", out);
    }

    private static String shortenUrl(String url) {
        if (url.length() <= 72) {
            return url;
        }
        return url.substring(0, 69) + "...";
    }

    private static String truncate(String s, int max) {
        if (s == null) {
            return "";
        }
        String oneLine = s.replace('\n', ' ').trim();
        if (oneLine.length() <= max) {
            return oneLine;
        }
        return oneLine.substring(0, max - 3) + "...";
    }

    private static String toHeartbeatUrl(String backend) {
        if (backend.contains("/api/upload.php")) {
            return backend.replace("/api/upload.php", "/api/heartbeat.php");
        }
        if (backend.endsWith("/")) {
            return backend + "api/heartbeat.php";
        }
        return backend + "/api/heartbeat.php";
    }

    private static String resolveBackendUrl(Context context) {
        String crash = CrashSettingsStore.load(context).uploadUrl;
        if (crash != null && !crash.trim().isEmpty()) {
            return crash.trim();
        }
        String ota = AppPreferences.getOtaChannelUrl(context);
        if (ota != null && !ota.trim().isEmpty()) {
            return ota.trim();
        }
        if (BuildConfig.OTA_CHANNEL_URL_DEFAULT != null && !BuildConfig.OTA_CHANNEL_URL_DEFAULT.trim().isEmpty()) {
            return BuildConfig.OTA_CHANNEL_URL_DEFAULT.trim();
        }
        return "";
    }

    private static boolean hasTransport(NetworkCapabilities caps, int t) {
        return caps != null && caps.hasTransport(t);
    }

    private static boolean hasBtPanInterface() {
        try {
            Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces();
            if (en == null) {
                return false;
            }
            for (NetworkInterface nif : Collections.list(en)) {
                String n = nif.getName();
                if (n != null && (n.startsWith("bt-pan") || n.startsWith("bnep"))) {
                    return nif.isUp();
                }
            }
        } catch (Exception ignored) {
        }
        return false;
    }

    private static HttpResult httpHead(String url) {
        long t0 = System.currentTimeMillis();
        try {
            HttpURLConnection c = (HttpURLConnection) new URL(url).openConnection();
            c.setRequestMethod("HEAD");
            c.setConnectTimeout(HTTP_TIMEOUT_MS);
            c.setReadTimeout(HTTP_TIMEOUT_MS);
            c.setInstanceFollowRedirects(true);
            int code = c.getResponseCode();
            long ms = System.currentTimeMillis() - t0;
            c.disconnect();
            return new HttpResult(code, "HTTP " + code + " · " + ms + " ms");
        } catch (Exception e) {
            long ms = System.currentTimeMillis() - t0;
            return new HttpResult(-1, "fail · " + e.getClass().getSimpleName() + " · " + ms + " ms");
        }
    }

    private static HttpResult httpPostJson(String url, String body) {
        long t0 = System.currentTimeMillis();
        try {
            HttpURLConnection c = (HttpURLConnection) new URL(url).openConnection();
            c.setRequestMethod("POST");
            c.setConnectTimeout(HTTP_TIMEOUT_MS);
            c.setReadTimeout(HTTP_TIMEOUT_MS);
            c.setDoOutput(true);
            c.setRequestProperty("Content-Type", "application/json");
            byte[] bytes = body.getBytes(StandardCharsets.UTF_8);
            try (OutputStream os = c.getOutputStream()) {
                os.write(bytes);
            }
            int code = c.getResponseCode();
            long ms = System.currentTimeMillis() - t0;
            c.disconnect();
            return new HttpResult(code, "HTTP " + code + " · " + ms + " ms · " + bytes.length + " B");
        } catch (Exception e) {
            long ms = System.currentTimeMillis() - t0;
            return new HttpResult(-1, "fail · " + e.getClass().getSimpleName() + " · " + ms + " ms");
        }
    }

    private static HttpReadResult httpRead(String url, int maxBytes) {
        long t0 = System.currentTimeMillis();
        try {
            HttpURLConnection c = (HttpURLConnection) new URL(url).openConnection();
            c.setRequestMethod("GET");
            c.setConnectTimeout(HTTP_TIMEOUT_MS);
            c.setReadTimeout(HTTP_TIMEOUT_MS);
            int code = c.getResponseCode();
            int read = 0;
            try (InputStream in = c.getInputStream()) {
                byte[] buf = new byte[4096];
                while (read < maxBytes) {
                    int n = in.read(buf, 0, Math.min(buf.length, maxBytes - read));
                    if (n <= 0) {
                        break;
                    }
                    read += n;
                }
            }
            long ms = Math.max(1L, System.currentTimeMillis() - t0);
            double kbps = (read * 8.0) / ms;
            c.disconnect();
            return new HttpReadResult("HTTP " + code + " · " + read + " B · "
                    + String.format(Locale.US, "%.1f Kbit/s", kbps));
        } catch (Exception e) {
            long ms = System.currentTimeMillis() - t0;
            return new HttpReadResult("fail · " + e.getClass().getSimpleName() + " · " + ms + " ms");
        }
    }

    private static HttpTextResult httpPostJsonText(String url, String body) {
        long t0 = System.currentTimeMillis();
        try {
            HttpURLConnection c = (HttpURLConnection) new URL(url).openConnection();
            c.setRequestMethod("POST");
            c.setConnectTimeout(HTTP_TIMEOUT_MS);
            c.setReadTimeout(HTTP_TIMEOUT_MS);
            c.setDoOutput(true);
            c.setRequestProperty("Content-Type", "application/json");
            try (OutputStream os = c.getOutputStream()) {
                os.write(body.getBytes(StandardCharsets.UTF_8));
            }
            int code = c.getResponseCode();
            String text = readBody(c);
            long ms = System.currentTimeMillis() - t0;
            c.disconnect();
            return new HttpTextResult("HTTP " + code + " · " + ms + " ms", text);
        } catch (Exception e) {
            long ms = System.currentTimeMillis() - t0;
            return new HttpTextResult("fail · " + e.getClass().getSimpleName() + " · " + ms + " ms", "");
        }
    }

    private static HttpTextResult httpGetText(String url) {
        long t0 = System.currentTimeMillis();
        try {
            HttpURLConnection c = (HttpURLConnection) new URL(url).openConnection();
            c.setRequestMethod("GET");
            c.setConnectTimeout(HTTP_TIMEOUT_MS);
            c.setReadTimeout(HTTP_TIMEOUT_MS);
            int code = c.getResponseCode();
            String text = readBody(c);
            long ms = System.currentTimeMillis() - t0;
            c.disconnect();
            return new HttpTextResult("HTTP " + code + " · " + ms + " ms", text);
        } catch (Exception e) {
            long ms = System.currentTimeMillis() - t0;
            return new HttpTextResult("fail · " + e.getClass().getSimpleName() + " · " + ms + " ms", "");
        }
    }

    private static String readBody(HttpURLConnection c) {
        try (BufferedReader br = new BufferedReader(new InputStreamReader(c.getInputStream(), StandardCharsets.UTF_8))) {
            StringBuilder sb = new StringBuilder();
            String line;
            while ((line = br.readLine()) != null) {
                sb.append(line);
                if (sb.length() > 2048) {
                    break;
                }
            }
            return sb.toString();
        } catch (Exception e) {
            try (BufferedReader br = new BufferedReader(new InputStreamReader(c.getErrorStream(), StandardCharsets.UTF_8))) {
                String line = br.readLine();
                return line != null ? line : "";
            } catch (Exception ignored) {
                return "";
            }
        }
    }

    private static final class HttpResult {
        final int code;
        final String summary;

        HttpResult(int code, String summary) {
            this.code = code;
            this.summary = summary;
        }
    }

    private static final class HttpReadResult {
        final String summary;

        HttpReadResult(String summary) {
            this.summary = summary;
        }
    }

    private static final class HttpTextResult {
        final String summary;
        final String body;

        HttpTextResult(String summary, String body) {
            this.summary = summary;
            this.body = body;
        }
    }
}
