"""
Modulo de impresion para ICEBERG POS.
Genera tickets termicos 80mm y los envia a la impresora Windows via win32print.
Reporte de cierre usa python-escpos con Win32Raw.

IMPORTANTE: Solo usar caracteres ASCII para la impresora (no Unicode).
La POS-80C no soporta bien ─ ═ ¡ y similares en cp437.
"""
import os
import json
from datetime import datetime

_DATA_DIR   = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'data')
_CFG_FILE   = os.path.join(_DATA_DIR, 'ticket_config.json')

_DEFAULTS = {
    "printer_name":       "",
    "sede":               "Cuenca",
    "auto_date":          True,
    "auto_time":          True,
    "date_override":      "",
    "time_override":      "",
    "receipt_number":     1,
    "header_tiempo":      "Tiempo",
    "header_descripcion": "Descripcion",
    "header_monto":       "Monto",
    "footer_message":     "Gracias por su visita!\nVuelva pronto y siga disfrutando!",
    "auto_print":         True,
}

W = 42  # caracteres por linea en impresora 80mm
SEP  = "-" * W
SEP2 = "=" * W


# ──────────────────────────────────────────────
# Config
# ──────────────────────────────────────────────

def load_config() -> dict:
    try:
        with open(_CFG_FILE, encoding='utf-8') as f:
            data = json.load(f)
        cfg = dict(_DEFAULTS)
        cfg.update(data)
        return cfg
    except Exception:
        return dict(_DEFAULTS)


def save_config(cfg: dict) -> None:
    os.makedirs(_DATA_DIR, exist_ok=True)
    with open(_CFG_FILE, 'w', encoding='utf-8') as f:
        json.dump(cfg, f, indent=2, ensure_ascii=False)


# ──────────────────────────────────────────────
# Impresoras
# ──────────────────────────────────────────────

def get_printers() -> list:
    """
    Devuelve lista de nombres de impresoras instaladas en Windows.
    Combina dos fuentes para capturar todas las impresoras:
      1. win32print.EnumPrinters  — impresoras locales y de red
      2. PowerShell Get-Printer   — refleja exactamente Configuracion > Impresoras
    """
    names: list[str] = []
    seen: set[str]   = set()

    def _add(name: str):
        n = name.strip()
        if n and n not in seen:
            seen.add(n)
            names.append(n)

    try:
        import win32print
        for flags in (
            win32print.PRINTER_ENUM_LOCAL | win32print.PRINTER_ENUM_CONNECTIONS,
            win32print.PRINTER_ENUM_LOCAL,
        ):
            try:
                for p in win32print.EnumPrinters(flags, None, 1):
                    _add(p[2])
            except Exception:
                pass
    except Exception:
        pass

    try:
        import subprocess
        r = subprocess.run(
            ["powershell", "-NoProfile", "-NonInteractive", "-Command",
             "Get-Printer | Select-Object -ExpandProperty Name"],
            capture_output=True, text=True, timeout=6,
            creationflags=0x08000000,
        )
        for line in r.stdout.splitlines():
            _add(line)
    except Exception:
        pass

    return names


def get_default_printer() -> str:
    try:
        import win32print
        return win32print.GetDefaultPrinter()
    except Exception:
        return ""


# ──────────────────────────────────────────────
# Numeracion de recibo
# ──────────────────────────────────────────────

def increment_receipt_number() -> int:
    """Incrementa el numero de recibo, lo guarda y retorna el nuevo numero."""
    cfg = load_config()
    n = cfg.get("receipt_number", 1) + 1
    cfg["receipt_number"] = n
    save_config(cfg)
    return n


# ──────────────────────────────────────────────
# Helpers
# ──────────────────────────────────────────────

def _lr(left: str, right: str, width: int = W) -> str:
    gap = width - len(left) - len(right)
    return left + (' ' * max(gap, 1)) + right


def _mins_to_str(mins: int) -> str:
    if mins >= 60 and mins % 60 == 0:
        return f"{mins // 60}h"
    if mins >= 60:
        return f"{mins // 60}h{mins % 60}m"
    return f"{mins}min"




# ──────────────────────────────────────────────
# Vista previa del ticket (texto plano para la UI)
# ──────────────────────────────────────────────

def format_preview(sale_data: dict, cfg: dict = None) -> str:
    if cfg is None:
        cfg = load_config()

    now = datetime.now()
    date_str    = now.strftime("%d/%m/%Y")
    time_str    = now.strftime("%H:%M")
    receipt_num = cfg.get("receipt_number", 1) + 1
    sede        = cfg.get("sede", "Cuenca")
    footer      = cfg.get("footer_message", "Gracias por su visita!")

    pkg_name = sale_data.get("package_name", "Combo 1 Hora")
    mins     = sale_data.get("minutes", 60)
    amount   = sale_data.get("amount", 5.00)
    name     = sale_data.get("customer_name", "")
    folio    = sale_data.get("folio_number", "")

    # Vista previa usa Unicode (se ve bien en pantalla)
    S1 = "=" * W
    S2 = "-" * W

    lines = [
        S1,
        f"ICEBERG {sede}".upper().center(W),
        "Sistema de Pulseras LED".center(W),
        S2,
        _lr(f"Fecha: {date_str}", f"Hora: {time_str}"),
        f"Recibo: #{receipt_num:06d}".center(W),
        S2,
        "",
        _lr("Combo:", pkg_name),
        _lr("Tiempo:", _mins_to_str(mins)),
        _lr("Monto:", f"${amount:.2f}"),
    ]
    if name:
        lines.append(_lr("Cliente:", name))
    if folio:
        lines.append(_lr("Folio:", folio))
    lines.append("")
    lines.append(S1)
    for fline in footer.split('\n'):
        lines.append(fline.center(W))
    lines.append("")
    return "\n".join(lines)


# ──────────────────────────────────────────────
# Bytes ESC/POS para impresora (ticket de venta)
# ──────────────────────────────────────────────

def format_bytes(sale_data: dict, cfg: dict, receipt_num: int) -> bytes:
    ESC       = b'\x1b'
    INIT      = ESC + b'\x40'
    CENTER    = ESC + b'\x61\x01'
    LEFT      = ESC + b'\x61\x00'
    BOLD_ON   = ESC + b'\x45\x01'
    BOLD_OFF  = ESC + b'\x45\x00'
    SIZE_BIG  = ESC + b'\x21\x30'
    SIZE_NORM = ESC + b'\x21\x00'
    CUT       = b'\x1d\x56\x42\x03'

    ENC = 'cp437'

    def t(s: str) -> bytes:
        return s.encode(ENC, errors='replace') + b'\n'

    now      = datetime.now()
    date_str = now.strftime("%d/%m/%Y")
    time_str = now.strftime("%H:%M")
    sede     = cfg.get("sede", "Cuenca")
    footer   = cfg.get("footer_message", "Gracias por su visita!")

    pkg_name = sale_data.get("package_name", "")
    mins     = sale_data.get("minutes", 0)
    amount   = sale_data.get("amount", 0.0)
    name     = sale_data.get("customer_name", "")
    folio    = sale_data.get("folio_number", "")

    buf = bytearray()
    buf += INIT

    # === Encabezado ===
    buf += CENTER + SIZE_BIG + BOLD_ON
    buf += f"ICEBERG {sede}".upper().encode(ENC, errors='replace') + b'\n'
    buf += SIZE_NORM + BOLD_OFF + CENTER
    buf += t("Sistema de Pulseras LED")
    buf += t(SEP)

    # === Fecha / Recibo ===
    buf += LEFT
    buf += t(_lr(f"Fecha: {date_str}", f"Hora: {time_str}"))
    buf += CENTER + BOLD_ON
    buf += t(f"Recibo: #{receipt_num:06d}")
    buf += BOLD_OFF
    buf += t(SEP)

    # === Datos de la venta ===
    buf += LEFT
    buf += b'\n'
    buf += BOLD_ON
    buf += t(_lr("Combo:", pkg_name))
    buf += BOLD_OFF
    buf += t(_lr("Tiempo:", _mins_to_str(mins)))
    buf += t(_lr("Monto:", f"${amount:.2f}"))
    if name:
        buf += t(_lr("Cliente:", name))
    if folio:
        buf += t(_lr("Folio:", folio))
    buf += b'\n'
    buf += t(SEP2)

    # === Footer ===
    buf += CENTER
    for fline in footer.split('\n'):
        buf += fline.encode(ENC, errors='replace') + b'\n'

    buf += b'\n\n\n'
    buf += CUT
    return bytes(buf)


# ──────────────────────────────────────────────
# Imprimir ticket
# ──────────────────────────────────────────────

def print_ticket(sale_data: dict, cfg: dict = None, receipt_num: int = None):
    if cfg is None:
        cfg = load_config()

    printer_name = cfg.get("printer_name", "").strip()
    if not printer_name:
        printer_name = get_default_printer()
    if not printer_name:
        return False, "No hay impresora configurada"

    if receipt_num is None:
        receipt_num = cfg.get("receipt_number", 1)

    ticket = format_bytes(sale_data, cfg, receipt_num)

    try:
        import win32print
        hp = win32print.OpenPrinter(printer_name)
        try:
            win32print.StartDocPrinter(hp, 1, ("ICEBERG Ticket", None, "RAW"))
            win32print.StartPagePrinter(hp)
            win32print.WritePrinter(hp, ticket)
            win32print.EndPagePrinter(hp)
            win32print.EndDocPrinter(hp)
        finally:
            win32print.ClosePrinter(hp)
        return True, ""
    except ImportError:
        return False, "pywin32 no esta instalado (pip install pywin32)"
    except Exception as e:
        return False, str(e)


def print_after_sale(sale_data: dict) -> None:
    """Llamar desde hilo secundario despues de cada venta exitosa."""
    cfg = load_config()
    if not cfg.get("auto_print", True):
        return
    receipt_num = increment_receipt_number()
    print_ticket(sale_data, cfg, receipt_num)


# ──────────────────────────────────────────────
# Cierre de Ventas (Reporte)
# ──────────────────────────────────────────────

class ReporteCierre:
    """
    Imprime el reporte de cierre de caja con desglose por paquete.
    Usa python-escpos Win32Raw.
    """

    def __init__(self, printer_name: str = None):
        cfg = load_config()
        self._printer_name = (printer_name or cfg.get("printer_name", "")).strip()
        if not self._printer_name:
            self._printer_name = get_default_printer()
        if not self._printer_name:
            raise RuntimeError("No hay impresora configurada")
        self._sede = cfg.get("sede", "Cuenca")

    def imprimir(self, report_data: dict, cashier_name: str = "") -> None:
        """
        Imprime el cierre con desglose real por paquete,
        anulaciones y autorizaciones del dia.
        report_data viene de api.get_daily_report():
          {"date", "total_sales", "total_revenue", "by_package", "anulaciones", "autorizaciones"}
        """
        now      = datetime.now()
        date_str = now.strftime("%d/%m/%Y")
        time_str = now.strftime("%H:%M:%S")

        total_sales    = report_data.get("total_sales", 0)
        total_revenue  = report_data.get("total_revenue", 0.0)
        by_package     = report_data.get("by_package", {})
        anulaciones    = report_data.get("anulaciones", [])
        autorizaciones = report_data.get("autorizaciones", [])

        try:
            from escpos.printer import Win32Raw
            p = Win32Raw(printer_name=self._printer_name)
        except ImportError:
            raise RuntimeError("python-escpos no instalado (pip install python-escpos)")
        except Exception as e:
            raise RuntimeError(f"No se puede abrir '{self._printer_name}': {e}")

        try:
            # ── Encabezado ──
            p.set(align="center", bold=True, double_height=True)
            p.text(f"ICEBERG {self._sede}\n".upper())
            p.set(align="center", bold=True, double_height=False)
            p.text("CIERRE DE CAJA\n")
            p.set(bold=False)
            p.text(SEP2 + "\n")

            # ── Fecha y hora ──
            p.set(align="left")
            p.text(_lr(f"Fecha: {date_str}", f"Hora: {time_str}") + "\n")
            if cashier_name:
                p.set(bold=True)
                p.text(_lr("Cajero:", cashier_name) + "\n")
                p.set(bold=False)
            p.text(SEP + "\n")
            p.text("\n")

            # ── Desglose por paquete ──
            p.set(align="center", bold=True)
            p.text("DETALLE POR PAQUETE\n")
            p.set(bold=False, align="left")
            p.text(SEP + "\n")

            if by_package:
                for item in by_package:
                    pkg_name = item.get("package_name", "?")
                    count    = item.get("count", 0)
                    subtotal = item.get("revenue", 0.0)
                    p.set(bold=True, align="left")
                    p.text(f" {pkg_name}\n")
                    p.set(bold=False)
                    p.text(_lr(f"   Cant: {count}", f"${subtotal:.2f}") + "\n")
            else:
                p.text("  (sin ventas)\n")

            p.text(SEP + "\n")
            p.text("\n")

            # ── Totales ──
            p.set(bold=True, align="left")
            p.text(_lr("TOTAL VENTAS:", str(total_sales)) + "\n")
            p.text(_lr("TOTAL INGRESOS:", f"${total_revenue:.2f}") + "\n")
            p.set(bold=False)
            p.text(SEP2 + "\n")

            # ── Anulaciones (solo si hay) ──
            if anulaciones:
                p.text("\n")
                p.set(align="center", bold=True)
                p.text(f"ANULACIONES ({len(anulaciones)})\n")
                p.set(bold=False, align="left")
                p.text(SEP + "\n")
                for a in anulaciones:
                    folio = a.get("folio", "")
                    pkg   = a.get("package", "")
                    amt   = a.get("amount", 0.0)
                    mot   = a.get("motivo", "")
                    sup   = a.get("supervisor", "")
                    caj   = a.get("cashier", "")
                    if folio:
                        p.set(bold=True)
                        p.text(f" {folio}\n")
                        p.set(bold=False)
                    p.text(_lr(f"   {pkg}", f"${amt:.2f}") + "\n")
                    if mot:
                        p.text(f"   Motivo: {mot[:30]}\n")
                    if sup:
                        p.text(f"   Supervisor: {sup}\n")
                    if caj:
                        p.text(f"   Cajero: {caj}\n")
                    p.text(SEP + "\n")

            # ── Autorizaciones (solo si hay) ──
            if autorizaciones:
                p.text("\n")
                p.set(align="center", bold=True)
                p.text(f"AUTORIZACIONES ({len(autorizaciones)})\n")
                p.set(bold=False, align="left")
                p.text(SEP + "\n")
                for au in autorizaciones:
                    label  = au.get("action_label", "")
                    folio  = au.get("folio", "")
                    detail = au.get("detail", "")
                    mot    = au.get("motivo", "")
                    sup    = au.get("supervisor", "")
                    caj    = au.get("cashier", "")
                    p.set(bold=True)
                    header = f" [{label}]"
                    if folio:
                        header += f" {folio}"
                    p.text(header + "\n")
                    p.set(bold=False)
                    if detail:
                        p.text(f"   {detail}\n")
                    if mot:
                        p.text(f"   Motivo: {mot[:30]}\n")
                    if sup:
                        p.text(f"   Supervisor: {sup}\n")
                    if caj:
                        p.text(f"   Cajero: {caj}\n")
                    p.text(SEP + "\n")

            # ── Pie ──
            p.text("\n")
            p.set(align="center")
            p.text("Tel: 0999958086\n")
            p.text(f"Sede: Iceberg {self._sede}\n")

            p.text("\n\n\n")
            p.cut()

        finally:
            p.close()

    @classmethod
    def desde_reporte(cls, report_data: dict, printer_name: str = None,
                      cashier_name: str = "") -> "ReporteCierre":
        """Crea instancia e imprime el cierre con los datos del reporte diario."""
        obj = cls(printer_name=printer_name)
        obj.imprimir(report_data, cashier_name=cashier_name)
        return obj


# ──────────────────────────────────────────────
# QR decorativo (raster ESC/POS)
# ──────────────────────────────────────────────

def _qr_escpos_bytes(text: str, module_size: int = 4) -> bytes:
    """Genera un QR como imagen raster ESC/POS (GS v 0)."""
    try:
        import qrcode
    except ImportError:
        return b''

    qr = qrcode.QRCode(
        box_size=1, border=1,
        error_correction=qrcode.constants.ERROR_CORRECT_L,
    )
    qr.add_data(text)
    qr.make(fit=True)
    matrix = qr.get_matrix()

    height = len(matrix)
    width = len(matrix[0])
    s_width = width * module_size
    s_height = height * module_size
    bytes_per_row = (s_width + 7) // 8

    raster_data = bytearray()
    for row in matrix:
        row_bits = bytearray(bytes_per_row)
        for x, cell in enumerate(row):
            if cell:
                for sx in range(module_size):
                    px = x * module_size + sx
                    row_bits[px // 8] |= (0x80 >> (px % 8))
        for _ in range(module_size):
            raster_data += row_bits

    # GS v 0 (raster bit image)
    cmd = b'\x1d\x76\x30\x00'
    cmd += bytes([bytes_per_row & 0xFF, (bytes_per_row >> 8) & 0xFF])
    cmd += bytes([s_height & 0xFF, (s_height >> 8) & 0xFF])
    cmd += bytes(raster_data)
    return cmd


# ──────────────────────────────────────────────
# Voucher de autorizacion de supervisor
# ──────────────────────────────────────────────

_ATRIBUCION_LABELS = {
    "error_cajero": "ERROR DEL CAJERO",
    "decision_supervisor": "DECISION DEL SUPERVISOR",
}

_ACCION_LABELS = {
    "cortesia": "Cortesia",
    "anulacion": "Anulacion",
    "reprogram": "Reprogramacion",
}


def format_voucher_bytes(voucher_data: dict, cfg: dict) -> bytes:
    """Genera bytes ESC/POS para el voucher de autorizacion de supervisor."""
    ESC       = b'\x1b'
    INIT      = ESC + b'\x40'
    CENTER    = ESC + b'\x61\x01'
    LEFT      = ESC + b'\x61\x00'
    BOLD_ON   = ESC + b'\x45\x01'
    BOLD_OFF  = ESC + b'\x45\x00'
    SIZE_BIG  = ESC + b'\x21\x30'
    SIZE_NORM = ESC + b'\x21\x00'
    CUT       = b'\x1d\x56\x42\x03'
    ENC       = 'cp437'

    def t(s: str) -> bytes:
        return s.encode(ENC, errors='replace') + b'\n'

    now      = datetime.now()
    date_str = now.strftime("%d/%m/%Y")
    time_str = now.strftime("%H:%M")
    sede     = cfg.get("sede", "Cuenca")

    accion      = voucher_data.get("accion", "")
    accion_label = _ACCION_LABELS.get(accion, accion.capitalize())
    motivo      = voucher_data.get("motivo", "")
    atribucion  = voucher_data.get("atribucion", "error_cajero")
    attr_label  = _ATRIBUCION_LABELS.get(atribucion, atribucion)
    cajero      = voucher_data.get("cajero", "")
    supervisor  = voucher_data.get("supervisor", "")
    sup_id      = voucher_data.get("supervisor_id", 0)

    buf = bytearray()
    buf += INIT

    # === Encabezado ===
    buf += CENTER + SIZE_BIG + BOLD_ON
    buf += f"ICEBERG {sede}".upper().encode(ENC, errors='replace') + b'\n'
    buf += SIZE_NORM + BOLD_OFF + CENTER
    buf += t("COMPROBANTE DE AUTORIZACION")
    buf += t(SEP2)

    # === Fecha ===
    buf += LEFT
    buf += t(_lr(f"Fecha: {date_str}", f"Hora: {time_str}"))
    buf += t(SEP)

    # === Datos de la autorizacion ===
    buf += b'\n'
    buf += BOLD_ON
    buf += t(_lr("Accion:", accion_label))
    buf += BOLD_OFF
    if motivo:
        buf += t(_lr("Motivo:", motivo[:30]))
        if len(motivo) > 30:
            buf += t(f"  {motivo[30:60]}")
            if len(motivo) > 60:
                buf += t(f"  {motivo[60:90]}")
    buf += BOLD_ON
    buf += t(_lr("Atribucion:", attr_label))
    buf += BOLD_OFF
    buf += b'\n'
    buf += t(SEP)

    # === Personas involucradas ===
    buf += t(_lr("Cajero:", cajero))
    buf += t(_lr("Supervisor:", supervisor))
    buf += b'\n'
    buf += t(SEP)

    # === QR decorativo ===
    qr_text = f"ICEBERG-AUTH-{now.strftime('%Y%m%d%H%M%S')}-{accion}-{sup_id}"
    qr_bytes = _qr_escpos_bytes(qr_text, module_size=3)
    if qr_bytes:
        buf += CENTER
        buf += qr_bytes
        buf += t("(codigo de verificacion)")
        buf += b'\n'
        buf += LEFT
        buf += t(SEP)

    # === Linea de firma ===
    buf += b'\n'
    buf += t("Firma del Supervisor:")
    buf += b'\n\n'
    buf += t("__________________________________________")
    buf += b'\n\n'
    buf += t(SEP2)

    # === Pie ===
    buf += CENTER
    buf += t("Documento de control interno")
    buf += t("ICEBERG - Sistema de Pulseras")

    buf += b'\n\n\n'
    buf += CUT
    return bytes(buf)


def print_voucher(voucher_data: dict) -> tuple[bool, str]:
    """Imprime el voucher de autorizacion de supervisor. Seguro para hilos."""
    cfg = load_config()
    printer_name = cfg.get("printer_name", "").strip()
    if not printer_name:
        printer_name = get_default_printer()
    if not printer_name:
        return False, "No hay impresora configurada"

    ticket = format_voucher_bytes(voucher_data, cfg)

    try:
        import win32print
        hp = win32print.OpenPrinter(printer_name)
        try:
            win32print.StartDocPrinter(hp, 1, ("ICEBERG Voucher Auth", None, "RAW"))
            win32print.StartPagePrinter(hp)
            win32print.WritePrinter(hp, ticket)
            win32print.EndPagePrinter(hp)
            win32print.EndDocPrinter(hp)
        finally:
            win32print.ClosePrinter(hp)
        return True, ""
    except ImportError:
        return False, "pywin32 no esta instalado"
    except Exception as e:
        return False, str(e)
