#!/usr/bin/python
# -*- coding: utf-8 -*-

from curl import pycurl, Curl
import re, sys, os, datetime, time

#eventuale proxy
#os.environ['http_proxy']='http://proxy.quargentan.loc:3128'
#os.environ['https_proxy']='http://proxy.quargentan.loc:3128'
#os.environ['http_proxy']='http://ruggero:ruggero@proxy.quargentan.loc:3128'
#os.environ['https_proxy']='http://ruggero:ruggero@proxy.quargentan.loc:3128'

if not hasattr(datetime.datetime,'strptime'):  #added for Python < 2.5
  def strptime(date_string,format):
    return datetime.datetime(*(time.strptime(date_string, format)[0:6]))
else:
  strptime = datetime.datetime.strptime


class MyCurl(Curl):
  def __init__(self,debug=0,*args,**kwargs):
    Curl.__init__(self,*args,**kwargs)
    self.debug = debug
    self.set_verbosity(debug)
    self.set_option(pycurl.NETRC,pycurl.NETRC_IGNORED)
    self.set_option(pycurl.TIMEOUT,10)
  def get(self,url):
    if self.debug:
      print >>sys.stderr, "### /GET ###"
      print >>sys.stderr, url
      print >>sys.stderr, "### GET/ ###"
    ret = Curl.get(self,url)
    if self.debug>1:
      print >>sys.stderr, "### /BODY ###"
      print >>sys.stderr, self.body()
      print >>sys.stderr, "### BODY/ ###"
    return ret
  def post(self,url,params):
    if self.debug:
      print >>sys.stderr, "### /POST ###"
      print >>sys.stderr, url
      print >>sys.stderr, params
      print >>sys.stderr, "### POST/ ###"
    ret = Curl.post(self,url,params)
    if self.debug>1:
      print >>sys.stderr, "### /BODY ###"
      print >>sys.stderr, self.body()
      print >>sys.stderr, "### BODY/ ###"
    return ret
  def httppost(self,url,params):
    if self.debug:
      print >>sys.stderr, "### /POST ###"
      print >>sys.stderr, url
      print >>sys.stderr, params
      print >>sys.stderr, "### POST/ ###"
    self.set_option(pycurl.POST,1)
    self.set_option(pycurl.HTTPPOST,params)
    if self.fakeheaders:
      self.set_option(pycurl.HTTPHEADER, self.fakeheaders)
    if url:
      self.set_option(pycurl.URL,os.path.join(self.base_url,url))
    self.payload = ""
    self.hdr = ""
    self.handle.perform()
    if self.debug>1:
      print >>sys.stderr, "### /BODY ###"
      print >>sys.stderr, self.body()
      print >>sys.stderr, "### BODY/ ###"
    return self.payload
  def update(self,params,values):
    pdict = dict(params)
    pdict.update(values)
    return pdict.items()

class DoganeUserSession(MyCurl):
  def __init__(self,username,password,test=True,*args,**kwargs):
    self.test = test
    if test: url = "https://telematicoprova.agenziadogane.it/"
    else:    url = "https://telematico.agenziadogane.it/"
    self.test = test
    MyCurl.__init__(self,base_url=url,*args,**kwargs)
    self.set_option(pycurl.SSLVERSION,pycurl.SSLVERSION_SSLv3)
#   self.set_option(pycurl.HTTPAUTH,pycurl.HTTPAUTH_ANY)
#   self.set_option(pycurl.USERPWD,':'.join([username,password]))
    self.login(username,password)

  def login(self,username,password):
    url = "TelematicoVisualizzareFileWEB/login.jsp"
    #0. get the login form
    self.get(url)
    hidden=re.compile('<INPUT\s+type="hidden"\s+name="([^"]+)"\s+value="([^"]*)">',re.I)
    params=hidden.findall(self.body())
    if not params:
      params = []
    #1. set 
    params += [ ("j_username",username), ("j_password",password), ("login", "   OK   ") ]
    self.post("TelematicoVisualizzareFileWEB/j_security_check",params)
    return self.body()
    
  def upload(self,filename):
    url = "TelematicoInvioFileWEB/GestireInvioFileServlet"
    #0. get the upload form
    self.get(url+"?UC=1&SC=1&ST=1")
    hidden=re.compile('<INPUT\s+type="hidden"\s+name="([^"]+)"\s+value="([^"]*)">',re.I)
    params=hidden.findall(self.body())
    if not params:
      return None
    #1. upload the file
    params += [ ("FILE",(pycurl.FORM_FILE,filename)), ("submit", "Invio File") ]
    self.httppost(url, params)
    params=hidden.findall(self.body())
    if not params:
      return None
    #2. confirm the upload
    self.post(url,params)
    #3. return results
    answer = self.body()
    trovacodice = re.compile('registrato con il n.:</td>.*?<td(?:[^>]*)>&nbsp;&nbsp;(\d+)</td>',re.S)
    results = trovacodice.search(answer)
    if not results:
      return None
    return results.group(1)
  def query(self,codice):
    url = "TelematicoVisualizzareFileWEB/VisualizzareFileServlet"
    #0. request query page
    self.get(url)
    hidden=re.compile('<INPUT\s+type="hidden"\s+name="([^"]+)"\s+value="([^"]*)">',re.I)
    params=hidden.findall(self.body())
    #1. ask for a specific code
    params = self.update(params,{"codfile": codice, "UC": 1, "SC": 1, "ST": 1})
    self.post(url,params)
    if self.answered('Non risultano files'):
      return None  #file inesistente
    hidden=re.compile('<INPUT\s+type="hidden"\s+name="([^"]+)"\s+value="([^"]*)">',re.I)
    params=hidden.findall(self.body())
    #2. ask for file info
    output = { 'codice':  codice }
    params = self.update(params,{"COD_FILE_RICERCA": codice, "UC": 1, "SC": 1, "ST": 2})
    self.post(url,params)
    if self.answered('Errori formali'):
      output['esito'] = 'Errori formali'
    elif self.answered('Errori sostanziali'):
      output['esito'] = 'Errori sostanziali'
    rex =  ">Data Invio:</td>\s*<td(?:[^>]*)>([^>]+)</td>"
    results = re.search(rex,self.body(),re.S)
    if results:
      output['datainvio'] = strptime(results.group(1),'%d/%m/%Y %H:%M:%S').strftime('%Y-%m-%d %H:%M:%S')
    params=hidden.findall(self.body())
    #3. ask for details
    params = self.update(params,{"UC": 1, "SC": 1, "ST": 4})
    self.post(url,params)
    params=hidden.findall(self.body())
    if self.answered('Documento di accompagnamento accise') or self.answered('Messaggio relativo a DAA'):
      output['tipofile'] = 'DAA'
    elif self.answered('ALCOAV'):
      output['tipofile'] = 'ACCISE'
    elif self.answered('Dichiarazioni Intrastat'):
      output['tipofile'] = 'INTRASTAT'
      #le dichiarazioni Intrastat possono contenere due esiti distinti,
      #uno per le CESSIONI ed uno per gli ACQUISTI
      if not output.has_key('esito'):
        if self.answered('NEGATIVO'):
          output['esito'] = 'NEGATIVO'
        elif self.answered('POSITIVO'):
          output['esito'] = 'POSITIVO'
        else:
          output['esito'] = 'SCONOSCIUTO'
    else:
      output['tipofile'] = 'SCONOSCIUTO'
    #4. show results
    trovapratica=re.compile("javascript:visualizzaDettagliEsito\('([^']+)'\)")
    results = trovapratica.search(self.body())
    if results:
      pratica = results.group(1)
      params = self.update(params,{"CHIAVE_PRATICA": pratica, "UC": 1, "SC": 1, "ST": 5})
      self.step = (3,params)
      self.post(url,params)
      #5. extract required values
      regex = {
        "filename":   ">Nome del file:</td>\s*<td(?:[^>]*)>([^<]+)</td>",
        "esito":      ">Esito:</b></td>\s*<td(?:[^>]*)>([^<]+)</td>",
        "firmatario": ">Codice id. Firmatario:</b></td>\s*<td(?:[^>]*)>([^<]+)</td>",
        "codicearc":  ">Codice ARC:</b></td>\s*<td(?:[^>]*)>([^<]+)</td>",
      }
      if output['tipofile'] == 'ACCISE':
        regex['esito']      = '>(ACQUISITO)</td>'
        regex['firmatario'] = '>Cod.Accisa Dich.:</td>\s*<td(?:[^>]*)>([^>]+)</td>'
      for key,rex in regex.items():
        if not output.has_key(key):
          results = re.search(rex,self.body(),re.S)
          if results:
            output[key] = results.group(1)
          else:
            output[key] = ''
    return output

  def list(self,date_from=None,date_to=None):
    url = "TelematicoVisualizzareFileWEB/VisualizzareFileServlet"
    if not date_to:
      date_to = datetime.date.today().isoformat()  #default today
    date_to = date_to.split('-')
    if not date_from:
      date_from = (datetime.date.today()-datetime.timedelta(days=1)).isoformat() #default yesterday
    date_from = date_from.split('-')
    datesearch={"UC": 1, "SC": 1, "ST": 1}
    datesearch=self.update(datesearch,dict(zip(['anno_da','mese_da','giorno_da','ora_da'],list(date_from)+[0])))
    datesearch=self.update(datesearch,dict(zip(['anno_a','mese_a','giorno_a','ora_a'],list(date_to)+[23])))
    #0. request query page
    self.get(url)
    hidden=re.compile('<INPUT\s+type="hidden"\s+name="([^"]+)"\s+value="([^"]*)">',re.I)
    params=hidden.findall(self.body())
    #1. ask for a date range
    params=self.update(params,datesearch)
    self.post(url,params)
    if self.answered('Non risultano files'):
      return (0,[])  #nessun file nel periodo selezionato
    trovacodici=re.compile("javascript:showFileInfo\('(\d+)'\)",re.I)
    codici=trovacodici.findall(self.body())
    trovapagine=re.compile("javascript:setPage\((\d+)\)",re.I)
    pagine=trovapagine.findall(self.body())
    precedente=1
    for pagina in pagine:
      params=hidden.findall(self.body())
      params=self.update(params,datesearch)
      params=self.update(params,{"GOTO_PAGE": pagina})
      self.post(url,params)
      codici+=trovacodici.findall(self.body())
      precedente=pagina
    params=hidden.findall(self.body())
    totale = int(dict(params).get('NUM_OCCORRENZE_TOT',0))
    codici.reverse()
    return (totale,codici)

class AidaUserSession(MyCurl):
  def __init__(self,username,password,codice_accise,*args,**kwargs):
    self.url = "https://aidaonline.agenziadogane.it/DAAInternetWeb/DAADispServlet"
    MyCurl.__init__(self,*args,**kwargs)
    self.set_option(pycurl.SSLVERSION,pycurl.SSLVERSION_SSLv3)
    self.set_option(pycurl.SSL_VERIFYPEER, 0) # il certificato di AIDA è self-signed dall'Agenzia delle Dogane
    self.set_option(pycurl.SSL_VERIFYHOST, 0) # il CN sul certificato è intra.agenziadogane.it
#   self.set_option(pycurl.HTTPAUTH,pycurl.HTTPAUTH_ANY)
#   self.set_option(pycurl.USERPWD,':'.join([username,password]))
    self.codice_accise = codice_accise
    self.username = username
    self.password = password
    self.loggedin = False
  def login(self):
    #l'autenticazione è cross-site
    #0. get the login form
    self.get('https://telematico.agenziadogane.it/TelematicoVisualizzareFileWEB/login.jsp')
    hidden=re.compile('<INPUT\s+type="hidden"\s+name="([^"]+)"\s+value="([^"]*)">',re.I)
    params=hidden.findall(self.body())
    if not params:
      params = []
    #1. set 
    params += [ ("j_username",self.username), ("j_password",self.password), ("login", "   OK   ") ]
    self.post('https://telematico.agenziadogane.it/TelematicoVisualizzareFileWEB/j_security_check',params)
    self.get('https://telematico.agenziadogane.it/TelematicoServiziDiUtilitaWeb/ServiziDiUtilitaAutServlet?UC=22&SC=1&ST=2')
    #una volta autenticati, forniamo il codice accise
    self.post(self.url,{"CODICE_ACCISE": self.codice_accise, "UC": 1, "SC": 1, "ST": 0})
    self.loggedin = True
  def dettaglio(self,codicearc):
    if not self.loggedin: self.login()
    data, stato = None, None
    self.post(self.url,params={"ARC": codicearc, "DAA_SEQ_NUMBER": 1, "UC": 4, "SC": 1, "ST": 0, "AREA_DI_PROVENIENZA": "Notifiche" })
    #print self.body()
    if self.answered('Pagina di Errore'):
      self.post(self.url,params={"ARC": codicearc, "DAA_SEQ_NUMBER": 2, "UC": 4, "SC": 1, "ST": 0, "AREA_DI_PROVENIENZA": "Notifiche" })
    if self.answered('Pagina di Errore'):
      self.post(self.url,params={"ARC": codicearc, "DAA_SEQ_NUMBER": 3, "UC": 4, "SC": 1, "ST": 0, "AREA_DI_PROVENIENZA": "Notifiche" })
    if self.answered('Pagina di Errore'):
      self.post(self.url,params={"ARC": codicearc, "DAA_SEQ_NUMBER": 4, "UC": 4, "SC": 1, "ST": 0, "AREA_DI_PROVENIENZA": "Notifiche" })
    if not self.answered('Pagina di Errore'):
      trovadata=re.compile(r'>Data Aggiornamento\s*:.*?<b>(.+?)</b>',re.S)
      trovastato=re.compile(r'>Stato\s*:.*?<b>(.+?)</b>',re.S)
      results = trovadata.search(self.body())
      if results:
        data = strptime(results.group(1),'%d/%m/%Y %H:%M:%S').strftime('%Y-%m-%d %H:%M:%S')
      results = trovastato.search(self.body())
      if results:
        stato = results.group(1)
    return (data,stato)
  def stampaDAA(self,codicearc):
    if not self.loggedin: self.login()
    self.post(self.url,{"ARC": codicearc, "DAA_SEQ_NUMBER": 1, "UC": 6, "SC": 3, "ST": 0})
    pdf = self.body()
    if not len(pdf):
      self.post(self.url,{"ARC": codicearc, "DAA_SEQ_NUMBER": 2, "UC": 6, "SC": 3, "ST": 0})
      pdf = self.body()
    if not len(pdf):
      self.post(self.url,{"ARC": codicearc, "DAA_SEQ_NUMBER": 3, "UC": 6, "SC": 3, "ST": 0})
      pdf = self.body()
    if not len(pdf):
      self.post(self.url,{"ARC": codicearc, "DAA_SEQ_NUMBER": 4, "UC": 6, "SC": 3, "ST": 0})
      pdf = self.body()
    return pdf
  def dettaglioRicezione(self,codicearc):
    if not self.loggedin: self.login()
    data, esito = None, None
    self.post(self.url,params={"ARC": codicearc, "DAA_SEQ_NUMBER": 1, "UC": 4, "SC": 6, "ST": 0})
    if self.answered('Pagina di Errore'):
      self.post(self.url,params={"ARC": codicearc, "DAA_SEQ_NUMBER": 2, "UC": 4, "SC": 6, "ST": 0})
    if self.answered('Pagina di Errore'):
      self.post(self.url,params={"ARC": codicearc, "DAA_SEQ_NUMBER": 3, "UC": 4, "SC": 6, "ST": 0})
    if self.answered('Pagina di Errore'):
      self.post(self.url,params={"ARC": codicearc, "DAA_SEQ_NUMBER": 4, "UC": 4, "SC": 6, "ST": 0})
    if not self.answered('Pagina di Errore'):
      trovadata=re.compile(r'<TD\s*.*?>Data Validazione\s*:.*?<b>(.+?)</b>.*?</TD>',re.S)
      trovaesito=re.compile(r'<TD\s*.*?>Esito Globale ricezione\s*:.*?<INPUT.*? value="(.*?)".*?>.*?</TD>',re.S)
      results = trovadata.search(self.body())
      if results:
        data = strptime(results.group(1),'%d/%m/%Y %H:%M:%S').strftime('%Y-%m-%d %H:%M:%S')
      results = trovaesito.search(self.body())
      if results:
        esito = results.group(1)
        tmp = esito.split(' - ',1)
        if len(tmp)==2 and tmp[0] == tmp[1]:
          esito = tmp[0]
    return (data,esito)

if __name__ == "__main__":
  from optparse import OptionParser
  usage = "usage: %prog [options] argument"
  parser = OptionParser(usage=usage)
  parser.set_defaults(mode="query")
  parser.add_option("-U", "--username", dest="username", action="store", type="string",
                    help="set the username for accessing the website [REQUIRED]")
  parser.add_option("-P", "--password", dest="password", action="store", type="string",
                    help="set the password for accessing the website [REQUIRED]")
  parser.add_option("-l", "--list", dest="mode", action="store_const", const="list",
                    help="list uploaded files since date provided (YYYY-MM-DD); an optional second argument is used as end date")
  parser.add_option("-q", "--query", dest="mode", action="store_const", const="query",
                    help="query ARC for given filecode (default)")
  parser.add_option("-u", "--upload", dest="mode", action="store_const", const="upload",
                    help="upload given file")
  parser.add_option("-r", "--receipt", dest="mode", action="store_const", const="receipt",
                    help="receipt for a given ARC [DAA only]")
  parser.add_option("-p", "--pdf", dest="mode", action="store_const", const="pdf",
                    help="DAA PDF for a given ARC [DAA only]")
  parser.add_option("-s", "--status", dest="mode", action="store_const", const="status",
                    help="status for a given ARC [DAA only]")
  parser.add_option("-t", "--test", dest="test", action="store_true", default=False,
                    help="use the test environment web site")
  parser.add_option("-v", "--verbose", dest="verbose", action="count", default=0,
                    help="debug conversation")
  (options, args) = parser.parse_args()
  if (len(args)!=1) and not ((options.mode == 'list') and len(args)==2):
    parser.error("an argument is required; try -h")
  if not options.username or not options.password:
    parser.error("-U username and -P password parameters are required; try -h")
  def errlog(msg):
    print >>sys.stderr,"%s - %s" % (datetime.datetime.now().ctime(), msg)
  if options.mode in ['list','query','upload']:
    session = DoganeUserSession(
      username=options.username,password=options.password,test=options.test,debug=options.verbose
    )
  else:  #options.mode in ['receipt','pdf','status']
    if options.test:
      errlog("ERRORE: funzioni DAA non disponibili in modalità di test")
      sys.exit(1)
    codicearc = args[0]
    codiceacc = codicearc[2:4] + '00' + codicearc[4:15]
    session = AidaUserSession(
      username=options.username,password=options.password,codice_accise=codiceacc,debug=options.verbose
    )
    
  try:
    if options.mode == 'list':
      try:
        dates = sorted(map(lambda d: strptime(d,'%Y-%m-%d').date().isoformat(),args))
      except ValueError,e:
        errlog("List: ERRORE: data non valida: %s" % e)
        sys.exit(1)
      errlog("List: %s" % ', '.join(dates))
      tot,codici = session.list(*dates)
      if not codici:
        errlog("List: ERRORE: nessun file trovato")
        sys.exit(1)
      if tot!=len(codici):
        errlog('List: WARNING: il sistema riporta %s files disponibili, ma mostra solo %d codici' % ( tot,len(codici) ))
      print 'totale:%s' % tot
      for codice in codici:
        print codice
    elif options.mode == 'upload':
      filename = args[0]
      errlog("Upload: file '%s'" % filename)
      if not os.path.isfile(filename):
        errlog("Upload: ERRORE: File non trovato '%s'" % filename)
        print "File da inviare non trovato"
        sys.exit(1)
      try:
        codice = session.upload(filename)
      except Exception, e:
        errlog("Upload: ERRORE: '%s'" % e)
        print "Errore connessione internet"
        sys.exit(1)
      if not codice:
        errlog("Upload: ERRORE: codice file non trovato nell'esito delle dogane")
        print "Codice file non trovato"
        sys.exit(1)
      errlog("Upload: codice file ricevuto '%s'" % codice)
      print codice
    elif options.mode == 'query':
      codice  = args[0]
      errlog("Query: codice file '%s'" % codice)
      results = session.query(codice)
      if not results or not results.has_key('esito'):
        errlog("Query: codice file '%s' non trovato" % codice)
        print "Codice file '%s' non trovato" % codice
        sys.exit(1)
      if not results.has_key('codicearc'):
        errlog("Query: esito file '%s'" % results['esito'])
        print results['esito']
        sys.exit(1)
      elif not results['codicearc']:
        errlog("Query: ricezione ARC fallita '%s'" % results['esito'])
        print results['esito']
        sys.exit(1)
      errlog("Query: codice ARC ricevuto '%s'" % results['codicearc'])
      print '%s|%s' % (codice,results['codicearc'])
      #print results
    elif options.mode == 'receipt':
      errlog("Receipt: codice ARC '%s'" % codicearc)
      data,esito = session.dettaglioRicezione(codicearc)
      if not data or not esito:
        errlog("Receipt: ERRORE: dettaglio ricezione per ARC '%s' non trovato" % codicearc)
        print "Dettaglio ricezione per ARC '%s' non trovato" % codicearc
        sys.exit(1)
        errlog("Receipt: dettaglio ricezione per ARC '%s': %s '%s'" % (codicearc,data,esito))
      print "%s|%s|%s" % (codicearc,data,esito)
    elif options.mode == 'status':
      errlog("Status: codice ARC '%s'" % codicearc)
      data,stato = session.dettaglio(codicearc)
      if not data or not stato:
        errlog("Status: ERRORE: dettaglio stato per ARC '%s' non trovato" % codicearc)
        print "Dettaglio stato per ARC '%s' non trovato" % codicearc
        sys.exit(1)
      print "%s|%s|%s" % (codicearc,data,stato)
    elif options.mode == 'pdf':
      errlog("PDF: codice ARC '%s'" % codicearc)
      pdf = session.stampaDAA(codicearc)
      if not len(pdf):
        errlog("PDF: ERRORE: stampa DAA per ARC '%s' non trovata" % codicearc)
        print "Stampa DAA per ARC '%s' non trovata" % codicearc
        sys.exit(1)
      print pdf
    else:
      parser.error("unsupported mode '%s'; try -h" % options.mode)
  except SystemExit,e:
    raise e
  except (pycurl.error,Exception),e:
    errlog("ERRORE connessione: %s" % e)
    print "ERRORE connessione: '%s'" % e
    sys.exit(1)
  sys.exit(0)

#TEST:
#   '2222222', #codice inesistente
#   '1869938', #controlli formali falliti
#   '1862976', #controlli sostanziali falliti
#   '1869859', #valido (sul test)
#   '11ITVRV00015S01834125', #TR, Rapporto Ricezione: 19/4/2011 13:57:09 Ricezione accettata e soddisfacente, PDF ok
#   '11ITVRV00015S02324743'  #TR, Rapporto Ricezione: nessuno, PDF ok
#   '11ITVRV00255A01723817'  #SB, Rapporto Ricezione: 13/04/2011 04:51:27 Ricezione accettata e soddisfacente, PDF ok
#   '11ITVRV00255A02259167'  #SB, Rapporto Ricezione: nessuno, PDF ok
