import concurrent.futures
import ipaddress
import logging
import platform
import socket
import subprocess
from typing import Dict, List, Optional

logger = logging.getLogger(__name__)


class ThermalPrinterManager:
    """
    [AI_DOC]
    Kuyumcu etiketleri (40x25mm, Kelebek Etiket vb.) icin Termal Barkod Yazici yoneticisi.
    PDF olusturmak yerine yaziciya dogrudan RAW (ZPL veya TSPL) komutlari gonderir.
    Windows (win32print) ve macOS/Linux (lpr -l) destekler.
    Ag yazicilari (IP) icin dogrudan TCP Soket (Port 9100) kullanir.
    [/AI_DOC]
    """

    def __init__(self, printer_name: str = "", printer_ip: str = ""):
        self.printer_name = printer_name.strip()
        self.printer_ip = printer_ip.strip()

    def get_available_printers(self) -> list:
        """Sistemdeki yuklu fiziksel yazicilarin listesini dondurur."""
        printers = []
        sys_name = platform.system()
        try:
            if sys_name == "Windows":
                try:
                    import win32print

                    printers = [p[2] for p in win32print.EnumPrinters(2)]
                except ImportError:
                    logger.warning("win32print modulu bulunamadi. Lutfen 'pip install pywin32' kurun.")
            else:
                result = subprocess.run(["lpstat", "-p"], capture_output=True, text=True)
                for line in result.stdout.split("\n"):
                    if line.startswith("printer"):
                        parts = line.split(" ")
                        if len(parts) > 1:
                            printers.append(parts[1])
        except Exception as e:
            logger.error(f"Yazici listesi alinamadi: {e}")
        return printers

    def get_local_ipv4_addresses(self) -> List[str]:
        """Aktif baglantilardan taramaya uygun yerel IPv4 adreslerini toplar."""
        ip_list = set()

        try:
            with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as probe:
                probe.connect(("8.8.8.8", 80))
                ip_list.add(str(probe.getsockname()[0]))
        except Exception:
            pass

        try:
            hostname = socket.gethostname()
            for addr_info in socket.getaddrinfo(hostname, None, socket.AF_INET, socket.SOCK_STREAM):
                ip = str((addr_info[4] or [""])[0] or "").strip()
                if self._is_scannable_local_ip(ip):
                    ip_list.add(ip)
        except Exception:
            pass

        return sorted(ip for ip in ip_list if self._is_scannable_local_ip(ip))

    def discover_network_printers(
        self, timeout: float = 0.25, max_workers: int = 64
    ) -> List[Dict[str, str]]:
        """
        Yerel agda 9100/TCP portu acik cihazlari bulur.
        Mevcut baski akisi 9100 kullandigi icin tarama bu port uzerinden yapilir.
        """
        candidate_ips = self._build_candidate_ip_pool()
        if not candidate_ips:
            return []

        found_devices: List[Dict[str, str]] = []
        worker_count = max(1, min(max_workers, len(candidate_ips)))

        with concurrent.futures.ThreadPoolExecutor(max_workers=worker_count) as executor:
            futures = [
                executor.submit(self._probe_network_printer, ip_addr, timeout)
                for ip_addr in candidate_ips
            ]
            for future in concurrent.futures.as_completed(futures):
                try:
                    result = future.result()
                except Exception:
                    continue
                if result:
                    found_devices.append(result)

        return sorted(found_devices, key=lambda item: self._sort_ip_key(item.get("ip", "")))

    def _build_candidate_ip_pool(self) -> List[str]:
        seen_ips = set()

        for local_ip in self.get_local_ipv4_addresses():
            try:
                network = ipaddress.ip_network(f"{local_ip}/24", strict=False)
            except ValueError:
                continue

            for host in network.hosts():
                host_ip = str(host)
                if host_ip != local_ip:
                    seen_ips.add(host_ip)

        return sorted(seen_ips, key=self._sort_ip_key)

    def _probe_network_printer(self, ip_addr: str, timeout: float) -> Optional[Dict[str, str]]:
        try:
            with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
                sock.settimeout(timeout)
                if sock.connect_ex((ip_addr, 9100)) != 0:
                    return None

            hostname = ""
            try:
                hostname = str(socket.gethostbyaddr(ip_addr)[0] or "").strip()
            except Exception:
                hostname = ""

            return {
                "ip": ip_addr,
                "hostname": hostname,
                "display_name": f"{hostname} ({ip_addr})" if hostname else f"{ip_addr} - Port 9100 acik",
            }
        except Exception:
            return None

    def _is_scannable_local_ip(self, ip_addr: str) -> bool:
        try:
            parsed = ipaddress.ip_address(str(ip_addr or "").strip())
        except ValueError:
            return False

        if parsed.version != 4:
            return False
        if parsed.is_loopback or parsed.is_multicast or parsed.is_unspecified:
            return False
        return bool(parsed.is_private or parsed.is_link_local)

    def _sort_ip_key(self, ip_addr: str):
        try:
            return tuple(int(part) for part in str(ip_addr).split("."))
        except Exception:
            return (999, 999, 999, 999)

    def print_jewelry_tag(
        self,
        product_name: str,
        barcode_val: str,
        karat: str,
        weight: str,
        price: str,
        language: str = "ZPL",
    ) -> bool:
        """
        Barkod komutunu olusturur ve gonderir.
        language: 'ZPL' (Zebra vb.) veya 'TSPL' (XPrinter, Argox vb.)
        """
        zpl_cmd = f"""^XA
^PW320
^LL200
^FO20,20^A0N,25,25^FD{product_name[:15]}^FS
^FO20,50^A0N,20,20^FD{karat}^FS
^FO180,50^A0N,20,20^FD{weight}^FS
^FO20,80^A0N,20,20^FD{price}^FS
^FO50,110^BCN,50,Y,N,N^FD{barcode_val}^FS
^XZ"""

        tspl_cmd = f"""SIZE 40 mm, 25 mm
GAP 2 mm, 0 mm
DIRECTION 1
CLS
TEXT 20,20,"TSS24.BF2",0,1,1,"{product_name[:15]}"
TEXT 20,50,"3",0,1,1,"{karat}"
TEXT 180,50,"3",0,1,1,"{weight}"
TEXT 20,80,"3",0,1,1,"{price}"
BARCODE 50,110,"128",40,1,0,2,2,"{barcode_val}"
PRINT 1,1
"""

        raw_data = zpl_cmd if language == "ZPL" else tspl_cmd
        raw_bytes = raw_data.encode("utf-8")

        if self.printer_ip:
            return self._print_network(raw_bytes)
        if self.printer_name:
            return self._print_usb(raw_bytes)

        logger.error("Ne yazici adi ne de IP adresi belirtilmis.")
        return False

    def _print_network(self, raw_bytes: bytes) -> bool:
        """Ag uzerinden (Ethernet/Wi-Fi) IP ile direkt baski yapar (Port 9100)."""
        try:
            with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
                sock.settimeout(5.0)
                sock.connect((self.printer_ip, 9100))
                sock.sendall(raw_bytes)
            return True
        except Exception as e:
            logger.error(f"Ag yazicisina gonderilemedi ({self.printer_ip}): {e}")
            return False

    def _print_usb(self, raw_bytes: bytes) -> bool:
        """USB veya sistem spooler'i uzerinden yerel yaziciya RAW veri gonderir."""
        sys_name = platform.system()
        try:
            if sys_name == "Windows":
                try:
                    import win32print
                except ImportError:
                    logger.error("Windows USB yazdirma icin 'pywin32' kutuphanesi gerekli.")
                    return False

                h_printer = win32print.OpenPrinter(self.printer_name)
                try:
                    win32print.StartDocPrinter(h_printer, 1, ("Golder Barcode", None, "RAW"))
                    win32print.StartPagePrinter(h_printer)
                    win32print.WritePrinter(h_printer, raw_bytes)
                    win32print.EndPagePrinter(h_printer)
                    win32print.EndDocPrinter(h_printer)
                finally:
                    win32print.ClosePrinter(h_printer)
                return True

            process = subprocess.Popen(["lpr", "-P", self.printer_name, "-l"], stdin=subprocess.PIPE)
            process.communicate(raw_bytes)
            return True
        except Exception as e:
            logger.error(f"USB yaziciya gonderilemedi ({self.printer_name}): {e}")
            return False




