"""
    Uses Alpm for request pacman databases
"""

import os
import subprocess
import re
from pathlib import Path
import tarfile
import json
import urllib.request
from datetime import datetime
from typing import List, Generator

import pyalpm
from pycman import config
import lib

from .exceptions import BranchNotFoundError

class AlpmBranch:
    """Multi branches APPM functions"""

    #BRANCHES = ('archlinux', 'unstable', 'testing', 'stable-staging', 'stable')
    BRANCHES = ('archlinux', 'unstable', 'testing', 'stable')
    ROOT = lib.read_config_root()
    COLORS = {
        'archlinux': '\33[34m',
        'unstable': '\33[31m',
        'testing': '\33[33m',
        'stable-staging': '\33[35m',
        'stable': '\33[32m',
        'none': '\33[0m',
        'i': '\33[3m'
    }
    handles = {}    # alpm handles

    def __new__(cls, branch='unstable'):
        # create 4 handles singleton
        if not cls.handles:
            for branchname in cls.BRANCHES:
                try:
                    cls.handles[branchname] = config.init_with_config(f"{cls.ROOT}/{branchname}/etc/pacman.conf")
                except FileNotFoundError:
                    if branch == branchname:
                        raise BranchNotFoundError(f"local repo \"{branchname}\" not found ! (use command \"update\")", branchname)
        return super().__new__(cls)

    def __init__(self, branch: str = 'unstable'):
        #self.handle = None
        self._branch = 'unstable'
        self.branch = branch

    @property
    def handle(self):
        """ return curent branch alpm handle """
        return self.handles[self._branch]

    @property
    def branch(self):
        """return curent branch"""
        return self._branch

    @branch.setter
    def branch(self, value: str):
        """set branch change pacman.conf"""
        if not value in self.BRANCHES:
            raise ValueError(f"{value} is not a valid branch {self.BRANCHES}")
        self._branch = value

    @classmethod
    def color(cls, color: str = 'none'):
        """one color by branch"""
        return cls.COLORS.get(color, 'none')

    def pkg_get(self, pkgname: str) -> object:
        """find one package in db"""
        for db_repo in self.get_repos():
            pkg = db_repo.get_pkg(pkgname)
            if pkg:
                return pkg
        return None

    def pkg_search(self, to_search: str) -> Generator:
        """search in databases in names and description"""
        for db_repo in self.get_repos():
            pkgs = db_repo.search(to_search)
            for pkg in pkgs:
                yield pkg

    def pkg_search_by_field(self, to_search: str, field: str = 'name') ->Generator:
        """ search in database by one package field """
        regex = re.compile(to_search)
        matchs = None
        for db_repo in self.get_repos():
            pkgs = db_repo.search('')
            for pkg in pkgs:
                if field == "builddate":
                    try:
                        value = datetime.fromtimestamp(pkg.builddate).strftime('%Y-%m-%d')
                    except:
                        value = ''
                else:
                    try:
                        value = getattr(pkg, field)
                    except AttributeError:
                        raise AttributeError(f"Attribut not exists for a package !\n\n{'  '.join((a for a in dir(pkg) if not '_' in a))}")
                matchs = regex.search(str(value))
                if matchs:
                    yield pkg

    def pkg_search_in_name(self, pkgname: str) -> Generator:
        """search in databases by only names"""
        regex = re.compile(pkgname)
        for db_repo in self.get_repos():
            pkgs = db_repo.search('')
            for pkg in pkgs:
                matchs = regex.match(pkg.name)
                if matchs:
                    yield pkg

    def get_repos(self) -> Generator:
        """list repos in curent branch"""
        for db_repo in self.handle.get_syncdbs():
            yield db_repo

    @staticmethod
    def add_sudo(args: list) -> list:
        if Path(AlpmBranch.ROOT).stat().st_uid == 0:
            args.insert(0, 'sudo')
        return args

    @classmethod
    def create_branch(cls, branch: str = 'unstable'):
        """create database structure from config files in /etc/"""
        if os.geteuid() != 0:
            raise PermissionError("use sudo for create local databases")
        Path(f"{cls.ROOT}/{branch}/etc").mkdir(parents=True, exist_ok=True)
        Path(f"{cls.ROOT}/hooks").mkdir(exist_ok=True)
        Path(f"{cls.ROOT}/{branch}/lib/pacman/sync").mkdir(parents=True, exist_ok=True)
        # write pacman.conf
        Path(f"{cls.ROOT}/{branch}/etc/pacman.conf").touch(644, True)
        if branch == 'archlinux':
            txt = Path(f"/etc/{lib.PKGNAME}/archlinux.conf").read_text()
        else:
            txt = Path(f"/etc/{lib.PKGNAME}/manjaro.conf").read_text()
            #change branch : .../packages/unstable/$repo/...
            txt = re.sub('/(unstable|stable|testing)/', f"/{branch}/", txt)
        txt = re.sub('\nRootDir.*', f"\nRootDir = {cls.ROOT}/{branch}", txt)
        txt = re.sub('\nDBPath.*', f"\nDBPath = {cls.ROOT}/{branch}/lib/pacman", txt)
        if re.search('\nHookDir', txt):
            txt = re.sub('\nHookDir.*', f"\nHookDir = {cls.ROOT}/hooks", txt)
        else:
            txt = txt.replace('[options]', f"[options]\nHookDir = {cls.ROOT}/hooks", 1)
        Path(f"{cls.ROOT}/{branch}/etc/pacman.conf").write_text(txt)
        Path(f"{cls.ROOT}/{branch}/etc/pacman.conf").chmod(mode=0o644)
        with open(f"{cls.ROOT}/hooks/00-no.hook", 'w') as writer:
            writer.write(
                "[Trigger]\nOperation = Install\nType = File\nTarget = *\n" + \
                "[Action]\nDescription = no\nWhen = PreTransaction\nExec = /usr/bin/false\nAbortOnFail\n"
            )

    @staticmethod
    def set_server(url: str) -> bool:
        """change server in pacman.conf"""
        '''
            #TODO for python 3.11
        s    use import tomllib
        '''
        #Server = https://mirror.netzspielplatz.de/manjaro/packages/stable/$repo/$arch

        if os.geteuid() != 0:
            raise PermissionError("use sudo for create local databases")

        conf_file = Path(f"/etc/{lib.PKGNAME}/manjaro.conf")
        txt = conf_file.read_text()
        m = re.search('\nServer.*', txt)
        result = m.group(0) if m else ""
        if result:
            result = result.split("=")[-1].strip()
            txt = txt.replace(result, f"{url}unstable/$repo/$arch")
            tmp_file = f"{lib.TMP_DIR}/pacman.conf"
            conf_file.write_text(txt)
            print(conf_file, "Updated")
        else:
            raise ValueError(conf_file + "without Server ?")

    def do_refresh(self, force: bool = False, use_files: bool = False) -> None:
        "Sync databases like pacman -Sy"
        if force:
            force = 'y'
        else:
            force = ''
        argsp = [
            'sudo', 'pacman', f"-Sy{force}",
            '--logfile', f"{self.ROOT}/{self.branch}/pacman.log",
            '--config', f"{self.ROOT}/{self.branch}/etc/pacman.conf"
        ]
        try:
            subprocess.check_call(argsp)
            # load databases.files
            if use_files:
                argsp[2] = "-Fy"
                subprocess.check_call(argsp)
        except KeyboardInterrupt:
            raise PermissionError('Update aborded, Interrupted by the user')

    def do_update(self, force: bool = False) -> bool:
        """ as self.do_refresh() but use only alpm
            not use files
            Want exeception for gui
        """
        _last_dl_filename = ''

        def dl_cb(filename, tx, total):
            nonlocal _last_dl_filename
            if filename != _last_dl_filename:
                _last_dl_filename = filename
                print(f"\tdownload {filename} ...")

        self.handle.dlcb = dl_cb
        try:
            transaction = self.handle.init_transaction(force=force)
        except pyalpm.error as err:
            raise err
        try:
            for db_repo in self.handle.get_syncdbs():
                _last_dl_filename = ''
                db_repo.update(force=force)
        except pyalpm.error as err:
            raise err
        finally:
            transaction.release()
            self.handle.dlcb = None
        return True

    @classmethod
    def read_files(cls, branch, pkg: object) -> list:
        """extract file list from a package"""
        result = []
        if not os.path.exists(f"{cls.ROOT}/{branch}/lib/pacman/sync/{pkg.db.name}.files"):
            return result
        with tarfile.open(f"{cls.ROOT}/{branch}/lib/pacman/sync/{pkg.db.name}.files", 'r') as tar:
            tar.extract(f"{pkg.name}-{pkg.version}/files", lib.TMP_DIR)
            with open(f"{lib.TMP_DIR}/{pkg.name}-{pkg.version}/files") as ffiles:
                for line in ffiles:
                    line = line.strip()
                    if not line.endswith('/') and not line.startswith('%'):
                        result.append(line)
            os.remove(f"{lib.TMP_DIR}/{pkg.name}-{pkg.version}/files")
            os.rmdir(f"{lib.TMP_DIR}/{pkg.name}-{pkg.version}")
        return result

    def find_files(self, to_find: str, only_one: bool = False) -> List:
        """find in files list"""
        regex = re.compile(to_find)
        result = []
        for db_repo in self.get_repos():
            pkg = {'name':'', 'version':'', 'files':[], 'db':db_repo.name}
            try:
                with tarfile.open(f"{self.ROOT}/{self.branch}/lib/pacman/sync/{db_repo.name}.files", 'r') as tar:
                    tar.extractall(lib.TMP_DIR)
            except FileNotFoundError:
                raise FileNotFoundError(f"run update --files before !")

            for item in Path(lib.TMP_DIR).glob('*/files'):
                with open(item, 'r') as f_files:
                    pkg = {'name':'', 'version':'', 'files':[], 'db':db_repo.name}
                    for line in f_files:
                        if regex.search(line):
                            pkg['files'].append(line.rstrip())
                    if pkg['files']:
                        with open(Path(item).parent / 'desc', 'r') as f_desc:
                            for line in f_desc:
                                line = line.rstrip()
                                if not line:
                                    continue
                                if line == '%NAME%':
                                    pkg['name'] = next(f_desc).strip()
                                if line == '%VERSION%':
                                    pkg['version'] = next(f_desc).strip()
                                    break
                        result.append(pkg)
                        title = f"{pkg['name']} {pkg['version']}"
                        print(f"{self.color(self.branch)}::{self.color('none')} {title:68} ({pkg['db']})")
                        for fpkg in pkg['files']:
                            print(f"\t{fpkg}")
                        if only_one:
                            subprocess.run(['rm', '-rf', lib.TMP_DIR])
                            os.mkdir(lib.TMP_DIR)
                            return result
            subprocess.run(['rm', '-rf', lib.TMP_DIR])
            os.mkdir(lib.TMP_DIR)
        return result

    @staticmethod
    def aur_query(pkgname: str) -> None:
        """get aur pkg infos"""

        url = f"https://aur.archlinux.org/rpc/?v=5&type=info&arg[]={pkgname}"
        with urllib.request.urlopen(url) as q_aur:
            req = q_aur.read().decode('utf-8')
        data = json.loads(req)
        if data['resultcount'] < 1 or data['type'] == "error":
            return None

        pkg = data['results'][0]
        print(f"{'AUR':15}: ------")
        print(f"{'Name':15}: {pkg['Name']}")
        print(f"{'Version':15}: {pkg['Version']}")
        print(f"{'Date ':15}: {datetime.fromtimestamp(pkg['LastModified']).strftime('%a %d %b %Y %X %Z')}")
        print(f"{'Url ':15}: {'https://aur.archlinux.org/packages/'+pkgname}") # or URLPath

    @staticmethod
    def aur_infos(pkgs, verbose: bool = False):
        """
        390 packages in aur 2019-04-05 , 4000 requests per day per IP.
        URI maximum length limit of 4443 bytes
        200 packages MAXI in multi info/search
        """
        # tests
        # 200 pkgs => 4100 len url !
        # 150 => 3200 :)
        # 140 => 3000
        MAX_PKGS = 140
        URL = f"https://aur.archlinux.org/rpc/?v=5&type=info"

        if not pkgs:
            return {}

        result = {}
        urls = []   # only MAX_PKGS by url
        url = URL
        i = 0
        for i, pkg in enumerate(pkgs):
            if i % MAX_PKGS == 0:
                urls.append(url)
                url = URL
            url += f"&arg[]={pkg}"
        if url != URL:
            urls.append(url)
            #if verbose:
            #    print("url", url)

        if len(urls) > 2:
            print(len(urls)-1, "request(s) aur")
        for url in urls:
            if url == URL:
                continue
            if len(url) > 4440:
                raise IndexError(f"URI maximum length limit of 4443 bytes : here {len(url)}")

            with urllib.request.urlopen(url) as q_aur:
                req = q_aur.read().decode('utf-8')
            data = json.loads(req)
            if data['resultcount'] < 1 or data['type'] == "error":
                print("\n\nAUR ERROR !!!\n", data, "\nURL:", url)

            for pkg in data['results']:
                result[pkg['Name']] = pkg['Version']

        #print(f"\nAUR packages ({len(result)}):\n", result)
        return result
