import socket
import datetime
import os
import json
import subprocess
import sys

# Standard ESC/POS Command Constants
ESC = b'\x1b'
GS = b'\x1d'

CMD_INIT = ESC + b'@'                    # Initialize printer
CMD_ALIGN_LEFT = ESC + b'a\x00'         # Left justification
CMD_ALIGN_CENTER = ESC + b'a\x01'       # Center justification
CMD_ALIGN_RIGHT = ESC + b'a\x02'        # Right justification
CMD_BOLD_ON = ESC + b'E\x01'            # Bold text ON
CMD_BOLD_OFF = ESC + b'E\x00'           # Bold text OFF
CMD_TEXT_NORMAL = GS + b'!\x00'         # Normal size text
CMD_TEXT_DOUBLE_HEIGHT = GS + b'!\x01'  # 2x Height text
CMD_TEXT_DOUBLE_WIDTH = GS + b'!\x10'   # 2x Width text
CMD_TEXT_DOUBLE_BOTH = GS + b'!\x11'    # 2x Width & Height (Large)
CMD_TEXT_TRIPLE = GS + b'!\x22'         # 3x Large
CMD_FEED_CUT = b'\n\n\n\n' + GS + b'V\x42\x00'  # Feed lines and cut paper (partial/full)
CMD_SEPARATOR = b'------------------------------------------\n'

def encode_text(text):
    """Encode text for thermal printer using CP858 or CP437 with fallback."""
    if not text:
        return b''
    try:
        return text.encode('cp858', errors='replace')
    except Exception:
        try:
            return text.encode('cp437', errors='replace')
        except Exception:
            return text.encode('latin1', errors='replace')

def format_totem_order_tickets(order, categories_list=None):
    """
    Generate binary ESC/POS payload containing:
    1. Individual department tickets (one for each category with cut)
    2. Final summary payment receipt (with cut)
    """
    if not order:
        return b''

    date_str = order.get('created_at', '')
    if date_str:
        try:
            dt = datetime.datetime.fromisoformat(date_str.replace(' ', 'T'))
            date_formatted = dt.strftime('%d/%m/%Y %H:%M')
        except Exception:
            date_formatted = date_str
    else:
        date_formatted = datetime.datetime.now().strftime('%d/%m/%Y %H:%M')

    disp_num = f"#{order.get('display_number') or order.get('order_number')}"

    # Category name lookup
    cat_names = {'ALTRO': 'REPARTO ALTRO'}
    cat_no_kitchen = {}
    if categories_list:
        for c in categories_list:
            cat_id = c.get('id')
            cat_names[cat_id] = f"REPARTO {c.get('name', cat_id).upper()}"
            if c.get('no_kitchen'):
                cat_no_kitchen[cat_id] = True

    # Group items by category
    items_by_cat = {}
    for item in order.get('items', []):
        cat = item.get('category') or 'ALTRO'
        if cat not in items_by_cat:
            items_by_cat[cat] = []
        items_by_cat[cat].append(item)

    payload = bytearray()
    payload.extend(CMD_INIT)

    # 1. Department tickets (one per kitchen/bar department)
    for cat, items in items_by_cat.items():
        cat_title = cat_names.get(cat, f"REPARTO {cat}")
        
        payload.extend(CMD_ALIGN_CENTER)
        payload.extend(CMD_BOLD_ON)
        payload.extend(CMD_TEXT_NORMAL)
        payload.extend(encode_text("ESTATE BEDULITESE - POLISPORTIVA APB\n"))
        payload.extend(CMD_TEXT_DOUBLE_HEIGHT)
        payload.extend(encode_text(f"{cat_title}\n"))
        payload.extend(CMD_BOLD_OFF)
        payload.extend(CMD_TEXT_NORMAL)
        payload.extend(CMD_SEPARATOR)

        payload.extend(CMD_ALIGN_LEFT)
        payload.extend(CMD_BOLD_ON)
        payload.extend(CMD_TEXT_DOUBLE_BOTH)
        payload.extend(encode_text(f" {disp_num} "))
        
        queue_idx = items[0].get('queue_index')
        if queue_idx and not cat_no_kitchen.get(cat):
            payload.extend(CMD_TEXT_NORMAL)
            payload.extend(encode_text(f"  [CODA: {queue_idx}]"))
        
        payload.extend(b'\n')
        payload.extend(CMD_BOLD_OFF)
        payload.extend(CMD_TEXT_NORMAL)
        payload.extend(encode_text(f"Data: {date_formatted}\n"))
        payload.extend(CMD_SEPARATOR)

        # Items in this category
        payload.extend(CMD_BOLD_ON)
        for itm in items:
            qty = itm.get('quantity', 1)
            name = (itm.get('item_name') or itm.get('name') or 'Piatto').upper()
            notes = itm.get('notes', '').strip()
            
            payload.extend(CMD_TEXT_DOUBLE_HEIGHT)
            payload.extend(encode_text(f"{qty:>2}x  {name}\n"))
            if notes:
                payload.extend(CMD_TEXT_NORMAL)
                payload.extend(encode_text(f"     * {notes}\n"))

        payload.extend(CMD_BOLD_OFF)
        payload.extend(CMD_TEXT_NORMAL)
        payload.extend(CMD_SEPARATOR)
        payload.extend(CMD_ALIGN_CENTER)
        payload.extend(encode_text("Ritira questo piatto al relativo banco\n"))
        payload.extend(CMD_FEED_CUT)

    # 2. Final summary payment receipt
    payload.extend(CMD_ALIGN_CENTER)
    payload.extend(CMD_BOLD_ON)
    payload.extend(CMD_TEXT_NORMAL)
    payload.extend(encode_text("ESTATE BEDULITESE - POLISPORTIVA APB\n"))
    payload.extend(encode_text("RICEVUTA DI PAGAMENTO (TOTEM)\n"))
    payload.extend(CMD_BOLD_OFF)
    payload.extend(CMD_SEPARATOR)

    payload.extend(CMD_ALIGN_LEFT)
    payload.extend(CMD_BOLD_ON)
    payload.extend(CMD_TEXT_DOUBLE_HEIGHT)
    payload.extend(encode_text(f"ORDINE {disp_num}\n"))
    payload.extend(CMD_BOLD_OFF)
    payload.extend(CMD_TEXT_NORMAL)
    payload.extend(encode_text(f"Data: {date_formatted} - PAGATO ONLINE\n"))
    if order.get('customer_name'):
        payload.extend(encode_text(f"Cliente: {order.get('customer_name')}\n"))
    payload.extend(CMD_SEPARATOR)

    # Items table with prices
    total = 0.0
    for itm in order.get('items', []):
        qty = itm.get('quantity', 1)
        name = (itm.get('item_name') or itm.get('name') or 'Piatto').upper()
        price = itm.get('price_at_order') or itm.get('price') or 0.0
        row_tot = qty * price
        total += row_tot
        
        # Format line max 42 chars
        # format: " 2x PIZZA MARGHERITA              16.00"
        qty_name = f"{qty:>2}x {name}"[:32]
        price_str = f"{row_tot:>7.2f}"
        spacing = max(1, 42 - len(qty_name) - len(price_str))
        line = f"{qty_name}{' ' * spacing}{price_str}\n"
        payload.extend(encode_text(line))

    order_tot = float(order.get('total_price') or total)
    payload.extend(CMD_SEPARATOR)
    payload.extend(CMD_BOLD_ON)
    payload.extend(CMD_TEXT_DOUBLE_HEIGHT)
    tot_line = f"TOTALE PAGATO:        {order_tot:>7.2f} EUR\n"
    payload.extend(encode_text(tot_line))
    payload.extend(CMD_BOLD_OFF)
    payload.extend(CMD_TEXT_NORMAL)
    payload.extend(CMD_SEPARATOR)

    payload.extend(CMD_ALIGN_CENTER)
    payload.extend(encode_text("Consegna i biglietti dei singoli reparti\n"))
    payload.extend(encode_text("alle rispettive casse/cucine per il ritiro.\n"))
    payload.extend(encode_text("Grazie e buon appetito!\n"))
    payload.extend(CMD_FEED_CUT)

    return bytes(payload)

def print_to_network_printer(ip_address, port=9100, data=b"", timeout=5):
    """Send raw ESC/POS bytes directly to a network thermal printer on TCP port 9100."""
    if not ip_address:
        raise ValueError("Indirizzo IP della stampante non specificato.")
    if not data:
        raise ValueError("Nessun dato da stampare.")

    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.settimeout(timeout)
    try:
        s.connect((ip_address.strip(), int(port)))
        s.sendall(data)
        return {"success": True, "message": f"Dati inviati con successo a {ip_address}:{port}"}
    except Exception as e:
        raise ConnectionError(f"Impossibile raggiungere la stampante termica all'indirizzo {ip_address}:{port} ({e})")
    finally:
        try:
            s.close()
        except Exception:
            pass

def print_to_system_printer(printer_name, data=b""):
    """Send raw ESC/POS bytes to a local system/USB printer queue (CUPS on Mac/Linux or spooler on Windows)."""
    if sys.platform.startswith('win'):
        try:
            import win32print
            p = win32print.OpenPrinter(printer_name)
            try:
                job = win32print.StartDocPrinter(p, 1, ("Totem Scontrino", None, "RAW"))
                win32print.StartPagePrinter(p)
                win32print.WritePrinter(p, data)
                win32print.EndPagePrinter(p)
                win32print.EndDocPrinter(p)
                return {"success": True, "message": f"Dati inviati alla stampante di sistema {printer_name}"}
            finally:
                win32print.ClosePrinter(p)
        except Exception as e:
            raise RuntimeError(f"Errore invio a stampante Windows '{printer_name}': {e}")
    else:
        # Mac / Linux CUPS raw print
        try:
            cmd = ['lpr', '-P', printer_name, '-l']
            proc = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
            stdout, stderr = proc.communicate(input=data)
            if proc.returncode != 0:
                raise RuntimeError(stderr.decode() or "Errore comando lpr")
            return {"success": True, "message": f"Dati inviati a {printer_name} via CUPS"}
        except Exception as e:
            raise RuntimeError(f"Errore invio a stampante CUPS '{printer_name}': {e}")

def print_totem_order(order, settings=None):
    """
    Main helper called by server.py when an order is validated/printed at the Totem.
    Reads printer IP / name from settings.json, generates ESC/POS tickets, and sends them.
    """
    if not settings:
        settings = {}
        if os.path.exists("settings.json"):
            try:
                with open("settings.json", "r", encoding="utf-8") as f:
                    settings = json.load(f)
            except Exception:
                pass

    printer_target = (settings.get("totem_printer_ip") or "").strip()
    printer_port = int(settings.get("totem_printer_port", 9100))

    if not printer_target:
        return {
            "success": False,
            "error": "NO_PRINTER_CONFIGURED",
            "message": "Nessuna stampante configurata in settings.json (totem_printer_ip)."
        }

    # Load categories if present for emojis and names
    cats = []
    if os.path.exists("categories.json"):
        try:
            with open("categories.json", "r", encoding="utf-8") as f:
                cats = json.load(f)
        except Exception:
            pass

    try:
        raw_data = format_totem_order_tickets(order, categories_list=cats)
        
        # Se è un indirizzo IP (contiene punti e numeri)
        if any(c.isdigit() for c in printer_target) and '.' in printer_target and not printer_target.lower().startswith('usb:'):
            res = print_to_network_printer(printer_target, printer_port, raw_data)
        else:
            # Nome stampante di sistema USB (es. "EPSON_TM_T20II" o "usb:EPSON")
            p_name = printer_target.replace('usb:', '').replace('printer:', '').strip()
            res = print_to_system_printer(p_name, raw_data)

        print(f"[Totem Stampa Server] ✅ Scontrini stampati con successo per ordine #{order.get('order_number')} su {printer_target}")
        return res
    except Exception as e:
        print(f"[Totem Stampa Server] ⚠️ Errore di stampa fisica su {printer_target}: {e}")
        return {"success": False, "error": str(e)}
