#!/usr/bin/env python3

import glob
import http.client
import os
import pprint
import random
import re
import shutil
import subprocess
import sys
import tempfile
import time
import packaging.version
import urllib.error
import urllib.request


if sys.version_info < (3, 9):
   sys.stderr.write('ERROR: ' + sys.argv[0] + ' requires Python 3.9 or later!\n')
   sys.exit(1)

from typing import Any, Final, TextIO, cast


TarballFormats : Final[list[str]]     = [ 'xz', 'bz2', 'gz' ]
TarOptions     : Final[dict[str,str]] = {
   'xz':  'J',
   'bz2': 'j',
   'gz':  'z'
}


# ###### Get Debian/Ubuntu codenames ########################################
def getDistributionCodenames(distribution : str = 'debian') -> list[str]:

   if distribution == 'debian':
      distroInfo : str = 'debian-distro-info'
   else:
      distroInfo = 'ubuntu-distro-info'
   try:
      process : subprocess.Popen[str] = \
         subprocess.Popen([ distroInfo, '--all'],
                          stdout=subprocess.PIPE, universal_newlines=True)
      assert process is not None
      assert process.stdout is not None
      result : Final[list[str]] = process.stdout.readlines()
   except Exception as e:
      sys.stderr.write('ERROR: Unable to obtain Debian codenames: ' + str(e) + '\n')
      sys.exit(1)

   codenames : list[str] = [ codename.strip() for codename in result ]
   if distribution == 'debian':
      codenames += [ 'unstable', 'testing', 'stable', 'oldstable' ]
      codenames += [ codename.strip() + '-backports'        for codename in result]
      codenames += [ codename.strip() + '-backports-sloppy' for codename in result]
   codenames = sorted(codenames)

   # pprint.pprint(codenames, indent=1)
   return codenames

DebianCodenames = getDistributionCodenames()


# ###### Fetch Debian changelog file ########################################
def fetchDebianChangelogAndControl(packageName : str,
                                   codename    : str = 'unstable') -> tuple[list[str] | None, list[str] | None]:

   # ====== Set download directory URL ======================================
   assert DebianCodenames is not None
   downloadDirectoryURL : str
   if codename in DebianCodenames:
      # Debian:
      downloadDirectoryURL = \
         'https://ftp.debian.org/debian/pool/main/' + \
            packageName[0:1] + '/' + packageName + '/'
   else:
      # Ubuntu:
      downloadDirectoryURL = \
         'https://archive.ubuntu.com/ubuntu/pool/universe/' + \
            packageName[0:1] + '/' + packageName + '/'

   # ====== Get package status ==============================================
   maxTrials            : Final[int] = 50
   avgWaitingTime       : Final[int] = 15
   re_debian_tarball    : Final[re.Pattern[str]] = \
      re.compile(r'(<tr>.*)(<a href=")(' + packageName + r')(_)([0-9+~\.a-z]+)(-)([0-9+~\.a-z]+)(\.debian\.tar\.[a-zA-Z0-9]+)(")')
   httpHeders : Final[dict[str,str]] = {
      'User-Agent': 'Build-Tool/0.4.0 (AmigaOS; MC680x0)',
      'Accept':     '*/*'
   }

   for trial in range(0, maxTrials):
      if trial > 0:
         sys.stderr.write('\n')
      sys.stderr.write('Looking for package status on ' + downloadDirectoryURL +
                     ' (trial ' + str(trial + 1) + '/' + str(maxTrials) + ') ... ')
      sys.stderr.flush()

      versions = [ ]
      try:
         statusPageRequest : urllib.request.Request   = urllib.request.Request(downloadDirectoryURL,
                                                                               headers = httpHeders)
         statusPage        : http.client.HTTPResponse = urllib.request.urlopen(statusPageRequest)

         for statusPageLine in statusPage:
            line  : str                  = statusPageLine.decode('utf-8')
            match : re.Match[str] | None = re_debian_tarball.match(line)
            if match is not None:
               # Break the version into Python-compatible segments:
               version = ( packaging.version.parse(match.group(5).replace('~', '+')),
                           packaging.version.parse(re.sub(r'(\d)([a-zA-Z])', r'\1+\2', match.group(7))),
                           match.group(5) + match.group(6) + match.group(7) + match.group(8) )
               versions.append(version)

         statusPage.close()
         break

      except urllib.error.HTTPError as e:
         sys.stderr.write('not found (HTTP ' + str(e.code) + ') ... ')
         if e.code == 404:
            # Not found -> done!
            break
         if trial + 1 < maxTrials:
            time.sleep(random.uniform(0, 2 * avgWaitingTime))

   if len(versions) == 0:
      sys.stderr.write('not found!\n')
      return ( None, None )

   # ====== Obtain latest version ===========================================
   versions = sorted(versions, reverse = True)
   debianVersion  : Final[str] = versions[0][2]
   debianArchive  : Final[str] = packageName + '_' + debianVersion
   archiveFileURL : Final[str] = downloadDirectoryURL + debianArchive
   sys.stderr.write('latest version is ' + debianVersion + '\n')

   # ====== Determine necessary compression option =============================
   debianArchiveFormat  = debianArchive[len(debianArchive) - 2 : len(debianArchive)]
   tarCompressionOption = TarOptions[debianArchiveFormat]

   # ====== Fetch debian archive ===============================================
   sys.stderr.write('Looking for \"debian\" archive at ' + archiveFileURL + ' ... ')
   sys.stderr.flush()

   result : tuple[list[str] | None,list[str] | None] = ( None, None )
   try:
      archiveFileRequest : urllib.request.Request   = urllib.request.Request(archiveFileURL,
                                                                             headers = httpHeders)
      archiveFile        : http.client.HTTPResponse = urllib.request.urlopen(archiveFileRequest)
      debianArchiveFile  : tempfile._TemporaryFileWrapper[bytes] = \
         tempfile.NamedTemporaryFile(delete = False)

      shutil.copyfileobj(archiveFile, debianArchiveFile)
      debianArchiveFile.close()
      archiveFile.close()
      sys.stderr.write('found!\n')

      # ====== Extract changelog and control ================================
      debianChangelog : list[str] = [ ]
      debianControl   : list[str] = [ ]
      try:
         # ------ Extract debian/changelog ----------------------------------
         process : subprocess.Popen[str] = \
            subprocess.Popen([ 'tar', 'x' + tarCompressionOption + 'fO', debianArchiveFile.name, 'debian/changelog'],
                             stdout=subprocess.PIPE, universal_newlines=True)
         if ( (process is not None) and (process.stdout is not None) ):
            debianChangelog = process.stdout.readlines()

         # ------ Extract debian/control ------------------------------------
         process = \
            subprocess.Popen([ 'tar', 'x' + tarCompressionOption + 'fO', debianArchiveFile.name, 'debian/control'],
                             stdout=subprocess.PIPE, universal_newlines=True)
         if ( (process is not None) and (process.stdout is not None) ):
            debianControl = process.stdout.readlines()

         os.unlink(debianArchiveFile.name)
         result = ( debianChangelog, debianControl )

      except Exception as e:
         sys.stderr.write('ERROR: Failed to extract debian/changelog from ' + debianArchiveFile.name + ': ' + str(e) + '\n')
         sys.exit(1)

   except urllib.error.HTTPError as e:
      sys.stderr.write('not found (HTTP ' + str(e.code) + ')!\n')

   return result




packages = [
   'bibtexconv',
   'dynmhs',
   'fractgen',
   'hipercontracer',
   'netperfmeter',
   'rsplib',
   'sctplib',
   'socketapi',
   'subnetcalc',
   'td-system-tools'
]
for package in packages:
   ( debianChangelog, debianControl ) = fetchDebianChangelogAndControl(package, 'unstable')
for package in packages:
   ( debianChangelog, debianControl ) = fetchDebianChangelogAndControl(package, 'resolute')
   # print(debianControl)
