#!/usr/bin/env python
# encoding: utf-8
"""mypl_rueckmelde_server - read messages from RabbitMQ and feed them to SoftM
"""

# mypl/bin/mypl_rueckmelde_server.py
# Created by Maximillian Dornseif on 2009-07-13 for HUDORA.
# Copyright (c) 2009 HUDORA. All rights reserved.


from optparse import OptionParser
from signal import signal, SIGTERM, SIGINT
import cs.caching
import cs.messaging as messaging
import cs.zwitscher
import cs.masterdata.reunite
import cs.masterdata
import datetime
import husoftm.connection
import husoftm.kunden
import husoftm.lieferscheine
import simplejson as json

# For exiting the proessing loop
process_messages = True
channel = None
options = None
consumer_tags = []

zwitscher_user = "mypl"

class SetArtikelError(Exception):
    pass


def parse_commandline():
    """Parse the commandline and return information."""
    
    parser = OptionParser(version=True)
    parser.description = __doc__
    
    parser.set_usage('usage: %prog [options]. Try %prog --help for details.')
    parser.add_option('--lager', type="int", help='Lager, das bearbeitet werden soll')
    parser.add_option('--in', dest="in_queue", help="Beispiel --in=erp.ic-wms.rueckmeldung.")
    parser.add_option('-d', '--debug', action='store_true', dest='debug',
                      help='Enables debugging mode')
    parser.add_option('-o', '--output', default='rueckmeldung.log',
                      help='output all Kommischeint to logfile [%default]')
    
    opts, args = parser.parse_args()
    if not opts.lager:
        parser.error("'--lager' nicht gesetzt")
    if not opts.in_queue:
        parser.error("'--in' nicht gesetzt")
    if len(args) > 0: # we expect no non option arguments
        parser.error('incorrect number of arguments')
    return opts, args


def zurueckmelden(auftragsnr, kommiauftragsnr, positionen, typ='nullen'):
    """Rückmeldung zu SoftM."""
    server = husoftm.connection.PyRoMoftSconnection()
    lock_key = datetime.datetime.now().strftime("%m%d%H%M%S")
    rows = server.query('ISR00', fields=['IRKBNR'], condition="IRKBNR=%d"
                        % int(kommiauftragsnr))
    if rows:
        cs.zwitscher.zwitscher("%s: Kommiauftrag schon in ISR00, wird nicht zurueckgemeldet!" % kommiauftragsnr, username=zwitscher_user)
        fd = open(options.output, 'a')
        fd.write("%s: Kommiauftrag schon in ISR00, wird nicht zurueckgemeldet!\n" % kommiauftragsnr)
        fd.close()
        return
    for pos in positionen:
        # auftrags_position = 
        pos_sql = dict(IRFNR='01',
                       IRKBNR=int(kommiauftragsnr), # take from auftrag
                       IRKPOS=int(pos['kommissionierbeleg_position']),
                       IRAUFN=int(auftragsnr),
                       IRAUPO=int(pos['auftrags_position']),
                       IRDFSL=lock_key)
        if typ == 'nullen':
            pos_sql['IRMENG'] = 0
            pos_sql['IRKZNU'] = 1
        # elif commit_type == 'cancel':
        #     pos_sql['IRMENG'] = 0
        #     pos_sql['IRKZST'] = 1
        else:
            pos_sql['IRMENG'] = int(pos['menge'])
        sqlstr = 'INSERT INTO ISR00 (%s) VALUES (%s)' % (','.join(pos_sql.keys()),
                                                    ','.join([repr(x) for x in pos_sql.values()]))
        server.insert_raw(sqlstr)
    # set all records to be unlocked
    server.update_raw(("UPDATE ISR00 SET IRDFSL='' WHERE IRKBNR='%s' AND IRDFSL='%s'" % (
                           int(kommiauftragsnr), lock_key)))
    # Datensatz an SoftM zurückgemeldet


def decodiere_message(msg, spezial=False):
    """Dekodiert die JSON Message und reichert sie mit den Nötigen weiterführenden Daten an."""
    global zwitscher_user

    hugogramm = json.loads(msg.body)

    # trackingnummern übernehmen, für huLOG messages
    if not spezial:
        hugogramm['trackingnr'] = [{'trackingnr': pos['nve']} for pos in hugogramm['positionen']]

    hugogramm['kommiauftragnr'] = hugogramm.get('kommiauftragnr', hugogramm.get('kommiauftragsnr'))
    kommibeleg = husoftm.lieferscheine.Kommibeleg(hugogramm['kommiauftragnr'])

    if kommibeleg.lager == 38:
        zwitscher_user = 'maeuler'
    else:
        zwitscher_user = 'mypl'

    # Der Rückmelde Code aus dem myPL gibt derzeit noch die kommissionierbeleg_position als posnr zurück
    if kommibeleg.lager != 38:
        raise RuntimeError("Erst den myPL Rueckmeldecode anpassen!")

    positionsmapping = {}
    for pos in kommibeleg.positionen:
        positionsmapping[pos.auftrags_position] = pos.kommissionierbeleg_position

    for pos in hugogramm['positionen']:
        if not pos.get('posnr'):
            raise RuntimeError("Kommiauftrag %s enthaelt keine Positionsnummern." % hugogramm['kommiauftragnr'])

    positionen = []
    for posnr, (qty, artnr) in cs.masterdata.reunite.reunite(hugogramm['positionen']).items():
        pos = {}
        pos['posnr'] = posnr
        pos['artnr'] = artnr
        pos['menge'] = qty
        pos['auftrags_position'] = pos['posnr']
        pos['kommissionierbeleg_position'] = positionsmapping[pos['posnr']]
        positionen.append(pos)
    hugogramm['positionen'] = positionen

    if options.debug:
        print hugogramm
    return kommibeleg, hugogramm


def log_message(typ, kommibeleg, hugogramm):
    """Information über den vorgang protokollieren."""
    fd = open(options.output, 'a')
    fd.write("%s %s %s %s\n" % (typ, kommibeleg.auftragsnr, hugogramm['kommiauftragnr'],
                 ','.join(["%d mal %s|%s" % (x['menge'], x['artnr'], x['kommissionierbeleg_position']) for x in hugogramm['positionen']])))
    fd.close()


def lieferscheindruck(kommibeleg, hugogramm=None):
    """Druckauftrag in die Lieferscheindruck Queue schicken."""
    # Lieferscheindruck anstossen
    zielqueue = 'lieferscheindruck#%s' % kommibeleg.lager
    chan = messaging.setup_queue(zielqueue, durable=True)
    doc = messaging.empty_message('myplfrontend.bin.wms2ic_rueckmeldung',
                                  guid='myplfrontend.kommiauftragsrueckmeldung-%s' % kommibeleg.kommissionierbelegnr,
                                  audit_info=u'Lieferscheindruck nach Rückmeldung angefordert')
    doc['kommiauftragnr'] = kommibeleg.kommissionierbelegnr
    doc['generation'] = 1

    # Lager 38 -> per ftp an Mäuler
    if int(kommibeleg.lager) == 38:
        doc['printer'] = "ftp"
        doc['username'] = "11515hudora"
        doc['password'] = "Pa84751987"
        doc['hostname'] = "maeuler-spedition.de"
        doc['directory'] = "In/LIEF/ZBV"
    # sonst auf Lempel drucken /lg100
    else:
        doc['printer'] = "DruckerLempel"
    messaging.publish(doc, zielqueue)


def benachrichtigen(kommibeleg, hugogramm):
    """Den entsprechenden Kundenbetreuer per Zwitschr über das Storno benachrichtigen."""
    betreuer = husoftm.kunden.get_kundenbetreuer(kommibeleg.warenempfaenger)
    cs.zwitscher.zwitscher(u'@%s: %s/%s: %s Kommiauftrag genullt (Kunde %s)' % (betreuer, kommibeleg.auftragsnr,
                                                                                hugogramm['kommiauftragnr'],
                                                                                hugogramm['audit_trail'],
                                                                                kommibeleg.name1),
                           username=zwitscher_user)


def create_hulog_msg(kommibeleg, hugogramm):
    """Erzeugt ein Nachricht an huLOG, um dort eine Sendung anzulegen."""
    fields = ['name1', 'name2', 'strasse', 'plz', 'ort', 'land']
    doc = dict((field, getattr(kommibeleg, field)) for field in fields)
    doc['lieferscheinnr'] = kommibeleg.kommissionierbelegnr
    if kommibeleg.lager == 38:
        doc['source'] = 'Maeuler'
    doc['auftragsnummer'] = kommibeleg.auftragsnr
    doc['spedition'] = hugogramm['spedition']
    doc['positionen'] = hugogramm['trackingnr']
    msg = messaging.empty_message('myplfrontend.bin.wms2ic_rueckmeldung',
                                  guid='myplfrontend.huLOG-%s' % kommibeleg.kommissionierbelegnr,
                                  audit_info=u'Anlegen der huLOG Trackinginfos')
    msg.update(doc)
    messaging.publish(msg, 'hulog.lieferung')


def handle_message_spezial(message):
    """Callback function used by channel.basic_consume for 'nullen'."""
    kommibeleg, hugogramm = decodiere_message(message, spezial=True)
    log_message('nullen', kommibeleg, hugogramm)
    zurueckmelden(kommibeleg.auftragsnr, hugogramm['kommiauftragnr'], hugogramm['positionen'], typ='nullen')
    channel.basic_ack(message.delivery_tag)
    benachrichtigen(kommibeleg, hugogramm)
    lieferscheindruck(kommibeleg)


def check_orderlines(hugogramm, kommibeleg):
    """Prüft, ob eingegangene Rückmeldedaten den Postionen aus dem Kommiauftrag entsprechen.

    SoftM kann nicht mit veränderten Positionsmengen und vertauschten Positionsnummern umgehen.
    """
    def _assert_equal(lhs, rhs, msg=''):
        if msg:
            msg += ": "
        message = "KB%s: %s" % (hugogramm['kommiauftragnr'], msg)
        if lhs != rhs:
            raise RuntimeError("%s%s != %s" % (message, lhs, rhs))

    softmpos = kommibeleg.positionen
    wmspos = hugogramm['positionen']
    _assert_equal(len(softmpos), len(wmspos), "Anzahl der Positionen")
    wmspos = dict((pos['auftrags_position'], pos) for pos in wmspos)
    for pos in softmpos:
        rel_wmspos = wmspos[pos.auftrags_position]
        _assert_equal(rel_wmspos['kommissionierbeleg_position'], pos.kommissionierbeleg_position, "Kommissionierbelegpositionsnummer")
        _assert_equal(rel_wmspos['artnr'], pos.artnr, "Positions-Artikelnr")
        _assert_equal(rel_wmspos['menge'], pos.menge_komissionierbeleg, "Positions-Menge")


def handle_message_normal(message):
    """Callback function used by channel.basic_consume for 'zurueckmelden'."""
    try:
        kommibeleg, hugogramm = decodiere_message(message)
    except RuntimeError, e:
        cs.zwitscher.zwitscher(str(e), username=zwitscher_user)
        channel.basic_ack(message.delivery_tag)
        return

    # Spezialfall: Alle Mengen = 0, dann Kommiauftrag stornieren
    if not any(pos['menge'] for pos in hugogramm['positionen']):
        return handle_message_spezial(message)

    # Prüfen der zurückzumeldenden Positionen (artnr, posnr, menge, ...)
    try:
        check_orderlines(hugogramm, kommibeleg)
    except RuntimeError, e:
        cs.zwitscher.zwitscher(str(e), username=zwitscher_user)
        channel.basic_ack(message.delivery_tag)
        return

    # eigentliches Rückmelden
    log_message('zurueckmelden', kommibeleg, hugogramm)
    zurueckmelden(kommibeleg.auftragsnr, hugogramm['kommiauftragnr'], hugogramm['positionen'], typ='zurueckmelden')
    channel.basic_ack(message.delivery_tag)
    lieferscheindruck(kommibeleg)
    if kommibeleg.lager == 38:
        create_hulog_msg(kommibeleg, hugogramm)


# This might go somewhere like a signal handler
def cancel_processing():
    """ Stop consuming messages from RabbitMQ """
    global process_messages
    
    # Do this so we exit our main loop
    print "Exiting"
    process_messages = False
    # Tell the channel you dont want to consume anymore
    for consumer_tag in consumer_tags:
        channel.basic_cancel(consumer_tag)


def main(opts, args):
    """Setum message consuming."""
    global channel, consumer_tags, options
    options = opts

    inqueuename = "%s#%s" % (options.in_queue, options.lager)
    qname = "%s#%s" % (inqueuename, 'spezial')
    print('listening to %s ...' % qname)
    channel = messaging.setup_queue(qname, channel=channel, durable=True)
    consumer_tags.append(channel.basic_consume(queue=qname, callback=handle_message_spezial))

    qname = "%s#%s" % (inqueuename, 'normal')
    print('listening to %s ...' % qname)
    channel = messaging.setup_queue(qname, channel=channel, durable=True)
    consumer_tags.append(channel.basic_consume(queue=qname, callback=handle_message_normal))

    signal(SIGTERM, lambda signum, stack_frame: cancel_processing())
    signal(SIGINT, lambda signum, stack_frame: cancel_processing())

    if options.debug:
        print "waiting for messages ..."

    # Loop while process_messages is True
    while process_messages:
        # Wait for a message
        channel.wait()

    channel.close()
    channel.connection.close()


if __name__ == '__main__':
    main(*parse_commandline())


def tst():
    def zwitsch(*args, **kwargs):
        print args, kwargs
    cs.zwitscher.zwitscher = zwitsch
    class Dummy(object): pass
    msg = Dummy()
    msg.body = open('rueck.json').read()
    options = Dummy()
    options.debug = False
    kommibeleg, hugogramm = decodiere_message(msg)
    if not any(pos['menge'] for pos in hugogramm['positionen']):
        print "nullen"
        sys.exit()
    check_orderlines(hugogramm, kommibeleg)
