# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.

import os
import logging
from datetime import datetime
from decimal import Decimal

from .printing.protocols import FileSSH

try:
    from escpos import printer
except:
    logging.warning("Escpos module not found!")

pyudev = None
try:
    import pyudev
except:
    logging.warning("Pyudev module not found!")

try:
    import cups
except:
    logging.warning("Cups module not found!")

__all__ = ['Receipt']

_ROW_CHARACTERS = 48

_DIGITS = 9
_PRINT_TAX_ID = False
_DIGITS_CODE_RECEIPT = 4

# ------------------- Type Font Escpos -----------------
_FONT_A = 'a'  # Normal Font
_FONT_B = 'b'  # Condensed Font
# ------------------------------------------------------

if os.name == 'posix':
    homex = 'HOME'
    dirconfig = '.tryton/temp'
elif os.name == 'nt':
    homex = 'USERPROFILE'
    dirconfig = 'AppData/Local/tryton/temp'

HOME_DIR = os.getenv(homex)
directory = os.path.join(HOME_DIR, dirconfig)
ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
print(ROOT_DIR)
image_test_file = os.path.join(ROOT_DIR, 'image_test.jpeg')
print(image_test_file)

if not os.path.exists(directory):
    os.makedirs(directory)

TEMP_INVOICE_FILE = os.path.join(directory, 'invoice.txt')
SSH_PORT = 23


def money(value):
    if type(value) != int:
        value = int(value)
    return '{:,}'.format(value)

dev_printers = {}
if pyudev:
    context = pyudev.Context()
#     for device in context.list_devices():
#         if device.subsystem == 'usbmisc':
#            print(device.subsystem, device.sys_path.split('2-1/')[1][0:5], device.device_node)
#            dev_printers[str(device.sys_path.split('2-1/')[1][0:5])] = device.device_node


class Receipt(object):
    __name__ = 'frontend_pos.ticket'

    def __init__(self, context, row_characters=None, logo=None, environment='retail'):
        self.logger = logging.getLogger('reporting')
        self._company = context.get('company')
        self._sale_device = context.get('sale_device')
        self._shop = context.get('shop')
        self._street = context.get('street')
        self._city = context.get('city')
        self._phone = context.get('phone')
        self._id_number = context.get('id_number')
        self._regime_tax = context.get('regime_tax')
        self._gta_info = context.get('gta_info')
        self._authorizations = context.get('authorizations')
        self._user = context.get('user')
        self._footer = context.get('footer')
        self._header = context.get('header')
        self._printing_taxes = context.get('printing_taxes')
        self._print_invoice_payment = context.get('print_invoice_payment')
        self._delta_locale = context.get('delta_locale')
        self._environment = environment

        self._row_characters = _ROW_CHARACTERS
        if context.get('row_characters'):
            self._row_characters = int(context.get('row_characters'))

        self.taxes_col_width = int(self._row_characters / 3)
        self.payments_col_width = int(self._row_characters / 3)

        order_col_width = int(self._row_characters / 3)
        self.order_col_1 = order_col_width - 7
        self.order_col_2 = order_col_width + 12
        self.order_col_3 = order_col_width - 5

        self._show_position = context.get('show_position')
        self._show_discount = context.get('show_discount')
        self._img_logo = logo if logo else None
        # if logo:
        #     self._img_logo = StringIO(logo)

    def printer_found(self):
        return self._printer

    def test_printer(self):
        if self._interface == 'usb':
            if os.name == 'posix':
                self._printer = printer.File(self._device, profile=self._profile)
            elif os.name == 'nt':
                self._printer = printer.Win32Raw(self._device)
                self._printer.open()
        elif self._interface == 'network':
            self._printer = printer.Network(self._device)
        elif self._interface == 'ssh':
            self._printer = FileSSH(*self._device.split('@'))
            self._printer.open()
        if not self._printer:
            return
        self.print_enter()
        self._printer.image(image_test_file, center=True)
        self.print_enter()
        self.print_header()
        self.print_enter()
        self.print_enter()
        self.print_enter()
        self._printer.cut()
        self._printer.cashdraw(2)
        self._printer.beep()
        self._printer.beep()
        self._printer.beep()
        self._printer.beep()
        self._printer.beep()
        self._printer.beep()

    def set_printer(self, printer):
        if dev_printers.get(printer['device']):
            device = dev_printers[printer['device']]
        else:
            device = printer['device']

        self._interface = printer['interface']
        self._device = device
        self._profile = printer['profile']

    def print_sale(self, sale, type_doc=None, open_box=False):
        try:
            if self._interface == 'usb':
                if os.name == 'posix':
                    self._printer = printer.File(self._device, profile=self._profile)
                elif os.name == 'nt':
                    self._printer = printer.Win32Raw(self._device)
                    self._printer.open()
            elif self._interface == 'network':
                self._printer = printer.Network(self._device)
            elif self._interface == 'ssh':
                self._printer = FileSSH(*self._device.split('@'))
                self._printer.open()
            elif self._interface == 'cups':
                self.conn = cups.Connection()
                self._file = open(TEMP_INVOICE_FILE, 'w')
                self._printer = CupsPrinter(self._file, self._row_characters)
            if not self._printer:
                self.logger.info("Warning: Can not found Printer!")
                return
            self.logger.info("Info: Printer is OK!")
            self._print_sale(sale, type_doc, open_box)
        except:
            self.logger.info("Warning: Printer error or device not found!")

    def _print_sale(self, sale, type_doc=None, open_box=False):
        self.print_header()
        self.print_body(sale, type_doc)
        if sale.get('cufe'):
            self._printer.text('CUFE: ' + sale['cufe'])
        if sale.get('qr_code'):
            self.print_qrcode(sale['qr_code'])
        self.print_footer(open_box)
        # self.print_extra_info(sale)
        if self._interface == 'cups':
            self._file.close()
            self.conn.printFile(self._printer_name, TEMP_INVOICE_FILE,
                'POS Invoice', {})
        else:
            self._printer.close()

    def print_logo(self):
        # self._printer.set(align='center')
        # self._printer.image('image_test.jpeg', center=True)
        self._printer.image(self._img_logo, center=True)
        self.print_enter()

    def print_qrcode(self, qrcode):
        # self._printer.set(align='center')
        self._printer.qr(qrcode, center=True, size=5)
        self.print_enter()

    def print_header(self):
        if self._img_logo:
            self.print_logo()
        self._printer.set(align='center')
        if self._header != '' and self._header is not None:
            self._printer.text(self._header)
            self.print_enter()
        self._printer.text(self._company)
        self.print_enter()
        self._printer.text(self._shop)
        self.print_enter()
        if self._id_number:
            self._printer.text('NIT:' + self._id_number)
        if self._regime_tax:
            self._printer.text(' ' + self._regime_tax)
        self.print_enter()
        if self._street:
            self._printer.text(self._street)
            self.print_enter()
        if self._city:
            self._printer.text(self._city)
        if self._phone:
            if self._city:
                self._printer.text(' ')
            self._printer.text('Telefono:' + self._phone)
        if self._city or self._phone:
            self.print_enter()
        self.print_enter()
        self.print_enter()

    def print_horinzontal_line(self, size=None):
        if size == 'big':
            row_characters = '-' * int(self._row_characters / 2)
        else:
            row_characters = '-' * self._row_characters
        self._printer.text(row_characters)

    def print_horinzontal_double_line(self):
        self._printer.text('=' * self._row_characters)

    def print_enter(self):
        self._printer.text('\n')

    def print_split(self, left, right):
        len_left = self._row_characters - len(right) - 1
        left = left[:len_left]
        if type(left) == bytes:
            left = left.decode("utf-8")
        if type(right) == bytes:
            right = right.decode("utf-8")
        left += (len_left - len(left) + 1) * ' '
        self._printer.text(left)
        self._printer.text(right + '\n')

    def print_body(self, sale, type_doc='invoice'):
        self._printer.set(font=_FONT_B)
        kind_string = sale.get('kind_string', None)
        if type_doc != 'invoice' and kind_string:
            self._printer.set(custom_size=True, width=2, height=2, align='center')
            kind = f'- - - {kind_string} - - -'
            self._printer.text(kind)
            self.print_enter()
            self.print_enter()
            self._printer.set(custom_size=False)
        self._printer.set(align='left')
        if type_doc == 'invoice':
            if sale['number']:
                if sale['total_amount'] >= 0:
                    self._printer.text('FACTURA DE VENTA No. ' + sale['number'])
                else:
                    self._printer.text('NOTA CREDITO No. ' + sale['number'])
        elif type_doc in ['order', 'delivery']:
            self._printer.text('PEDIDO: ' + sale['order'])

        self.print_enter()
        self._printer.text('Fecha:%s' % sale['date'])
        if sale.get('invoice_time'):
            self._printer.text(' Hora:%s' % sale['invoice_time'])
        if sale.get('turn') and sale['turn'] != 0:
            self._printer.text('Turno: %s - ' % str(sale['turn']))
        self.print_enter()
        self.print_horinzontal_line()
        party_name = 'Cliente: %s ' % sale['party']
        party_id_number = 'Id: %s' % sale.get('party_id_number', '')
        if len(party_name + party_id_number) > self._row_characters:
            self._printer.text(party_name)
            self.print_enter()
            self._printer.text(party_id_number)
        else:
            self._printer.text(party_name + party_id_number)
        if sale.get('party_address'):
            self.print_enter()
            self._printer.text('Direccion: %s' % sale['party_address'])
        if sale.get('party_phone'):
            self.print_enter()
            self._printer.text('Telefono: %s' % sale['party_phone'])
        if sale.get('payment_term'):
            self.print_enter()
            self._printer.text('Forma de Pago: %s' % sale['payment_term'])

        self.print_enter()
        consumer_name = sale.get('consumer_name', None)
        if consumer_name:
            payment_method = sale.get('payment_method', None)
            consumer_address = sale.get('consumer_address', '')
            consumer_phone = sale.get('consumer_phone', '')
            consumer_notes = sale.get('consumer_notes', '')
            self.print_horinzontal_line()
            self.print_enter()
            self._printer.set(align='center')
            self._printer.text('========== INFO CONSUMIDOR ==========')
            self.print_enter()
            self._printer.set(align='left')
            self._printer.text('NOMBRE: %s' % consumer_name)
            self.print_enter()
            self._printer.text('DIRECCION: %s' % consumer_address)
            self.print_enter()
            self._printer.text('TELEFONO: %s' % consumer_phone)
            self.print_enter()
            self._printer.text('NOTAS: %s' % consumer_notes)
            self.print_enter()
            if payment_method:
                if payment_method == 'cash':
                    payment_method = '--- PAGA EN EFECTIVO'
                else:
                    payment_method = '--- PAGA CON DATAFONO'
                self._printer.text('MEDIO DE PAGO: %s' % payment_method)
                self.print_enter()

        self.print_enter()
        self.print_horinzontal_line()
        self.print_split(' Articulo ', 'Subtotal ')
        self.print_horinzontal_line()

        len_row = self._row_characters - (_DIGITS_CODE_RECEIPT + 1) - (_DIGITS + 1)
        for line in sale['lines']:
            if line['taxes'] and _PRINT_TAX_ID:
                tax_id = ' ' + str(line['taxes'][0].id)
            else:
                tax_id = ''
            line_total = money(line['amount_w_tax']) + tax_id

            code = line['code'] or ' '
            if line['quantity'] != 1:
                length_name = self._row_characters - 11
                first_line = code + ' ' + line['name'][:length_name]

                if type(first_line) == bytes:
                    first_line = first_line.decode('utf-8')

                self._printer.text(first_line + '\n')

                unit_price_w_tax = str(round(line['unit_price_w_tax'], 2))

                second_line = '  %s x %s' % (line['quantity'], unit_price_w_tax)
                second_line = second_line.encode('utf-8')
                self.print_split(second_line, line_total)
            else:
                if self._environment == 'retail':
                    line_pt = code + ' ' + line['name'][:len_row]
                else:
                    line_pt = line['name'][:len_row]

                self.print_split(line_pt, line_total)

        untaxed_amount = sale['untaxed_amount']
        total_amount = sale['total_amount']
        total_string = 'Total:'
        net_amount = total_amount

        delivery_amount = sale.get('delivery_amount', 0)
        if delivery_amount and delivery_amount > 0:
            net_amount = net_amount + delivery_amount

        tip_amount = sale.get('tip_amount', 0)
        if tip_amount and tip_amount > 0:
            net_amount = net_amount + tip_amount

        self.print_split('', '----------------')
        self.print_split('Subtotal Base:', money(untaxed_amount))
        self.print_split('Impuesto:', money(sale['tax_amount']))
        self.print_split('', '----------------')
        self.print_split(total_string, money(total_amount))
        self.print_enter()

        if tip_amount:
            self.print_split('Propina:', money(tip_amount))
        if delivery_amount:
            self.print_split('Domicilio:', money(delivery_amount))

        if tip_amount or delivery_amount:
            self.print_split('Valor Neto:', money(net_amount))

        if self._show_discount:
            self.print_split('Descuento:', money(sale['discount']))
        self.print_enter()
        if sale['cash_received']:
            self.print_split('Recibido:', money(sale['cash_received']))
        else:
            #self.print_split('Recibido:', money(sale['paid_amount']))
            #residual_amount = net_amount - sale['paid_amount']
            #self.print_split('Saldo Pendiente:', money(residual_amount))
            pass
        self.print_split('Cambio:', money(sale['change']))
        self.print_horinzontal_line()
        self.print_enter()
        if self._printing_taxes:
            self.print_col('Tipo', self.taxes_col_width)
            self.print_col('Base', self.taxes_col_width)
            self.print_col('Imp.', self.taxes_col_width)
            taxes = sale['taxes']
            for tax in taxes:
                self.print_col(str(taxes[tax]['name']) + ' ', self.taxes_col_width)
                self.print_col(str(int(taxes[tax]['base'])), self.taxes_col_width)
                self.print_col(str(int(taxes[tax]['tax'])), self.taxes_col_width)
                self.print_enter()

        self.print_horinzontal_line()
        self.print_enter()
        if sale.get('payments'):
            self.print_enter()
            self.print_col('MEDIO DE PAGO', self.payments_col_width + 2)
            self.print_col('VOUCHER', self.payments_col_width)
            self.print_col('VALOR', self.payments_col_width)
            payments = sale['payments']
            for p in payments:
                self.print_col(str(p['name']) + ' ', self.payments_col_width)
                self.print_col(str(p['voucher']) + ' ', self.payments_col_width)
                self.print_col(str(int(p['amount'])), self.payments_col_width)

            self.print_enter()
            self.print_horinzontal_line()
        no_products = 'No ITEMS: %s' % str(sale['num_products'])
        self._printer.text(no_products)
        self.print_enter()

        auth_kind = sale.get('auth_kind', None)
        if auth_kind and sale['state'] not in ['draft']:
            auth_data = self._authorizations[auth_kind]
            self._printer.text(self.get_authorization(auth_data))
            self.print_enter()
        elif self._gta_info and sale['state'] not in ['draft']:
            self._printer.text(self._gta_info)
            self.print_enter()

        if type_doc == 'invoice':
            self._printer.text('No PEDIDO: ' + sale['order'])
            self.print_enter()

        register = 'CAJA No. %s' % self._sale_device
        self._printer.text(register)
        self.print_enter()

        self._printer.text('CAJERO: %s' % self._user)
        self.print_enter()
        if sale.get('salesman'):
            self._printer.text('VENDEDOR: %s' % sale['salesman'])
            self.print_enter()

        if sale.get('comment'):
            self._printer.text('NOTAS: %s' % sale['comment'])
            self.print_enter()

        if self._show_position:
            self._printer.text('POSICION: %s' % str(sale['position']))
            self.print_enter()

        self.print_horinzontal_line()
        if sale.get('delivery_party'):
            self._printer.text('DOMICILIARIO: %s' % sale['delivery_party'])
            self.print_enter()
        if sale.get('channel'):
            self._printer.text('CANAL DE VENTA: %s' % sale['channel'])
            self.print_enter()

        self._printer.text(f'Fecha de creacion: %s' % sale["create_date"])

        self.print_enter()
        self.print_enter()

    def print_extra_info(self, sale):
        if sale.get('pos_notes'):
            self.print_enter()
            self.print_header()
            self.print_horinzontal_line()
            self.print_enter()
            party_name = 'Cliente: %s ' % sale['party']
            self._printer.text(party_name)
            self.print_enter()
            if self._show_position:
                self._printer.text('Posicion: %s' % str(sale['position']))
                self.print_enter()

            if sale['state'] in ['draft']:
                self._printer.text('Cotizacion: ', sale['order'])
            else:
                self._printer.text('Factura No. ' + sale['number'])
            self.print_enter()
            self._printer.text(str(sale.get('pos_notes')))
            self.print_enter()
            self.print_horinzontal_line()
            self.print_enter()
            self._printer.cut()

    def print_col(self, x, l):
        self._printer.text(x[:l] + (l - len(x)) * ' ')

    def print_footer(self, open_box=False):
        if self._footer:
            self._printer.text(self._footer)
        self.print_enter()
        self._printer.text('SOFTWARE TRYTON - www.presik.com')
        self.print_enter()
        self._printer.cut()
        if open_box:
            self._printer.cashdraw(2)
        self.print_enter()

    def print_orders(self, orders, reversion=None, kind='command'):
        res = []
        self.order_kind = kind
        for order in orders.values():
            try:
                if dev_printers.get(order['host']):
                    host = dev_printers[order['host']]
                else:
                    host = order['host']
                if order['interface'] == 'usb':
                    self._printer = printer.File(host)
                elif order['interface'] == 'network':
                    self._printer = printer.Network(host)
                elif order['interface'] == 'ssh':
                    self._printer = FileSSH(*host.split('@'))
                    if self._printer:
                        self._printer.open()
                elif order['interface'] == 'cups':
                    pass
                if not self._printer:
                    self.logger.info("Warning: Interface not found for printer!")
                    res.append(None)
                    continue

                self.logger.info("Info: Printer is OK!")
                res.append(self._print_order(order, reversion))
                self._printer.close()
            except:
                self.logger.info("Warning: Can not found Printer!")
                res.append(None)
        return all(res)

    def _print_order(self, order, reversion):
        self.print_body_order(order, reversion)
        self._printer.cut()
        self._row_characters = order['row_characters']
        return True

    def print_body_order(self, order, reversion):
        # Exists 2 types of order:
        # command => For restaurants kitchens
        # dispatch => For external dispatch
        self._printer.set(font=_FONT_B)
        self._printer.set(align='center')
        turn = order.get('turn')
        if turn:
            self._printer.text('TURNO: %s' % str(turn))
        self.print_enter()
        self.print_enter()

        kind = order.get('kind', 'delivery')
        if kind == 'take_away':
            _kind = 'PARA LLEVAR'
        elif kind == 'to_table':
            _kind = 'PARA MESA'
        else:
            _kind = 'DOMICILIO'

        self._printer.set(custom_size=True, width=2, height=2, align='center')
        title = f'+ + + {_kind} + + +'
        self._printer.text(title)
        self.print_enter()

        self._printer.set(custom_size=True, width=2, height=2, align='center')
        self.print_enter()
        self._printer.text(order['sale_number'])
        self.print_enter()
        self._printer.set(custom_size=False)

        self._printer.set(align='left')
        self.print_enter()
        date_ = datetime.now().strftime("%Y-%m-%d %H:%M %p")
        self._printer.text('FECHA: ' + date_)
        self.print_enter()

        table_assigned = order.get('table_assigned', None)
        if table_assigned:
            self._printer.text('MESA: %s' % table_assigned)
            self.print_enter()
        delivery_charge = order.get('delivery_charge', None)

        if self.order_kind == 'dispatch' and delivery_charge:
            self._printer.text('FACTURA: ' + order['number'])
            self.print_enter()
            _charge_to = 'Cliente'
            if delivery_charge == 'company':
                _charge_to = 'Empresa'
            self._printer.text('CARGO DEL DOMICILIO: ' + _charge_to)
            self.print_enter()

        position = order.get('position')
        if position:
            self._printer.text('POSICION: %s' % str(position))
        self.print_enter()
        self._printer.text('VENDEDOR: %s' % order['salesman'])
        self.print_enter()
        self._printer.text('AMBIENTE: %s' % order['shop'])
        self.print_enter()
        self._printer.text('CLIENTE: %s' % order['party'])
        self.print_enter()

        if self.order_kind == 'dispatch':
            self._printer.text('VALOR: ' + str(order['total_amount']))
            self.print_enter()
        if order.get('pos_notes'):
            self._printer.text(order['pos_notes'])
            self.print_enter()
        self._printer.text('CAJA No: %s' % self._sale_device or '')
        self.print_enter()
        self.print_enter()
        self._printer.set(align='center')

        if self.order_kind == 'command':
            self._printer.set(custom_size=True, width=2, height=2, align='center')
            if reversion:
                self._printer.text('>>> R E V E R S I O N <<<')
                self.print_enter()

        col_width_name = self.order_col_2 + self.order_col_3
        if self.order_kind == 'command':
            self.print_horinzontal_line(size='big')

            self.print_enter()
            self._printer.set(custom_size=True, width=1, height=2)
            for line in order['lines']:
                qty = '  ' + str(int(Decimal(line['quantity'])))
                self._printer.set(custom_size=True, width=1, height=2)
                self.print_col(qty, self.order_col_1)
                self.print_col(line['name'], col_width_name)
                # self._printer.set(custom_size=False)
                # self.print_col(' ' + str(line['unit_price']), self.order_col_3)
                if line['note']:
                    self._printer.set(custom_size=True, width=1, height=2, align='right')
                    self.print_enter()
                    self._printer.text('--->> NOTA: ' + line['note'])
                    self.print_enter()
                self.print_enter()

        self.print_enter()
        self.print_horinzontal_double_line()

        self.print_enter()
        self._printer.text('NOTA:')
        self.print_enter()
        if order['comment']:
            self._printer.text(str(order['comment']))
            self.print_enter()
            self.print_horinzontal_line()
        self.print_enter()
        self.print_enter()

    @classmethod
    def get_authorization(cls, auth):
        res = ''
        kind = auth['kind']
        start_date_auth = auth['start_date_auth']
        from_auth = auth['from_auth']
        to_auth = auth['to_auth']
        number = auth['number']
        if auth:
            res = f"Autorizacion de facturacion {kind} No {number} del {start_date_auth}, habilita desde {from_auth} a {to_auth}"
        return res


class CupsPrinter(object):
    "Cups Printer"
    __name__ = 'sale_pos_frontend.cups_printer'

    def __init__(self, _file, row_characters):
        self._file = _file
        self.align = 'left'
        self._row_characters = row_characters

    def text(self, text):
        self._text(text)

    def set(self, align='left', font=_FONT_A):
        if align:
            self.align = align
        if font:
            self.font = font

    def cut(self):
        pass

    def cashdraw(number):
        pass

    def _text(self, text):
        start_spaces = ''
        if self.align == 'center':
            start_spaces = int((self._row_characters - len(text)) / 2) * ' '
        elif self.align == 'right':
            start_spaces = int(self._row_characters - len(text)) * ' '
        else:
            pass
        text = start_spaces + text
        self._file.write(text)


if __name__ == '__main__':

    # Test for Escpos interface printer Linux

    # Network example
    # device = 'network', '192.168.0.33'

    # Unix-like Usb example
    device = ('usb',' /dev/usb/lp1')

    # Windows Usb example for printer name SATPOS
    # device = 'usb', 'SATPOS'

    # SSH example
    # device = 'ssh', 'psk@123@192.168.20.25@22@/dev/usb/lp1'

    example_dev = {
        'interface': device[0],
        'device': device[1],
        'profile': 'TM-P80',
    }

    ctx_printing = {}
    ctx_printing['company'] = 'OSCORP INC'
    ctx_printing['sale_device'] = 'CAJA-10'
    ctx_printing['shop'] = 'Shop Wall Boulevard'
    ctx_printing['street'] = 'Cll 21 # 172-81. Central Park'
    ctx_printing['user'] = 'Charles Chapplin'
    ctx_printing['city'] = 'Dallas'
    ctx_printing['zip'] = '0876'
    ctx_printing['phone'] = '591 5513 455'
    ctx_printing['id_number'] = '123456789-0'
    ctx_printing['tax_regime'] = 'none'

    receipt = Receipt(ctx_printing)
    receipt.set_printer(example_dev)
    receipt.test_printer()
