"""This Module handles the identitydb object"""
from os import path, listdir
from datetime import datetime
from pem import parse_file
from sqlalchemy.orm import sessionmaker
from tqdm import tqdm
from libpkpass.crypto import (
    pk_verify_chain,
    get_cert_fingerprint,
    get_cert_subject,
    get_cert_issuer,
    get_cert_enddate,
    get_cert_issuerhash,
    get_cert_subjecthash,
)
from libpkpass.errors import FileOpenError, CliArgumentError
from libpkpass.models.recipient import Recipient
from libpkpass.models.cert import Cert
from libpkpass.util import create_or_update


class IdentityDB:
    ##########################################################################
    """User database class.  Contains information about the identities of and
    things pertinent to recipients and their groups and keys."""
    ##########################################################################

    def __init__(self):
        self.extensions = {"certificate": [".cert", ".crt"], "key": ".key"}
        self.cabundle = ""
        self.args = {}
        self.session = None
        self.id = None

    def __repr__(self):
        return f"{self.__class__}({self.__dict__})"

    def __str__(self):
        return f"{self.__dict__}"

    def _load_certs_from_external(self, connection_map, usedb=True):
        #######################################################################
        """Load certificates from external via use of plugin"""
        #######################################################################
        if "base_directory" in connection_map and connection_map["base_directory"]:
            del connection_map["base_directory"]
        for key, value in connection_map.items():
            connector = getattr(__import__(key.lower(), fromlist=[key]), key)(value)
            if usedb:
                with open(self.args["db"]["path"], "wb") as db_path:
                    db_path.write(connector.get_db())
            else:
                certs = connector.list_certificates()
                print("Loading certs into database")
                for name, certlist in tqdm(certs.items()):
                    self.load_db(name, certlist)
            break

    def _load_from_directory(self, fpath, filetype):
        #######################################################################
        """Helper function to read in (keys|certs) and store them correctly"""
        #######################################################################
        try:
            print("Loading certs into database")
            for fname in tqdm(listdir(fpath)):
                if fname.endswith(tuple(self.extensions[filetype])):
                    uid = fname.split(".")[0]
                    filepath = path.join(fpath, fname)
                    if filetype == "certificate":
                        self.load_db(
                            uid, certlist=[x.as_text() for x in parse_file(filepath)]
                        )
                    elif filetype == "key":
                        session = sessionmaker(bind=self.args["db"]["engine"])()
                        create_or_update(
                            session,
                            Recipient,
                            unique_identifiers=["name"],
                            **{
                                "name": uid,
                                "key": filepath,
                            },
                        )
                        session.commit()
        except OSError as error:
            raise FileOpenError(fpath, str(error.strerror)) from error

    def load_certs_from_directory(
        self, certpath, connectmap=None, nocache=False, usedb=True
    ):
        #######################################################################
        """Read in all x509 certificates from directory and name them as found"""
        #######################################################################
        session = sessionmaker(bind=self.args["db"]["engine"])()
        if not session.query(Recipient).first() or nocache:
            if connectmap:
                self._load_certs_from_external(connectmap, usedb=usedb)
            if certpath:
                self._load_from_directory(certpath, "certificate")

    def load_db(self, identity, certlist=None):
        #######################################################################
        """Read in all rsa keys from directory and name them as found"""
        #######################################################################
        try:
            session = sessionmaker(bind=self.args["db"]["engine"])()
            recipient = create_or_update(
                session, Recipient, dont_update=["key"], **{"name": identity}
            )
            for cert in certlist:
                cert = cert.encode()
                cert_dict = {}
                cert_dict["cert_bytes"] = cert
                cert_dict["verified"] = pk_verify_chain(cert, self.cabundle)
                cert_dict["fingerprint"] = get_cert_fingerprint(cert)
                cert_dict["subject"] = get_cert_subject(cert)
                cert_dict["issuer"] = get_cert_issuer(cert)
                cert_dict["enddate"] = datetime.strptime(
                    get_cert_enddate(cert), "%b %d %H:%M:%S %Y %Z"
                )
                cert_dict["issuerhash"] = get_cert_issuerhash(cert)
                cert_dict["subjecthash"] = get_cert_subjecthash(cert)
                cert = create_or_update(
                    session, Cert, unique_identifiers=["fingerprint"], **cert_dict
                )
                if cert not in recipient.certs:
                    recipient.certs.append(cert)
            session.commit()
        except KeyError as err:
            raise CliArgumentError(
                f"Error: Recipient '{identity}' is not in the recipient database"
            ) from err

    def verify_identity(self, identity):
        session = sessionmaker(bind=self.args["db"]["engine"])()
        recipient = session.query(Recipient).filter(Recipient.name == identity).first()
        if not recipient:
            return False
        for cert in (
            session.query(Cert).filter(Cert.recipients.contains(recipient)).all()
        ):
            if not cert.verified:
                return False
        return True

    def load_keys_from_directory(self, fpath):
        #######################################################################
        """Read in all rsa keys from directory and name them as found"""
        #######################################################################
        if path.isdir(fpath):
            self._load_from_directory(fpath, "key")
        # We used to print a warning on else here, but it seems unnecessary;
        # if a user is using PIVs/Smartcards, this warning could just annoy
        # them needlessly
