"""ESC/POS thermal printer commands for 58mm and 80mm printers.

Generates raw ESC/POS byte commands that can be sent to a thermal printer
via USB, serial, or network connection.
"""

# ESC/POS command constants
ESC = b'\x1b'
GS = b'\x1d'
INIT = ESC + b'@'           # Initialize printer
CUT = GS + b'V' + b'\x00'  # Full cut
PARTIAL_CUT = GS + b'V' + b'\x01'
FEED = ESC + b'd'           # Feed N lines
ALIGN_LEFT = ESC + b'a' + b'\x00'
ALIGN_CENTER = ESC + b'a' + b'\x01'
ALIGN_RIGHT = ESC + b'a' + b'\x02'
BOLD_ON = ESC + b'E' + b'\x01'
BOLD_OFF = ESC + b'E' + b'\x00'
DOUBLE_HEIGHT = ESC + b'!' + b'\x10'
NORMAL_SIZE = ESC + b'!' + b'\x00'
LARGE_SIZE = ESC + b'!' + b'\x30'  # Double width + double height


def generate_ticket(sale_data, business_info, width=80):
    """Generate ESC/POS bytes for a sale ticket.

    Args:
        sale_data: dict with sale info (items, totals, payment, folio)
        business_info: dict with business name, RFC, address
        width: 58 or 80 (mm)

    Returns: bytes ready to send to printer
    """
    chars = 32 if width == 58 else 48  # characters per line
    buf = bytearray()
    buf += INIT

    # Header: business name (centered, bold, large)
    buf += ALIGN_CENTER
    buf += LARGE_SIZE
    buf += (business_info.get('name', 'NEXUS POS') + '\n').encode('cp437', errors='replace')
    buf += NORMAL_SIZE
    buf += (business_info.get('rfc', '') + '\n').encode('cp437', errors='replace')
    buf += (business_info.get('address', '') + '\n').encode('cp437', errors='replace')
    buf += b'\n'

    # Folio + date
    buf += ALIGN_LEFT
    buf += BOLD_ON
    folio = sale_data.get('folio', 'N/A')
    date = sale_data.get('date', '')
    buf += f'Folio: {folio}\n'.encode('cp437', errors='replace')
    buf += BOLD_OFF
    buf += f'Fecha: {date}\n'.encode('cp437', errors='replace')
    buf += f'Cajero: {sale_data.get("employee", "")}\n'.encode('cp437', errors='replace')
    if sale_data.get('customer'):
        buf += f'Cliente: {sale_data["customer"]}\n'.encode('cp437', errors='replace')
    buf += ('-' * chars + '\n').encode()

    # Column header
    buf += BOLD_ON
    hdr = _format_line('Cant  Descripcion', 'Importe', chars)
    buf += (hdr + '\n').encode('cp437', errors='replace')
    buf += BOLD_OFF
    buf += ('-' * chars + '\n').encode()

    # Items
    for item in sale_data.get('items', []):
        name = item.get('name', '')[:chars - 10]
        qty = item.get('quantity', 1)
        subtotal = item.get('subtotal', 0)
        buf += f'{qty}x {name}\n'.encode('cp437', errors='replace')
        buf += ALIGN_RIGHT
        buf += f'${subtotal:,.2f}\n'.encode('cp437', errors='replace')
        buf += ALIGN_LEFT

    buf += ('-' * chars + '\n').encode()

    # Totals
    buf += ALIGN_RIGHT
    buf += _total_line('Subtotal:', sale_data.get('subtotal', 0), chars).encode('cp437', errors='replace')
    if sale_data.get('discount_total', 0) > 0:
        buf += _total_line('Descuento:', -sale_data['discount_total'], chars).encode('cp437', errors='replace')
    buf += _total_line('IVA 16%:', sale_data.get('tax_total', 0), chars).encode('cp437', errors='replace')
    buf += BOLD_ON + DOUBLE_HEIGHT
    buf += _total_line('TOTAL:', sale_data.get('total', 0), chars).encode('cp437', errors='replace')
    buf += NORMAL_SIZE + BOLD_OFF

    # Payment
    buf += ALIGN_LEFT
    buf += f'\nPago: {sale_data.get("payment_method", "Efectivo")}\n'.encode('cp437', errors='replace')
    if sale_data.get('amount_paid'):
        buf += f'Recibido: ${sale_data["amount_paid"]:,.2f}\n'.encode('cp437', errors='replace')
    if sale_data.get('change_given'):
        buf += f'Cambio: ${sale_data["change_given"]:,.2f}\n'.encode('cp437', errors='replace')

    # Footer
    buf += b'\n'
    buf += ALIGN_CENTER
    buf += 'Gracias por su compra!\n'.encode('cp437', errors='replace')
    buf += 'Nexus Autoparts POS\n'.encode('cp437', errors='replace')
    buf += b'\n\n\n'
    buf += PARTIAL_CUT

    return bytes(buf)


def generate_quotation_ticket(quote_data, business_info, width=80):
    """Generate ESC/POS bytes for a quotation ticket.

    Args:
        quote_data: dict with keys: id, items[{name, part_number, quantity, unit_price, subtotal}],
                    subtotal, tax_total, total, valid_until, customer_name, wa_phone, created_at
        business_info: dict with name, rfc, address
        width: 58 or 80 (mm)

    Returns: bytes ready to send to printer
    """
    chars = 32 if width == 58 else 48
    buf = bytearray()
    buf += INIT

    # Header
    buf += ALIGN_CENTER
    buf += LARGE_SIZE
    buf += (business_info.get('name', 'NEXUS POS') + '\n').encode('cp437', errors='replace')
    buf += NORMAL_SIZE
    if business_info.get('rfc'):
        buf += (business_info['rfc'] + '\n').encode('cp437', errors='replace')
    if business_info.get('address'):
        buf += (business_info['address'] + '\n').encode('cp437', errors='replace')
    buf += b'\n'

    # Title
    buf += BOLD_ON + DOUBLE_HEIGHT
    buf += 'COTIZACION\n'.encode('cp437', errors='replace')
    buf += NORMAL_SIZE + BOLD_OFF
    buf += b'\n'

    # Folio + date
    buf += ALIGN_LEFT
    buf += BOLD_ON
    buf += f'Folio: COT-{quote_data.get("id", "")}\n'.encode('cp437', errors='replace')
    buf += BOLD_OFF
    buf += f'Fecha: {str(quote_data.get("created_at", ""))[:10]}\n'.encode('cp437', errors='replace')
    buf += f'Vigencia: {quote_data.get("valid_until", "7 dias")}\n'.encode('cp437', errors='replace')
    if quote_data.get('customer_name'):
        buf += f'Cliente: {quote_data["customer_name"]}\n'.encode('cp437', errors='replace')
    if quote_data.get('wa_phone'):
        buf += f'WhatsApp: {quote_data["wa_phone"]}\n'.encode('cp437', errors='replace')
    buf += ('-' * chars + '\n').encode()

    # Column header
    buf += BOLD_ON
    hdr = _format_line('Cant  Descripcion', 'Importe', chars)
    buf += (hdr + '\n').encode('cp437', errors='replace')
    buf += BOLD_OFF
    buf += ('-' * chars + '\n').encode()

    # Items
    for item in quote_data.get('items', []):
        name = item.get('name', '')[:chars - 10]
        part_no = item.get('part_number', '')
        qty = item.get('quantity', 1)
        subtotal = item.get('subtotal', 0)
        buf += f'{qty}x {name}\n'.encode('cp437', errors='replace')
        if part_no:
            buf += f'   #{part_no}\n'.encode('cp437', errors='replace')
        buf += ALIGN_RIGHT
        buf += f'${subtotal:,.2f}\n'.encode('cp437', errors='replace')
        buf += ALIGN_LEFT

    buf += ('-' * chars + '\n').encode()

    # Totals
    buf += ALIGN_RIGHT
    buf += _total_line('Subtotal:', quote_data.get('subtotal', 0), chars).encode('cp437', errors='replace')
    buf += _total_line('IVA:', quote_data.get('tax_total', 0), chars).encode('cp437', errors='replace')
    buf += BOLD_ON + DOUBLE_HEIGHT
    buf += _total_line('TOTAL:', quote_data.get('total', 0), chars).encode('cp437', errors='replace')
    buf += NORMAL_SIZE + BOLD_OFF

    # Footer
    buf += b'\n'
    buf += ALIGN_CENTER
    buf += 'Esta cotizacion no es comprobante fiscal\n'.encode('cp437', errors='replace')
    buf += 'Precios sujetos a disponibilidad\n'.encode('cp437', errors='replace')
    buf += 'Nexus Autoparts POS\n'.encode('cp437', errors='replace')
    buf += b'\n\n\n'
    buf += PARTIAL_CUT

    return bytes(buf)


def _format_line(left, right, width):
    """Pad a left-right line to fill the ticket width."""
    space = width - len(left) - len(right)
    if space < 1:
        space = 1
    return left + ' ' * space + right


def _total_line(label, amount, width):
    """Format a totals line like 'Subtotal:        $1,234.56'."""
    val = f'${abs(amount):,.2f}' if amount >= 0 else f'-${abs(amount):,.2f}'
    return _format_line(label, val, width) + '\n'


def generate_service_order_ticket(so_data, business_info, width=80):
    """Generate ESC/POS bytes for a workshop service order ticket.

    Args:
        so_data: dict with service order info:
            order_number, status, customer_name, vehicle_plate, vehicle_make,
            vehicle_model, mileage_in, fuel_level, reception_notes,
            employee_name, created_at, items[{name, part_number, quantity,
            unit_price}], labor[{description, hours, hourly_rate, total_cost}],
            estimated_cost, total
        business_info: dict with name, rfc, address
        width: 58 or 80 (mm)

    Returns: bytes ready to send to printer
    """
    chars = 32 if width == 58 else 48
    buf = bytearray()
    buf += INIT

    # Header
    buf += ALIGN_CENTER
    buf += LARGE_SIZE
    buf += (business_info.get("name", "NEXUS POS") + "\n").encode("cp437", errors="replace")
    buf += NORMAL_SIZE
    if business_info.get("rfc"):
        buf += (business_info["rfc"] + "\n").encode("cp437", errors="replace")
    if business_info.get("address"):
        buf += (business_info["address"] + "\n").encode("cp437", errors="replace")
    buf += b"\n"

    # Title
    buf += BOLD_ON + DOUBLE_HEIGHT
    buf += "ORDEN DE SERVICIO\n".encode("cp437", errors="replace")
    buf += NORMAL_SIZE + BOLD_OFF
    buf += b"\n"

    # Order info
    buf += ALIGN_LEFT
    buf += BOLD_ON
    buf += f"Folio: {so_data.get('order_number', 'N/A')}\n".encode("cp437", errors="replace")
    buf += BOLD_OFF
    buf += f"Estado: {so_data.get('status', '')}\n".encode("cp437", errors="replace")
    buf += f"Fecha: {str(so_data.get('created_at', ''))[:19]}\n".encode("cp437", errors="replace")
    if so_data.get("employee_name"):
        buf += f"Mecanico: {so_data['employee_name']}\n".encode("cp437", errors="replace")
    buf += ("-" * chars + "\n").encode()

    # Customer / vehicle
    if so_data.get("customer_name"):
        buf += BOLD_ON
        buf += f"Cliente: {so_data['customer_name']}\n".encode("cp437", errors="replace")
        buf += BOLD_OFF
    vehicle = " ".join(
        str(v) for v in [
            so_data.get("vehicle_plate", ""),
            so_data.get("vehicle_make", ""),
            so_data.get("vehicle_model", ""),
        ] if v
    ).strip()
    if vehicle:
        buf += f"Vehiculo: {vehicle}\n".encode("cp437", errors="replace")
    if so_data.get("mileage_in"):
        buf += f"Kilometraje: {so_data['mileage_in']}\n".encode("cp437", errors="replace")
    if so_data.get("fuel_level"):
        buf += f"Gasolina: {so_data['fuel_level']}\n".encode("cp437", errors="replace")
    buf += ("-" * chars + "\n").encode()

    # Reception notes
    if so_data.get("reception_notes"):
        buf += BOLD_ON
        buf += "Falla / Observaciones:\n".encode("cp437", errors="replace")
        buf += BOLD_OFF
        for line in str(so_data["reception_notes"]).splitlines():
            buf += (line[:chars] + "\n").encode("cp437", errors="replace")
        buf += ("-" * chars + "\n").encode()

    # Parts
    items = so_data.get("items", [])
    if items:
        buf += BOLD_ON
        buf += "REFACCIONES\n".encode("cp437", errors="replace")
        buf += BOLD_OFF
        for item in items:
            name = item.get("name", "")[:chars - 10]
            part_no = item.get("part_number", "")
            qty = item.get("quantity", 1)
            unit_price = item.get("unit_price", 0)
            line_total = qty * unit_price
            buf += f"{qty}x {name}\n".encode("cp437", errors="replace")
            if part_no:
                buf += f"   #{part_no}\n".encode("cp437", errors="replace")
            buf += ALIGN_RIGHT
            buf += f"${line_total:,.2f}\n".encode("cp437", errors="replace")
            buf += ALIGN_LEFT
        buf += ("-" * chars + "\n").encode()

    # Labor
    labor_items = so_data.get("labor", [])
    if labor_items:
        buf += BOLD_ON
        buf += "MANO DE OBRA\n".encode("cp437", errors="replace")
        buf += BOLD_OFF
        for labor in labor_items:
            desc = labor.get("description", "")[:chars - 10]
            hours = labor.get("hours", 0)
            rate = labor.get("hourly_rate", 0)
            total = labor.get("total_cost", hours * rate)
            buf += f"{desc}\n".encode("cp437", errors="replace")
            buf += f"   {hours} hrs x ${rate:,.2f}\n".encode("cp437", errors="replace")
            buf += ALIGN_RIGHT
            buf += f"${total:,.2f}\n".encode("cp437", errors="replace")
            buf += ALIGN_LEFT
        buf += ("-" * chars + "\n").encode()

    # Totals
    buf += ALIGN_RIGHT
    if items or labor_items:
        total = so_data.get("total") or sum(
            i.get("quantity", 1) * i.get("unit_price", 0) for i in items
        ) + sum(labor.get("total_cost", 0) for labor in labor_items)
        buf += BOLD_ON + DOUBLE_HEIGHT
        buf += _total_line("TOTAL ESTIMADO:", total, chars).encode("cp437", errors="replace")
        buf += NORMAL_SIZE + BOLD_OFF
    if so_data.get("estimated_cost"):
        buf += _total_line("Costo estimado:", so_data["estimated_cost"], chars).encode("cp437", errors="replace")

    # Footer
    buf += b"\n"
    buf += ALIGN_CENTER
    buf += "No es comprobante fiscal\n".encode("cp437", errors="replace")
    buf += "Nexus Autoparts POS\n".encode("cp437", errors="replace")
    buf += b"\n\n\n"
    buf += PARTIAL_CUT

    return bytes(buf)
