package com.foxx.androidcast.discovery;

/*********************************************************************
 * NsdCastDiscovery.java
 * Created at: Mon 18 May 2026 17:46:19 +0200
 * Updated at: Wed 20 May 2026 15:17:13 +0200 by Anton Afanasyeu <a.afanasieff@gmail.com>
 * Commit: 5d8e82d2e60a21fff3138d2a394ee4e8b4c6dcb8
 * class NsdCastDiscovery
 * Contributors:
 *    - Anton Afanasyeu <a.afanasieff@gmail.com> (2 commits, 203 lines)
 *    - Cursor Agent (project assistant)
 * Digest: SHA256	6930ce92263586668ef5644052979b105a5707a40e17deccdb2c721ed1b7af51
**********************************************************************/
import android.content.Context;
import android.net.nsd.NsdManager;
import android.net.nsd.NsdServiceInfo;
import android.os.Build;
import android.util.Log;

import com.foxx.androidcast.CastConfig;

import java.util.concurrent.atomic.AtomicBoolean;

/**
 * Android NSD (mDNS) discovery/advertise alongside UDP broadcast.
 * TXT: port, transport, pin_auth (optional).
 */
public final class NsdCastDiscovery {
    private static final String TAG = "NsdCastDiscovery";

    public interface Host {
        void onNsdDeviceFound(DiscoveryManager.DiscoveredDevice device);
    }

    private final Context appContext;
    private final Host host;
    private NsdManager nsdManager;
    private NsdManager.DiscoveryListener discoveryListener;
    private NsdManager.RegistrationListener registrationListener;
    private final AtomicBoolean discovering = new AtomicBoolean(false);

    public NsdCastDiscovery(Context context, Host host) {
        this.appContext = context.getApplicationContext();
        this.host = host;
    }

    public void startDiscovering() {
        if (!discovering.compareAndSet(false, true)) {
            return;
        }
        nsdManager = (NsdManager) appContext.getSystemService(Context.NSD_SERVICE);
        if (nsdManager == null) {
            discovering.set(false);
            return;
        }
        discoveryListener = new NsdManager.DiscoveryListener() {
            @Override
            public void onDiscoveryStarted(String serviceType) {
                Log.d(TAG, "NSD discovery started");
            }

            @Override
            public void onServiceFound(NsdServiceInfo service) {
                if (service == null || service.getServiceType() == null) {
                    return;
                }
                if (!service.getServiceType().contains("_androidcast")) {
                    return;
                }
                nsdManager.resolveService(service, new NsdManager.ResolveListener() {
                    @Override
                    public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode) {
                        Log.d(TAG, "NSD resolve failed: " + errorCode);
                    }

                    @Override
                    public void onServiceResolved(NsdServiceInfo info) {
                        publishResolved(info);
                    }
                });
            }

            @Override
            public void onServiceLost(NsdServiceInfo service) {}

            @Override
            public void onDiscoveryStopped(String serviceType) {
                discovering.set(false);
            }

            @Override
            public void onStartDiscoveryFailed(String serviceType, int errorCode) {
                Log.w(TAG, "NSD start failed: " + errorCode);
                discovering.set(false);
            }

            @Override
            public void onStopDiscoveryFailed(String serviceType, int errorCode) {
                discovering.set(false);
            }
        };
        try {
            nsdManager.discoverServices(CastConfig.NSD_SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD,
                    discoveryListener);
        } catch (Exception e) {
            Log.w(TAG, "NSD discover: " + e.getMessage());
            discovering.set(false);
        }
    }

    public void stopDiscovering() {
        if (!discovering.get() || nsdManager == null || discoveryListener == null) {
            discovering.set(false);
            return;
        }
        try {
            nsdManager.stopServiceDiscovery(discoveryListener);
        } catch (Exception ignored) {
        }
        discoveryListener = null;
        discovering.set(false);
    }

    public void register(String serviceName, int port, String transport, String pinAuth) {
        nsdManager = (NsdManager) appContext.getSystemService(Context.NSD_SERVICE);
        if (nsdManager == null) {
            return;
        }
        NsdServiceInfo info = new NsdServiceInfo();
        info.setServiceName(sanitizeServiceName(serviceName));
        info.setServiceType(CastConfig.NSD_SERVICE_TYPE);
        info.setPort(port);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            info.setAttribute("transport", transport != null ? transport : CastConfig.DEFAULT_TRANSPORT);
            if (pinAuth != null && !pinAuth.isEmpty()) {
                info.setAttribute("pin_auth", pinAuth);
            }
        }
        registrationListener = new NsdManager.RegistrationListener() {
            @Override
            public void onRegistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
                Log.w(TAG, "NSD register failed: " + errorCode);
            }

            @Override
            public void onUnregistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {}

            @Override
            public void onServiceRegistered(NsdServiceInfo serviceInfo) {
                Log.i(TAG, "NSD registered " + serviceInfo.getServiceName());
            }

            @Override
            public void onServiceUnregistered(NsdServiceInfo serviceInfo) {}
        };
        try {
            nsdManager.registerService(info, NsdManager.PROTOCOL_DNS_SD, registrationListener);
        } catch (Exception e) {
            Log.w(TAG, "NSD register: " + e.getMessage());
        }
    }

    public void unregister() {
        if (nsdManager == null || registrationListener == null) {
            return;
        }
        try {
            nsdManager.unregisterService(registrationListener);
        } catch (Exception ignored) {
        }
        registrationListener = null;
    }

    private void publishResolved(NsdServiceInfo info) {
        if (info == null || info.getHost() == null) {
            return;
        }
        String hostAddress = info.getHost().getHostAddress();
        int port = info.getPort();
        if (port <= 0) {
            port = CastConfig.CAST_PORT;
        }
        String transport = CastConfig.DEFAULT_TRANSPORT;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            byte[] t = info.getAttributes().get("transport");
            if (t != null) {
                transport = new String(t).trim();
            }
        }
        String name = info.getServiceName() != null ? info.getServiceName().replace('_', ' ') : hostAddress;
        DiscoveryManager.DiscoveredDevice device = new DiscoveryManager.DiscoveredDevice(
                name, hostAddress, port, transport, System.currentTimeMillis());
        host.onNsdDeviceFound(device);
    }

    private static String sanitizeServiceName(String name) {
        String s = name != null ? name : "AndroidCast";
        s = s.replaceAll("[^a-zA-Z0-9-]", "");
        if (s.length() < 1) {
            s = "AndroidCast";
        }
        if (s.length() > 63) {
            s = s.substring(0, 63);
        }
        return s;
    }
}
