package com.foxx.androidcast.dev;

import android.Manifest;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.media.MediaCodecInfo;
import android.media.MediaCodecList;
import android.media.MediaFormat;
import android.net.ConnectivityManager;
import android.net.NetworkCapabilities;
import android.net.Uri;
import android.os.Build;
import android.util.Size;

import androidx.core.content.ContextCompat;

import com.foxx.androidcast.AppPreferences;
import com.foxx.androidcast.BuildConfig;
import com.foxx.androidcast.CastSettings;
import com.foxx.androidcast.PlatformCastSupport;
import com.foxx.androidcast.crash.CrashSettingsStore;
import com.foxx.androidcast.display.WiredDisplayMonitor;
import com.foxx.androidcast.media.CodecPriorityCatalog;
import com.foxx.androidcast.media.CodecTier;
import com.foxx.androidcast.media.codec.jni.NativeCodecBridge;
import com.foxx.androidcast.network.CastCodecFlags;
import com.foxx.androidcast.network.transport.StreamProtectionCapability;
import com.foxx.androidcast.sender.CodecCatalog;

import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.atomic.AtomicBoolean;

/** Read-only device probes for developer diagnostics (no user interaction). */
public final class DevDiagnosticsProbe {
    private static final int HTTP_TIMEOUT_MS = 6_000;

    private DevDiagnosticsProbe() {}

    public static String probeHardwareAcceleration(Context context) {
        CodecCatalog.invalidateCapabilities();
        CastSettings settings = AppPreferences.loadSenderDefaults(context);
        StringBuilder sb = new StringBuilder();
        sb.append("API ").append(Build.VERSION.SDK_INT).append('\n');
        sb.append("Native JNI probes:\n");
        sb.append("  libvpx: ").append(NativeCodecBridge.isLibvpxAvailable() ? "yes" : "no").append('\n');
        sb.append("  Opus: ").append(NativeCodecBridge.isOpusAvailable() ? "yes (lib linked)" : "no (stub→AAC)")
                .append('\n');
        sb.append("  Speex: ").append(NativeCodecBridge.isSpeexAvailable() ? "yes (lib linked)" : "no (stub→AAC)")
                .append('\n');
        sb.append('\n').append("Video encoders (MediaCodec):\n");
        appendVideoCodecLines(sb, true, settings);
        List<String> swEnc = CodecCatalog.localNativeSoftwareVideoLines(settings);
        if (!swEnc.isEmpty()) {
            sb.append('\n').append("Video encoders (bundled SW, not in MediaCodec list):\n");
            for (String line : swEnc) {
                sb.append(line).append('\n');
            }
        }
        sb.append('\n').append("Video decoders (MediaCodec):\n");
        appendVideoCodecLines(sb, false, settings);
        if (!swEnc.isEmpty()) {
            sb.append('\n').append("Video decoders (bundled SW):\n");
            for (String line : CodecCatalog.localNativeSoftwareVideoDecoderLines(settings)) {
                sb.append(line).append('\n');
            }
        }
        sb.append('\n').append("MIME summary (catalog):\n");
        sb.append("  advertised enc: ").append(join(CodecCatalog.localEncoderMimes())).append('\n');
        sb.append("  advertised dec: ").append(join(CodecCatalog.localDecoderMimes()));
        if (NativeCodecBridge.isLibvpxAvailable()) {
            sb.append('\n').append("  native SW: video/vp8, video/vp9 (libvpx JNI)");
        }
        return sb.toString().trim();
    }

    public static String probeCameraCapture(Context context) {
        StringBuilder sb = new StringBuilder();
        sb.append("Camera2 capture modes (cast settings):\n");
        List<String> modes = new ArrayList<>();
        modes.add(context.getString(com.foxx.androidcast.R.string.capture_full_screen));
        if (PlatformCastSupport.isCaptureUserChoiceSupported()) {
            modes.add(context.getString(com.foxx.androidcast.R.string.capture_user_choice));
        } else {
            modes.add(context.getString(com.foxx.androidcast.R.string.capture_user_choice_unavailable,
                    Build.VERSION.SDK_INT));
        }
        if (PlatformCastSupport.isCameraCaptureModeSupported()) {
            modes.add(context.getString(com.foxx.androidcast.R.string.capture_camera_rear));
            if (PlatformCastSupport.hasFrontCamera(context)) {
                modes.add(context.getString(com.foxx.androidcast.R.string.capture_camera_front));
            }
        } else {
            modes.add(context.getString(com.foxx.androidcast.R.string.capture_camera_unavailable));
        }
        modes.add(context.getString(com.foxx.androidcast.R.string.capture_calibration_test));
        for (String m : modes) {
            sb.append("  • ").append(m).append('\n');
        }
        sb.append('\n').append("Physical cameras:\n");
        appendCameraDevices(context, sb);
        return sb.toString().trim();
    }

    public static String probeAudioCapture(Context context) {
        StringBuilder sb = new StringBuilder();
        boolean api = PlatformCastSupport.isAudioCaptureSupported();
        sb.append("Playback capture (MediaProjection): ")
                .append(api ? "supported (API 29+)" : "unavailable").append('\n');
        boolean micPerm = ContextCompat.checkSelfPermission(context, Manifest.permission.RECORD_AUDIO)
                == PackageManager.PERMISSION_GRANTED;
        sb.append("RECORD_AUDIO granted: ").append(micPerm ? "yes" : "no").append('\n');
        sb.append('\n').append("Runtime modes (AudioCapture):\n");
        if (!api) {
            sb.append("  • (none — API < 29)\n");
        } else {
            sb.append("  • playback\n");
            sb.append("  • playback+mic (when mic available)\n");
            sb.append("  • mic (calibration / no projection)\n");
        }
        return sb.toString().trim();
    }

    public static String probeAudioCodecs(Context context) {
        CastSettings settings = AppPreferences.loadSenderDefaults(context);
        StringBuilder sb = new StringBuilder();
        sb.append("User preference (sender): ").append(settings.getAudioCodec().name()).append('\n');
        sb.append("Wire mask: 0x").append(Integer.toHexString(CastCodecFlags.localAudioMask())).append('\n');
        sb.append('\n').append("Settings choices (priority order after AUTO):\n");
        for (CodecCatalog.AudioCodecChoice c : CodecCatalog.audioCodecChoicesForUi(settings)) {
            if (c.codec == CastSettings.AudioCodec.AUTO) {
                sb.append("  • AUTO\n");
                continue;
            }
            String marker = c.codec == settings.getAudioCodec() ? " ← user" : "";
            sb.append(String.format(Locale.US, "  • %s pri=%d%s\n",
                    c.codec.name(), c.priorityScore, marker));
        }
        sb.append('\n').append("MIME summary (catalog):\n");
        sb.append("  advertised enc:\n");
        for (String line : CodecCatalog.localAdvertisedAudioEncoderLines(settings)) {
            sb.append(line).append('\n');
        }
        sb.append("  advertised dec:\n");
        for (String line : CodecCatalog.localAdvertisedAudioDecoderLines(settings)) {
            sb.append(line).append('\n');
        }
        sb.append('\n').append("Audio encoders (MediaCodec):\n");
        appendAudioCodecLines(sb, true);
        sb.append('\n').append("Audio decoders (MediaCodec):\n");
        appendAudioCodecLines(sb, false);
        return sb.toString().trim();
    }

    public static String probeNetwork(Context context, AtomicBoolean cancel) {
        StringBuilder sb = new StringBuilder();
        sb.append(connectivitySummary(context)).append('\n');
        String crashUrl = resolveCrashUploadUrl(context);
        sb.append('\n').append(probeHttp("Crash upload", crashUrl, cancel));
        appendOtaProbe(sb, context, cancel);
        sb.append('\n').append("-----").append('\n');
        sb.append(DevVpnStatusProbe.formatForDiagnostics(context));
        return sb.toString().trim();
    }

    private static void appendOtaProbe(StringBuilder sb, Context context, AtomicBoolean cancel) {
        String otaUrl = resolveOtaUrl(context);
        if (otaUrl.isEmpty()) {
            sb.append('\n').append("OTA channel: (not configured — ok for debug)");
            return;
        }
        String trimmed = otaUrl.trim();
        String host = null;
        try {
            host = Uri.parse(trimmed).getHost();
        } catch (Exception ignored) {
            // fall through
        }
        if (host == null || host.isEmpty()) {
            String shown = trimmed.length() > 96 ? trimmed.substring(0, 96) + "…" : trimmed;
            sb.append('\n').append("OTA channel: (invalid URL — ").append(shown)
                    .append("; ok if OTA unused)");
            return;
        }
        if (cancel != null && cancel.get()) {
            sb.append('\n').append("OTA channel: stopped");
            return;
        }
        sb.append('\n').append(probeHttp("OTA channel", trimmed, cancel));
    }

    /** Wire advertisement summary (last card on diagnostics tab). */
    public static String probeFullAdvertisementInfo(Context context) {
        CastSettings settings = AppPreferences.loadSenderDefaults(context);
        StringBuilder sb = new StringBuilder();
        sb.append("[audio adv] ").append(join(CodecCatalog.localAdvertisedAudioCodecsInOrder(settings)))
                .append(" · mask 0x")
                .append(Integer.toHexString(CastCodecFlags.localAudioMask()))
                .append('\n');
        List<String> videoMimes = CodecCatalog.localAdvertisedVideoMimesInOrder();
        sb.append("[video adv] ").append(join(videoMimes));
        List<String> videoCodecs = CodecCatalog.localAdvertisedVideoCodecsInOrder(settings);
        if (!videoCodecs.isEmpty()) {
            sb.append(" (").append(join(videoCodecs)).append(')');
        }
        sb.append('\n');
        sb.append("[protection adv] ").append(join(StreamProtectionCapability.advertisedProtectionInOrder()))
                .append('\n');
        sb.append("-----");
        return sb.toString();
    }

    public static String probeCastingExtras(Context context) {
        StringBuilder sb = new StringBuilder();
        PackageManager pm = context.getPackageManager();
        sb.append("Wi‑Fi Direct (OS): ")
                .append(pm.hasSystemFeature(PackageManager.FEATURE_WIFI_DIRECT) ? "yes" : "no")
                .append('\n');
        sb.append("Miracast / Wi‑Fi Display: ")
                .append(miracastSummary(pm))
                .append(" (in-app cast uses MediaProjection, not WFD)\n");
        WiredDisplayMonitor monitor = WiredDisplayMonitor.getInstance(context);
        monitor.ensureRegistered();
        sb.append('\n').append("HDMI / external display:\n  ")
                .append(monitor.getSummaryLine().replace("\n", "\n  "))
                .append('\n');
        sb.append('\n').append("MediaProjection screen capture: ")
                .append(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ? "available" : "N/A")
                .append('\n');
        sb.append("App/screen picker (API 34+): ")
                .append(PlatformCastSupport.isCaptureUserChoiceSupported() ? "yes" : "no")
                .append('\n');
        sb.append("Google Cast resolver: ")
                .append(resolveCastIntent(pm) ? "activity found" : "none")
                .append('\n');
        sb.append("Nearby / Share resolver: ")
                .append(resolveNearbyShare(pm) ? "activity found" : "none");
        return sb.toString().trim();
    }

    private static void appendVideoCodecLines(StringBuilder sb, boolean encoder, CastSettings settings) {
        List<String> lines = new ArrayList<>();
        MediaCodecList list = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
        for (MediaCodecInfo info : list.getCodecInfos()) {
            if (info.isEncoder() != encoder) {
                continue;
            }
            boolean hw = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && info.isHardwareAccelerated();
            CodecTier tier = hw ? CodecTier.HARDWARE : CodecTier.SOFTWARE;
            String tierTag = hw ? "HW" : "SW";
            for (String type : info.getSupportedTypes()) {
                if (type == null || !type.startsWith("video/")) {
                    continue;
                }
                int score = CodecPriorityCatalog.priorityScoreForVideoMime(type, tier, settings);
                lines.add(formatCodecDiagLine(tierTag, score, type, info.getName()));
            }
        }
        if (lines.isEmpty()) {
            sb.append("  (none)\n");
            return;
        }
        for (String line : lines) {
            sb.append(line).append('\n');
        }
    }

    /** `[HW:0042] MIME: 'video/avc', c2.unisoc.avc.encoder` — tier tag + catalog priority score. */
    private static String formatCodecDiagLine(String tierTag, int priorityScore, String mime, String codecName) {
        return String.format(Locale.US, "  [%s:%04d] MIME: '%s', %s", tierTag, priorityScore, mime, codecName);
    }

    private static void appendAudioCodecLines(StringBuilder sb, boolean encoder) {
        List<String> lines = new ArrayList<>();
        MediaCodecList list = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
        for (MediaCodecInfo info : list.getCodecInfos()) {
            if (info.isEncoder() != encoder) {
                continue;
            }
            boolean hw = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && info.isHardwareAccelerated();
            for (String type : info.getSupportedTypes()) {
                if (type == null || !type.startsWith("audio/")) {
                    continue;
                }
                lines.add(String.format(Locale.US, "  %s [%s] %s", type, hw ? "HW" : "SW", info.getName()));
            }
        }
        if (lines.isEmpty()) {
            sb.append("  (none)\n");
            return;
        }
        for (String line : lines) {
            sb.append(line).append('\n');
        }
    }

    private static void appendCameraDevices(Context context, StringBuilder sb) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
            sb.append("  (Camera2 modes need API 29+)\n");
            return;
        }
        try {
            CameraManager mgr = context.getSystemService(CameraManager.class);
            if (mgr == null) {
                sb.append("  (no CameraManager)\n");
                return;
            }
            for (String id : mgr.getCameraIdList()) {
                CameraCharacteristics ch = mgr.getCameraCharacteristics(id);
                Integer facing = ch.get(CameraCharacteristics.LENS_FACING);
                String facingStr = facing == null ? "?" :
                        facing == CameraCharacteristics.LENS_FACING_FRONT ? "front" :
                                facing == CameraCharacteristics.LENS_FACING_BACK ? "back" : "other";
                StreamConfigurationMap map = ch.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
                int outputs = 0;
                if (map != null) {
                    Size[] sizes = map.getOutputSizes(android.graphics.ImageFormat.YUV_420_888);
                    outputs = sizes != null ? sizes.length : 0;
                }
                sb.append("  • id=").append(id)
                        .append(" ").append(facingStr)
                        .append(", YUV outputs=").append(outputs)
                        .append('\n');
            }
        } catch (CameraAccessException | SecurityException e) {
            sb.append("  Error: ").append(e.getMessage()).append('\n');
        }
    }

    private static String connectivitySummary(Context context) {
        ConnectivityManager cm = context.getSystemService(ConnectivityManager.class);
        if (cm == null) {
            return "Connectivity: unknown";
        }
        android.net.Network net = cm.getActiveNetwork();
        if (net == null) {
            return "Connectivity: no active network";
        }
        NetworkCapabilities caps = cm.getNetworkCapabilities(net);
        if (caps == null) {
            return "Connectivity: active network (no caps)";
        }
        List<String> parts = new ArrayList<>();
        if (caps.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
            parts.add("Wi‑Fi");
        }
        if (caps.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
            parts.add("cellular");
        }
        if (caps.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)) {
            parts.add("ethernet");
        }
        if (caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
            parts.add("internet");
        }
        return "Connectivity: " + (parts.isEmpty() ? "other" : String.join(", ", parts));
    }

    private static String probeHttp(String label, String url, AtomicBoolean cancel) {
        if (cancel != null && cancel.get()) {
            return label + ": stopped";
        }
        if (url == null || url.trim().isEmpty()) {
            return label + ": (no URL configured)";
        }
        String trimmed = url.trim();
        long t0 = System.currentTimeMillis();
        try {
            String host = Uri.parse(trimmed).getHost();
            if (host != null && !host.isEmpty()) {
                InetAddress addr = InetAddress.getByName(host);
                long dnsMs = System.currentTimeMillis() - t0;
                if (cancel != null && cancel.get()) {
                    return label + ": stopped";
                }
                HttpURLConnection conn = (HttpURLConnection) new URL(trimmed).openConnection();
                conn.setConnectTimeout(HTTP_TIMEOUT_MS);
                conn.setReadTimeout(HTTP_TIMEOUT_MS);
                conn.setRequestMethod("HEAD");
                conn.setInstanceFollowRedirects(true);
                int code = conn.getResponseCode();
                long totalMs = System.currentTimeMillis() - t0;
                conn.disconnect();
                return label + ": HTTP " + code + ", DNS " + dnsMs + " ms, total " + totalMs + " ms"
                        + " (" + addr.getHostAddress() + ")";
            }
            return label + ": invalid URL";
        } catch (IOException e) {
            return label + ": fail — " + e.getClass().getSimpleName() + ": " + e.getMessage()
                    + " (" + (System.currentTimeMillis() - t0) + " ms)";
        }
    }

    private static String resolveCrashUploadUrl(Context context) {
        return CrashSettingsStore.load(context).uploadUrl;
    }

    private static String resolveOtaUrl(Context context) {
        String url = AppPreferences.getOtaChannelUrl(context);
        if (url.isEmpty() && BuildConfig.OTA_CHANNEL_URL_DEFAULT != null) {
            url = BuildConfig.OTA_CHANNEL_URL_DEFAULT.trim();
        }
        return url;
    }

    private static String miracastSummary(PackageManager pm) {
        if (pm.hasSystemFeature(PackageManager.FEATURE_WIFI_DIRECT)) {
            return "likely via OS Wi‑Fi Display stack";
        }
        return "not reported";
    }

    private static boolean resolveCastIntent(PackageManager pm) {
        Intent i = new Intent("android.settings.CAST_SETTINGS");
        return i.resolveActivity(pm) != null;
    }

    private static boolean resolveNearbyShare(PackageManager pm) {
        Intent i = new Intent(Intent.ACTION_SEND);
        i.setType("video/*");
        return i.resolveActivity(pm) != null;
    }

    private static String join(List<String> list) {
        if (list == null || list.isEmpty()) {
            return "(none)";
        }
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < list.size(); i++) {
            if (i > 0) {
                sb.append(", ");
            }
            sb.append(list.get(i));
        }
        return sb.toString();
    }
}
