import oracledb

import os
import sys
import json
import csv
import re
import subprocess
import argparse
from datetime import datetime
from jit_oracle_connection import JitOracleConnection

args = None
oracle_connection: JitOracleConnection = None

def striprtf(text):
    pattern = re.compile(r"\\([a-z]{1,32})(-?\d{1,10})?[ ]?|\\'([0-9a-f]{2})|\\([^a-z])|([{}])|[\r\n]+|(.)", re.I)
    destinations = frozenset((
        'aftncn','aftnsep','aftnsepc','annotation','atnauthor','atndate','atnicn','atnid',
        'atnparent','atnref','atntime','atrfend','atrfstart','author','background',
        'bkmkend','bkmkstart','blipuid','buptim','category','colorschememapping',
        'colortbl','comment','company','creatim','datafield','datastore','defchp','defpap',
        'do','doccomm','docvar','dptxbxtext','ebcend','ebcstart','factoidname','falt',
        'fchars','ffdeftext','ffentrymcr','ffexitmcr','ffformat','ffhelptext','ffl',
        'ffname','ffstattext','field','file','filetbl','fldinst','fldrslt','fldtype',
        'fname','fontemb','fontfile','fonttbl','footer','footerf','footerl','footerr',
        'footnote','formfield','ftncn','ftnsep','ftnsepc','g','generator','gridtbl',
        'header','headerf','headerl','headerr','hl','hlfr','hlinkbase','hlloc','hlsrc',
        'hsv','htmltag','info','keycode','keywords','latentstyles','lchars','levelnumbers',
        'leveltext','lfolevel','linkval','list','listlevel','listname','listoverride',
        'listoverridetable','listpicture','liststylename','listtable','listtext',
        'lsdlockedexcept','macc','maccPr','mailmerge','maln','malnScr','manager','margPr',
        'mbar','mbarPr','mbaseJc','mbegChr','mborderBox','mborderBoxPr','mbox','mboxPr',
        'mchr','mcount','mctrlPr','md','mdeg','mdegHide','mden','mdiff','mdPr','me',
        'mendChr','meqArr','meqArrPr','mf','mfName','mfPr','mfunc','mfuncPr','mgroupChr',
        'mgroupChrPr','mgrow','mhideBot','mhideLeft','mhideRight','mhideTop','mhtmltag',
        'mlim','mlimloc','mlimlow','mlimlowPr','mlimupp','mlimuppPr','mm','mmaddfieldname',
        'mmath','mmathPict','mmathPr','mmaxdist','mmc','mmcJc','mmconnectstr',
        'mmconnectstrdata','mmcPr','mmcs','mmdatasource','mmheadersource','mmmailsubject',
        'mmodso','mmodsofilter','mmodsofldmpdata','mmodsomappedname','mmodsoname',
        'mmodsorecipdata','mmodsosort','mmodsosrc','mmodsotable','mmodsoudl',
        'mmodsoudldata','mmodsouniquetag','mmPr','mmquery','mmr','mnary','mnaryPr',
        'mnoBreak','mnum','mobjDist','moMath','moMathPara','moMathParaPr','mopEmu',
        'mphant','mphantPr','mplcHide','mpos','mr','mrad','mradPr','mrPr','msepChr',
        'mshow','mshp','msPre','msPrePr','msSub','msSubPr','msSubSup','msSubSupPr','msSup',
        'msSupPr','mstrikeBLTR','mstrikeH','mstrikeTLBR','mstrikeV','msub','msubHide',
        'msup','msupHide','mtransp','mtype','mvertJc','mvfmf','mvfml','mvtof','mvtol',
        'mzeroAsc','mzeroDesc','mzeroWid','nesttableprops','nextfile','nonesttables',
        'objalias','objclass','objdata','object','objname','objsect','objtime','oldcprops',
        'oldpprops','oldsprops','oldtprops','oleclsid','operator','panose','password',
        'passwordhash','pgp','pgptbl','picprop','pict','pn','pnseclvl','pntext','pntxta',
        'pntxtb','printim','private','propname','protend','protstart','protusertbl','pxe',
        'result','revtbl','revtim','rsidtbl','rxe','shp','shpgrp','shpinst',
        'shppict','shprslt','shptxt','sn','sp','staticval','stylesheet','subject','sv',
        'svb','tc','template','themedata','title','txe','ud','upr','userprops',
        'wgrffmtfilter','windowcaption','writereservation','writereservhash','xe','xform',
        'xmlattrname','xmlattrvalue','xmlclose','xmlname','xmlnstbl',
        'xmlopen',
    ))
    # Translation of some special characters.
    specialchars = {
        'par': '\n',
        'sect': '\n\n',
        'page': '\n\n',
        'line': '\n',
        'tab': '\t',
        'emdash': '\u2014',
        'endash': '\u2013',
        'emspace': '\u2003',
        'enspace': '\u2002',
        'qmspace': '\u2005',
        'bullet': '\u2022',
        'lquote': '\u2018',
        'rquote': '\u2019',
        'ldblquote': '\201C',
        'rdblquote': '\u201D',
    }
    stack = []
    ignorable = False       # Whether this group (and all inside it) are "ignorable".
    ucskip = 1              # Number of ASCII characters to skip after a unicode character.
    curskip = 0             # Number of ASCII characters left to skip
    out = []                # Output buffer.
    for match in pattern.finditer(text):
        word,arg,hex,char,brace,tchar = match.groups()
        if brace:
            curskip = 0
            if brace == '{':
                # Push state
                stack.append((ucskip,ignorable))
            elif brace == '}':
                # Pop state
                ucskip,ignorable = stack.pop()
        elif char: # \x (not a letter)
            curskip = 0
            if char == '~':
                if not ignorable:
                    out.append('\xA0')
            elif char in '{}\\':
                if not ignorable:
                    out.append(char)
            elif char == '*':
                ignorable = True
        elif word: # \foo
            curskip = 0
            if word in destinations:
                ignorable = True
            elif ignorable:
                pass
            elif word in specialchars:
                out.append(specialchars[word])
            elif word == 'uc':
                ucskip = int(arg)
            elif word == 'u':
                c = int(arg)
                if c < 0: c += 0x10000
                if c > 127: out.append(chr(c)) #NOQA
                else: out.append(chr(c))
                curskip = ucskip
        elif hex: # \'xx
            if curskip > 0:
                curskip -= 1
            elif not ignorable:
                c = int(hex,16)
                if c > 127: out.append(chr(c)) #NOQA
                else: out.append(chr(c))
        elif tchar:
            if curskip > 0:
                curskip -= 1
            elif not ignorable:
                out.append(tchar)
    return ''.join(out)

def log_print(text):
    global args
    if args.para_silent:
        print(text)

def log_input(text):
    global args
    if args.para_silent:
        print(text)

def escaped_csv_text(text):
    if not text:
        return ''
    return str(text).replace(';', '\\;')

def artikel_daten_schreiben(uebteilenr: str, json_data: {}, csv_rows: [{}], level: int):
    global oracle_connection

    sql = f"""select lpad(c.posnr || '/' || c.varianten, length(c.posnr || '/' || c.varianten) + :ebene, '.') position,
                     c.kompteilenr zeichnungsnummer,
                     p.bez bezeichnung,
                     (select sivas_aj.jit_tools.nclob_to_varchar2(g.text_nclob)
                        from grnr_text g
                       where g.grnr_teilenr = c.kompteilenr
                         and g.art = 'T'
                         and g.lfd_nr = 1
                         and g.sprache = 'D') konstruktionstext,
                     decode(c.abw_anzahl, null, null, c.abw_anzahl || ' Stck á ' || c.laenge || ' mm') stueli_text,
                     c.werkstoff,
                     c.menge,
                     c.werknorm,
                     bez.name1_lieferant(artikel_lieferant.ermittle_hauptlieferant(c.kompteilenr)) hauptlieferant,
                     (select min(uebteilenr) from comp where uebteilenr = c.kompteilenr) baugruppe,
                     decode(nvl(c.dispo_aktiv_jn, 'N'), 'J', 'True', 'False') dispo_aktiv_jn,
                     decode(c.ruestcode, null, null, c.ruestcode || ' - ' || bez.bez_ruestcode(c.ruestcode)) ruestcode,
                     decode(nvl(c.kz_lieferanten_beistellung, 'N'), 'J', 'True', 'False') kz_lieferanten_beistellung
                from comp c,
                     parts p
               where p.teilenr = c.kompteilenr and uebteilenr = :uebteilenr
               order by c.posnr, c.varianten"""

    cursor = oracle_connection.cursor()
    cursor.execute(sql, [level, uebteilenr])
    columns = [col[0] for col in cursor.description]
    cursor.rowfactory = lambda *args: dict(zip(columns, args))
    pos_data = cursor.fetchall()
    if not pos_data:
        log_print(f"Keine Datensätze für diese Parameter gefunden")
        return

    for pos in pos_data:
        artikel_data = {}
        artikel_data['Position'] = pos['POSITION']
        artikel_data['Zeichnungsnummer'] = pos['ZEICHNUNGSNUMMER']
        artikel_data['Parent'] = uebteilenr
        artikel_data['Bezeichnung'] = pos['BEZEICHNUNG']
        if not pos['KONSTRUKTIONSTEXT']:
            artikel_data['Konstruktionstext'] = str()
        else:
            artikel_data['Konstruktionstext'] = striprtf(str(pos['KONSTRUKTIONSTEXT']))
        artikel_data['Stüli-Text'] = pos['STUELI_TEXT']
        artikel_data['Werkstoff'] = pos['WERKSTOFF']
        artikel_data['Norm'] = pos['WERKNORM']
        artikel_data['Menge'] = int(pos['MENGE'])
        artikel_data['Hauptlieferant'] = pos['HAUPTLIEFERANT']
        artikel_data['Dispoaktiv'] = pos['DISPO_AKTIV_JN']
        artikel_data['Rüstcode'] = pos['RUESTCODE']
        artikel_data['Lieferantenbeistellung'] = pos['KZ_LIEFERANTEN_BEISTELLUNG']

        header = ['Nummer', 'Position', 'Zeichnungsnummer', 'Bezeichnung', 'Parent', 'Konstruktionstext',
                  'Stüli-Text', 'Werkstoff', 'Norm', 'Menge', 'Hauptlieferant', 'Dispoaktiv', 'Rüstcode', 'Lieferantenbeistellung']

        csv_row = {'Nummer': escaped_csv_text(artikel_data['Zeichnungsnummer']),
                   'Position': escaped_csv_text(artikel_data['Position']),
                   'Zeichnungsnummer': escaped_csv_text(artikel_data['Zeichnungsnummer']),
                   'Bezeichnung': escaped_csv_text(artikel_data['Bezeichnung']),
                   'Parent': escaped_csv_text(artikel_data['Parent']),
                   'Konstruktionstext': escaped_csv_text(artikel_data['Konstruktionstext']),
                   'Stüli-Text': escaped_csv_text(artikel_data['Stüli-Text']),
                   'Werkstoff': escaped_csv_text(artikel_data['Werkstoff']),
                   'Norm': escaped_csv_text(artikel_data['Norm']),
                   'Menge': escaped_csv_text(artikel_data['Menge']),
                   'Hauptlieferant': escaped_csv_text(artikel_data['Hauptlieferant']),
                   'Dispoaktiv': escaped_csv_text(artikel_data['Dispoaktiv']),
                   'Rüstcode': escaped_csv_text(artikel_data['Rüstcode']),
                   'Lieferantenbeistellung': escaped_csv_text(artikel_data['Lieferantenbeistellung'])}
        csv_rows.append(csv_row)

        sql = f"""select o.agnr,
                         o.kostenst kostenstelle,
                         o.verricht_text,
                         decode(o.codeausw,
                                'J',
                                (select name1 from gepa where kdnr = o.kostenst),
                                (select c.bez from centre c where c.kostenst = o.kostenst)) bezeichnung,
                         nvl(o.ruestzeit * 60, 0) ruestzeit,
                         nvl(o.stueckzeit * 60, 0) stueckzeit
                    from opplan o
                   where o.teilenr = :teilenr"""
        cursor.execute(sql, [pos['ZEICHNUNGSNUMMER']])
        columns = [col[0] for col in cursor.description]
        cursor.rowfactory = lambda *args: dict(zip(columns, args))
        ag_data = cursor.fetchall()

        first_time = True
        for ag in ag_data:
            if first_time:
                artikel_data['Arbeitsgänge'] = []
                first_time = False

            ag_content = {}
            ag_content['Nummer'] = int(ag['AGNR'])
            ag_content['Kostenstelle'] = int(ag['KOSTENSTELLE'])
            ag_content['Bezeichnung'] = ag['BEZEICHNUNG']
            ag_content['Verrichttext'] = ag['VERRICHT_TEXT']
            ag_content['Rüstzeit'] = int(ag['RUESTZEIT'])
            ag_content['Stückzeit'] = int(ag['STUECKZEIT'])
            artikel_data['Arbeitsgänge'].append(ag_content)

        artikel_data_list_of_dict = [artikel_data]
        if artikel_data['Zeichnungsnummer'] in json_data:
            artikel_data_list_of_dict = json_data[artikel_data['Zeichnungsnummer']]
            artikel_data_list_of_dict.append(artikel_data)

        json_data[artikel_data['Zeichnungsnummer']] = artikel_data_list_of_dict

        if pos['BAUGRUPPE']:
            artikel_daten_schreiben(pos['BAUGRUPPE'], json_data, csv_rows, level+1)

def main():
    global args
    global oracle_connection

    instantclient_path = r"\\195.243.223.3\sivas_schreiben\jit\oracle\bin\x64"
    os.environ["PATH"] = instantclient_path + ";" + os.environ["PATH"]

    parser = argparse.ArgumentParser(description='Exportiert Stücklisten und deren Arbeitsgänge in eine JSON-, bzw. '
                                                 'CSV-Datei.')
    parser.add_argument('-o', '--output', dest='para_output_dir',
                        default=os.path.join(os.path.join(os.environ['USERPROFILE']), 'Desktop'),
                        help='Spezifiziert das Zielverzeichnis, wo die Export-Datei abgelegt wird.')
    parser.add_argument('--format', dest='para_format', default='json', help='Spezifiziert den Typ der Export-Datei.')
    parser.add_argument('--silent', dest='para_silent', action='store_false',
                        help='Deaktiviert die Ausgabe und Benutzereingaben.')
    parser.add_argument('--openresult', dest='para_openresult', action='store_true',
                        help='Spezifiziert, ob nach dem Export die Export-Datei geöffnet werden soll.')
    parser.add_argument('-number', dest='para_number', help='Einzelne Artikelnummer')
    parser.add_argument('-numbers', dest='para_numbers', help='Kommaseparierte liste von Artikelnummern')
    parser.add_argument('-from', dest='para_from', help="Beginn des Artikelnummernbereichs, bspw.: 4700")
    parser.add_argument('-to', dest='para_to', help="Ende des Artikelnummernbereichs, bspw.: 4999")
    parser.add_argument('-export_pattern', dest='para_export_pattern', help="Angabe eines Artikelnummernbereichs mit "
                                                                            "Wildcards, bspw.: 87X123X45")

    args = parser.parse_args()

    try:
        oracledb.init_oracle_client(lib_dir=instantclient_path)
    except Exception as err:
        log_print("Error connecting: oracledb.init_oracle_client()")
        log_print(err)
        sys.exit(1)

    log_print('=== Start Sivas2json ===\n')
    log_print('Prüfe Parameter...')

    if not args.para_number and not args.para_numbers and not args.para_from and not args.para_to and not args.para_export_pattern:
        log_input("Spezifizieren Sie mindestens die Parameter '-number=4711', '-numbers=4711,4712' "
              "oder '-from=47xx' und '-to=49xx'.\nBeliebige Taste drücken um zu beenden...")
        return 1

    log_print('Parameter OK\n')
    log_print('Oracle Verbindung...')

    try:
        # oracle_connection = JitOracleConnection('195.243.223.3', 'linux', instantclient_dir=instantclient_path,
        #                                      init_oracle_client=False)
        oracle_connection = JitOracleConnection(db_host='195.243.223.3', db_sid='linux', db_user='sivas', db_pwd='forte',
                                                db_database='sivas', instantclient_dir=instantclient_path, tns_admin=None, app=None,
                                                init_oracle_client=True)
    except Exception as e:
        log_print(str(e))

    log_print('Oracle Verbindung OK\n')

    sql = """select distinct uebteilenr from comp where ("""

    or_needed = False
    if args.para_number:
        sql += f" uebteilenr = '{args.para_number}'"
        or_needed = True
    if args.para_numbers:
        args.para_numbers = ','.join(["'" + x.strip() + "'" for x in args.para_numbers.split(',')])
        if or_needed:
            sql += ' or '
        sql += f" uebteilenr in ({args.para_numbers})"
        or_needed = True
    if args.para_from and args.para_to:
        if or_needed:
            sql += ' or '
        sql += f" uebteilenr between '{args.para_from}' and '{args.para_to}'"
    if args.para_export_pattern:
        if or_needed:
            sql += ' or '
        sql += f" uebteilenr like '{args.para_export_pattern.replace('X', '_').replace('x', '_')}'"

    sql += """) """

    cursor = oracle_connection.cursor()
    cursor.execute(sql)
    columns = [col[0] for col in cursor.description]
    cursor.rowfactory = lambda *args: dict(zip(columns, args))
    baugruppen = cursor.fetchall()

    if not baugruppen:
        if oracle_connection.connection():
            oracle_connection.connection().close()
        log_input(f"Keine Datensätze für diese Parameter gefunden")
        return 1

    file_count = 0
    export_filename = str()
    header = ['Nummer', 'Position', 'Zeichnungsnummer', 'Bezeichnung', 'Parent', 'Konstruktionstext',
              'Stüli-Text', 'Werkstoff', 'Norm', 'Menge', 'Hauptlieferant', 'Dispoaktiv', 'Rüstcode', 'Lieferantenbeistellung']

    for baugruppe in baugruppen:
        json_data = {}
        csv_rows = [{}]
        artikel_daten_schreiben(uebteilenr=baugruppe['UEBTEILENR'], json_data=json_data, csv_rows=csv_rows, level=0)

        export_filename = os.path.join(args.para_output_dir, baugruppe['UEBTEILENR'] + '.' + args.para_format.lower())

        log_print(f"Schreibe Export-Datei {export_filename} ...")


        # Writing json
        with open(export_filename, "w+", encoding='utf8', newline='') as outfile:
            if args.para_format.lower() == 'csv':
                writer = csv.DictWriter(outfile,  delimiter=";", fieldnames=header, escapechar='\\')
                # writer.writerow(header)
                writer.writeheader()
                for csv_row in csv_rows:
                    writer.writerow(csv_row)
            else:
                outfile.write(json.dumps(json_data, indent=4, ensure_ascii=False))

        if args.para_format.lower() == 'csv':
            lines = []
            with open(export_filename, 'r') as f:
                lines = f.readlines()

            with open(export_filename, 'w') as f:
                f.writelines(lines[:1] + lines[2:])

        log_print(f"Export-Datei geschrieben nach: {export_filename}.")

        file_count += 1

    if oracle_connection.connection():
        oracle_connection.connection().close()

    if args.para_openresult:
        if file_count > 1:
            log_input("Beliebige Taste drücken um Programm zu beenden und Ordner zu öffnen...")
            subprocess.Popen(['explorer', args.para_output_dir])
        elif file_count == 1:
            log_input("Beliebige Taste drücken um Programm zu beenden und Datei zu öffnen...")
            subprocess.Popen([export_filename], shell=True)

    return 0


if __name__ == '__main__':
    main()