import 'dart:async';
import 'dart:convert';
import 'dart:io';

import 'package:flutter/services.dart';
import 'package:xml/xml.dart';

// ⭐ Usa il DLNADevice VERO (quello con setUrl/play/stop)
import 'package:aves/widgets/cast/dlna.dart';

class DLNAManager {
  DLNAManager._();
  static final instance = DLNAManager._();

  static const _ssdpAddress = "239.255.255.250";
  static const _ssdpPort = 1900;

  static const _channel = MethodChannel("aves/dlna");

  Future<List<DLNADevice>> discover() async {
    print("DLNA: discover() START");

    // 🔥 Acquire multicast lock
    try {
      print("DLNA: acquiring multicast lock…");
      await _channel.invokeMethod("dlnaAcquireLock");
      print("DLNA: multicast lock ACQUIRED");
    } catch (e) {
      print("DLNA: ERROR acquiring lock → $e");
    }

    // 🔥 Bind UDP socket
    print("DLNA: binding UDP socket…");
    final socket = await RawDatagramSocket.bind(InternetAddress.anyIPv4, 0);
    socket.broadcastEnabled = true;
    print("DLNA: socket bound on port ${socket.port}");

    print("DLNA: sending multi‑ST M‑SEARCH…");

    // ⭐ Lista completa ST per compatibilità massima
    final searchTargets = [
      "urn:schemas-upnp-org:device:MediaRenderer:1",   // AirScreen, AirReceiver
      "ssdp:all",                                      // Sony Bravia
      "upnp:rootdevice",                               // Sony Bravia
      "urn:schemas-upnp-org:service:AVTransport:1",    // Sony Bravia
      "urn:schemas-upnp-org:service:ConnectionManager:1",
    ];

    // ⭐ Multicast M-SEARCH
    for (final st in searchTargets) {
      final msg = '''
M-SEARCH * HTTP/1.1
HOST: 239.255.255.250:1900
MAN: "ssdp:discover"
MX: 2
ST: $st

''';

      print("DLNA: sending MULTICAST M-SEARCH for ST=$st");
      socket.send(
        utf8.encode(msg),
        InternetAddress(_ssdpAddress),
        _ssdpPort,
      );
    }

    // ⭐ Broadcast M-SEARCH (compatibilità router strani)
    print("DLNA: sending BROADCAST M‑SEARCH…");

    for (final st in searchTargets) {
      final msg = '''
M-SEARCH * HTTP/1.1
HOST: 255.255.255.255:1900
MAN: "ssdp:discover"
MX: 2
ST: $st

''';

      socket.send(
        utf8.encode(msg),
        InternetAddress("255.255.255.255"),
        1900,
      );
    }

    print("DLNA: M-SEARCH SENT");

    final devices = <DLNADevice>[];

    final completer = Completer<void>();
    final timer = Timer(const Duration(seconds: 2), () {
      print("DLNA: timeout reached, finishing discovery");
      completer.complete();
    });

    socket.listen((event) async {
      if (event == RawSocketEvent.read) {
        final dg = socket.receive();
        if (dg == null) return;

        print("DLNA: received UDP packet from ${dg.address.address}");

        final response = utf8.decode(dg.data);

        if (!response.contains("200 OK")) {
          print("DLNA: packet ignored (not 200 OK)");
          return;
        }

        print("DLNA: SSDP response:\n$response");

        final location = _extractHeader(response, "LOCATION");
        if (location == null) {
          print("DLNA: LOCATION header missing");
          return;
        }

        print("DLNA: LOCATION = $location");

        final device = await _loadDevice(location);
        if (device != null) {
          final loc = Uri.parse(location);

          // ⭐ Filtro anti‑duplicati basato su LOCATION (origin) + friendlyName
          final already = devices.any((d) {
            return d.avTransportUrl.origin == loc.origin &&
                   d.info.friendlyName == device.info.friendlyName;
          });

          if (!already) {
            print("DLNA: DEVICE ADDED (unique) → ${device.info.friendlyName}");
            devices.add(device);
          } else {
            print("DLNA: DUPLICATE IGNORED for ${device.info.friendlyName}");
          }
        } else {
          print("DLNA: device load FAILED");
        }
      }
    });

    await completer.future;
    timer.cancel();
    socket.close();

    print("DLNA: discovery FINISHED → ${devices.length} devices found");

    try {
      print("DLNA: releasing multicast lock…");
      await _channel.invokeMethod("dlnaReleaseLock");
      print("DLNA: multicast lock RELEASED");
    } catch (e) {
      print("DLNA: ERROR releasing lock → $e");
    }

    return devices;
  }

  // ⭐ Carica il device description e costruisce il DLNADevice VERO
  Future<DLNADevice?> _loadDevice(String url) async {
    print("DLNA: loading device description from $url");

    try {
      final client = HttpClient();
      final req = await client.getUrl(Uri.parse(url));
      final res = await req.close();
      final xmlStr = await res.transform(utf8.decoder).join();

      final doc = XmlDocument.parse(xmlStr);
      final device = doc.findAllElements("device").first;

      final friendly = device.getElement("friendlyName")?.innerText ?? "DLNA";

      // ⭐ Trova i servizi AVTransport e ConnectionManager
      Uri? avTransport;
      Uri? connectionManager;

      for (final s in device.findAllElements("service")) {
        final type = s.getElement("serviceType")?.innerText ?? "";

        if (type.contains("AVTransport")) {
          final ctrl = s.getElement("controlURL")?.innerText ?? "";
          avTransport = Uri.parse(_resolveUrl(url, ctrl));
        }

        if (type.contains("ConnectionManager")) {
          final ctrl = s.getElement("controlURL")?.innerText ?? "";
          connectionManager = Uri.parse(_resolveUrl(url, ctrl));
        }
      }

      if (avTransport == null || connectionManager == null) {
        print("DLNA: missing AVTransport or ConnectionManager");
        return null;
      }

      return DLNADevice(
        avTransportUrl: avTransport,
        connectionManagerUrl: connectionManager,
        info: DLNAInfo(friendlyName: friendly),
      );
    } catch (e) {
      print("DLNA: ERROR parsing device → $e");
      return null;
    }
  }

  // ⭐ Risolve URL relativi
  String _resolveUrl(String base, String path) {
    final b = Uri.parse(base);
    return Uri(
      scheme: b.scheme,
      host: b.host,
      port: b.port,
      path: path.startsWith("/") ? path : "/$path",
    ).toString();
  }

  // ⭐ Fix definitivo per LOCATION
  String? _extractHeader(String response, String header) {
    for (final line in response.split("\r\n")) {
      if (line.toUpperCase().startsWith("$header:")) {
        final idx = line.indexOf(":");
        if (idx == -1) return null;
        return line.substring(idx + 1).trim();
      }
    }
    return null;
  }
}
