# -*- encoding: utf-8 -*-
"""
KERI
keri.app.habbing module

"""
import json
from contextlib import contextmanager
from math import ceil
from urllib.parse import urlsplit

from hio.base import doing
from hio.help import hicting

from keri.peer import exchanging
from . import keeping, configing
from .. import help
from .. import kering
from ..core import coring, eventing, parsing, routing, serdering
from ..db import dbing, basing
from ..kering import MissingSignatureError, Roles

logger = help.ogler.getLogger()

@contextmanager
def openHby(*, name="test", base="", temp=True, salt=None, **kwa):
    """
    Context manager wrapper for Habery instance.
    Context 'with' statements call .close on exit of 'with' block

    Parameters:
        name (str): name of habery and shared db and file path
        base (str): optional if "" path component of shared db and files.
        temp (bool): True means use temporary or limited resources testing.
            Store .ks, .db, and .cf in /tmp
            Use quick method to stretch salts for seeds such as
                bran salt to seed or key creation of Habs.
                Otherwise use more resources set by tier to stretch
        salt (str): qb64 salt for creating key pairs

    Parameters: Passed through via kwa
        ks (Keeper):  keystore lmdb subclass instance
        db (Baser): database lmdb subclass instance
        cf (Configer): config file instance
        seed (str): qb64 private-signing key (seed) for the aeid from which
            the private decryption key may be derived. If aeid stored in
            database is not empty then seed may required to do any key
            management operations. The seed value is memory only and MUST NOT
            be persisted to the database for the manager with which it is used.
            It MUST only be loaded once when the process that runs the Manager
            is initialized. Its presence acts as an authentication, authorization,
            and decryption secret for the Manager and must be stored on
            another device from the device that runs the Manager.
        aeid (str): qb64 of non-transferable identifier prefix for
            authentication and encryption of secrets in keeper. If provided
            aeid (not None) and different from aeid stored in database then
            all secrets are re-encrypted using new aeid. In this case the
            provided prikey must not be empty. A change in aeid should require
            a second authentication mechanism besides the prikey.
        bran (str): Base64 22 char string that is used as base material for
            seed. bran allows alphanumeric passcodes generated by key managers
            like 1password to be key store for seed.
        pidx (int): Initial prefix index for vacuous ks
        algo (str): algorithm (randy or salty) for creating key pairs
            default is root algo which defaults to salty
        tier (str): security tier for generating keys from salt (Tierage)
        free (boo): free resources by closing on Doer exit if any

    """
    habery = None
    salt = salt if not None else coring.Salter(raw=b'0123456789abcdef').qb64
    try:
        habery = Habery(name=name, base=base, temp=temp, salt=salt, **kwa)
        yield habery

    finally:
        if habery:
            habery.close(clear=habery.temp)


@contextmanager
def openHab(name="test", base="", salt=b'0123456789abcdef', temp=True, cf=None, **kwa):
    """
    Context manager wrapper for Hab instance.
    Defaults to temporary resources
    Context 'with' statements call .close on exit of 'with' block

    Parameters:
        name(str): name of habitat to create
        base(str): the name used for shared resources i.e. Baser and Keeper The habitat specific config file will be
        in base/name
        salt(bytes): passed to habitat to use for inception raw salt not qb64
        temp(bool): indicates if this uses temporary databases
        cf(Configer): optional configer for loading configuration data

    """

    salt = coring.Salter(raw=salt).qb64

    with openHby(name=name, base=base, salt=salt, temp=temp, cf=cf) as hby:
        if (hab := hby.habByName(name)) is None:
            hab = hby.makeHab(name=name, icount=1, isith='1', ncount=1, nsith='1', **kwa)

        yield hby, hab


class Habery:
    """Habery class provides shared database environments for all its Habitats
    Key controller and identifier controller shared configuration file, keystore
    and KEL databases.


    Attributes:
        name (str): name of associated databases
        base (str): optional directory path segment inserted before name
                    that allows further hierarchical differentiation of databases.
                    "" means optional.
        temp (bool): True for testing:
            temporary storage of databases and config file
            weak resources for stretch of salty key

        ks (keeping.Keeper): lmdb key store
        db (basing.Baser): lmdb data base for KEL etc
        cf (configing.Configer): config file instance
        mgr (keeping.Manager): creates and rotates keys in key store
        rtr (routing.Router): routes reply 'rpy' messages
        rvy (routing.Revery): factory that processes reply 'rpy' messages
        kvy (eventing.Kevery): factory for local processing of local event msgs
        psr (parsing.Parser):  parses local messages for .kvy .rvy

        habs (dict): Hab instances keyed by prefix.
            To look up Hab by name get prefix from db.habs .prefix field using
            .habByName

        inited (bool): True means fully initialized wrt databases.
                          False means not yet fully initialized

    Properties:
        kevers (dict): of eventing.Kever(s) keyed by qb64 prefix
        prefixes (OrderedSet): local prefixes for .db

    """

    def __init__(self, *, name='test', base="", temp=False,
                 ks=None, db=None, cf=None, clear=False, headDirPath=None, **kwa):
        """
        Initialize instance.

        Parameters:
            name (str): alias name for shared environment config databases etc.
            base (str): optional directory path segment inserted before name
                that allows further differentiation with a hierarchy. "" means
                optional.
            temp (bool): True means use temporary or limited resources testing.
                Store .ks, .db, and .cf in /tmp
                Use quick method to stretch salts for seeds such as
                    bran salt to seed or key creation of Habs.
                    Otherwise use more resources set by tier to stretch
            ks (Keeper):  keystore lmdb subclass instance
            db (Baser): database lmdb subclass instance
            cf (Configer): config file instance
            clear (bool): True means remove resource directory upon close when
                            reopening
                          False means do not remove directory upon close when
                            reopening
            headDirPath (str): directory override


        Parameters: Passed through via kwa to setup for later init
            seed (str): qb64 private-signing key (seed) for the aeid from which
                the private decryption key may be derived. If aeid stored in
                database is not empty then seed may required to do any key
                management operations. The seed value is memory only and MUST NOT
                be persisted to the database for the manager with which it is used.
                It MUST only be loaded once when the process that runs the Manager
                is initialized. Its presence acts as an authentication, authorization,
                and decryption secret for the Manager and must be stored on
                another device from the device that runs the Manager.
            aeid (str): qb64 of non-transferable identifier prefix for
                authentication and encryption of secrets in keeper. If provided
                aeid (not None) and different from aeid stored in database then
                all secrets Haberyare re-encrypted using new aeid. In this case the
                provided prikey must not be empty. A change in aeid should require
                a second authentication mechanism besides the prikey.
            bran (str): Base64 22 char string that is used as base material for
                seed. bran allows alphanumeric passcodes generated by key managers
                like 1password to be key store for seed.
            pidx (int): Initial prefix index for vacuous ks
            algo (str): algorithm (randy or salty) for creating key pairs
                default is root algo which defaults to salty
            salt (str): qb64 salt for creating key pairs
            tier (str): security tier for generating keys from salt (Tierage)
            free (boo): free resources by closing on Doer exit if any
            temp (bool): See above
        """
        self.name = name
        self.base = base
        self.temp = temp

        self.ks = ks if ks is not None else keeping.Keeper(name=self.name,
                                                           base=self.base,
                                                           temp=self.temp,
                                                           reopen=True,
                                                           clear=clear,
                                                           headDirPath=headDirPath)
        self.db = db if db is not None else basing.Baser(name=self.name,
                                                         base=self.base,
                                                         temp=self.temp,
                                                         reopen=True,
                                                         clear=clear,
                                                         headDirPath=headDirPath)
        self.cf = cf if cf is not None else configing.Configer(name=self.name,
                                                               base=self.base,
                                                               temp=self.temp,
                                                               reopen=True,
                                                               clear=clear)

        self.mgr = None  # wait to setup until after ks is known to be opened
        self.rtr = routing.Router()
        self.rvy = routing.Revery(db=self.db, rtr=self.rtr)
        self.exc = exchanging.Exchanger(hby=self, handlers=[])
        self.kvy = eventing.Kevery(db=self.db, lax=False, local=True, rvy=self.rvy)
        self.kvy.registerReplyRoutes(router=self.rtr)
        self.psr = parsing.Parser(framed=True, kvy=self.kvy, rvy=self.rvy, exc=self.exc)
        self.habs = {}  # empty .habs
        self.namespaces = {}  # empty .namespaces
        self._signator = None
        self.inited = False

        # save init kwy word arg parameters as ._inits in order to later finish
        # init setup elseqhere after databases are opened if not below
        self._inits = kwa
        self._inits['temp'] = temp  # add temp for seed from bran tier override

        if self.db.opened and self.ks.opened:
            self.setup(**self._inits)  # finish setup later

    def setup(self, *, seed=None, aeid=None, bran=None, pidx=None, algo=None,
              salt=None, tier=None, free=False, temp=None, ):
        """
        Setup Habery. Assumes that both .db and .ks have been opened.
        This allows dependency injection of .db and .ks into Habery instance
        prior to .db and .kx being opened to accomodate asynchronous process
        setup of these resources. Putting the .db and .ks associated
        initialization here enables asynchronous opening .db and .ks after
        Baser and Keeper instances are instantiated. First call to .setup will
        initialize databases (vacuous initialization).

        Parameters:
            seed (str): qb64 private-signing key (seed) for the aeid from which
                the private decryption key may be derived. If aeid stored in
                database is not empty then seed may required to do any key
                management operations. The seed value is memory only and MUST NOT
                be persisted to the database for the manager with which it is used.
                It MUST only be loaded once when the process that runs the Manager
                is initialized. Its presence acts as an authentication, authorization,
                and decryption secret for the Manager and must be stored on
                another device from the device that runs the Manager.
            aeid (str): qb64 of non-transferable identifier prefix for
                authentication and encryption of secrets in keeper. If provided
                aeid (not None) and different from aeid stored in database then
                all secrets are re-encrypted using new aeid. In this case the
                provided prikey must not be empty. A change in aeid should require
                a second authentication mechanism besides the prikey.
            bran (str): Base64 21 char string that is used as base material for
                seed. bran allows alphanumeric passcodes generated by key managers
                like 1password to be  Okey store for seed.
            pidx (int): Initial prefix index for vacuous ks
            algo (str): algorithm (randy or salty) for creating key pairs
                default is root algo which defaults to salty
            salt (str): qb64 salt for creating key pairs
            tier (str): security tier for generating keys from salt (Tierage)
            free (boo): free resources by closing on Doer exit if any
            temp (bool): True means use shortcuts for testing.
                    Use quick method to stretch salts for seeds such as
                    bran salt to seed or key creation of Habs.
                    Otherwise use more resources set by tier to stretch
        """
        if not (self.ks.opened and self.db.opened):
            raise kering.ClosedError("Attempt to setup Habitat with closed "
                                     "database, .ks or .db.")
        self.free = True if free else False

        if bran and not seed:  # create seed from stretch of bran as salt
            if len(bran) < 21:
                raise ValueError(f"Bran (passcode seed material) too short.")
            bran = coring.MtrDex.Salt_128 + 'A' + bran[:21]  # qb64 salt for seed
            signer = coring.Salter(qb64=bran).signer(transferable=False,
                                                     tier=tier,
                                                     temp=temp)
            seed = signer.qb64
            if not aeid:  # aeid must not be empty event on initial creation
                aeid = signer.verfer.qb64  # lest it remove encryption

        if salt is None:  # salt for signing keys not aeid seed
            salt = coring.Salter(raw=b'0123456789abcdef').qb64
        else:
            salt = coring.Salter(qb64=salt).qb64

        try:
            self.mgr = keeping.Manager(ks=self.ks, seed=seed, aeid=aeid, pidx=pidx,
                                       algo=algo, salt=salt, tier=tier)
        except kering.AuthError as ex:
            self.close()
            raise ex

        self._signator = Signator(db=self.db, mgr=self.mgr, temp=self.temp, ks=self.ks, cf=self.cf,
                                  rtr=self.rtr, kvy=self.kvy, psr=self.psr, rvy=self.rvy)

        self.loadHabs()
        self.inited = True

    def loadHabs(self):
        """Load Habs instance from db

        .db.reopen calls .db.reload which loads .db.kevers from key state in
        .db.states and loads  associated .db.prefixes.
        It also removes any bare .habs without key state
        Thus by now know that .habs are valid so can create Hab instances

        """
        self.reconfigure()  # pre hab load reconfiguration

        groups = []
        for name, habord in self.db.habs.getItemIter():
            name = ".".join(name)  # detupleize the database key name
            pre = habord.hid

            # create Hab instance and inject dependencies
            if habord.mid and not habord.sid:
                hab = GroupHab(ks=self.ks, db=self.db, cf=self.cf, mgr=self.mgr,
                               rtr=self.rtr, rvy=self.rvy, kvy=self.kvy, psr=self.psr,
                               name=name, pre=pre, temp=self.temp, smids=habord.smids, rmids=habord.rmids)
                groups.append(habord)
            elif habord.sid and not habord.mid:
                hab = SignifyHab(ks=self.ks, db=self.db, cf=self.cf, mgr=self.mgr,
                                 rtr=self.rtr, rvy=self.rvy, kvy=self.kvy, psr=self.psr,
                                 name=name, pre=habord.sid)
            elif habord.sid and habord.mid:
                hab = SignifyGroupHab(ks=self.ks, db=self.db, cf=self.cf, mgr=self.mgr,
                                      rtr=self.rtr, rvy=self.rvy, kvy=self.kvy, psr=self.psr,
                                      name=name, pre=pre)
                groups.append(habord)
            else:
                hab = Hab(ks=self.ks, db=self.db, cf=self.cf, mgr=self.mgr,
                          rtr=self.rtr, rvy=self.rvy, kvy=self.kvy, psr=self.psr,
                          name=name, pre=pre, temp=self.temp)

            # Rules for acceptance
            #  if its delegated its accepted into its own local KEL even if the
            #    delegator has not sealed it
            if not hab.accepted and not habord.mid:
                raise kering.ConfigurationError(f"Problem loading Hab pre="
                                                f"{pre} name={name} from db.")

            # read in config file and process any oobis or endpoints for hab
            hab.inited = True
            self.habs[hab.pre] = hab

        for keys, habord in self.db.nmsp.getItemIter():
            ns = keys[0]
            name = ".".join(keys[1:])  # detupleize the database key name
            pre = habord.hid

            # create Hab instance and inject dependencies
            if habord.mid and not habord.sid:
                hab = GroupHab(ks=self.ks, db=self.db, cf=self.cf, mgr=self.mgr,
                               rtr=self.rtr, rvy=self.rvy, kvy=self.kvy, psr=self.psr,
                               name=name, ns=ns, pre=pre, temp=self.temp, smids=habord.smids)
                groups.append(habord)
            elif habord.sid and not habord.mid:
                hab = SignifyHab(ks=self.ks, db=self.db, cf=self.cf, mgr=self.mgr,
                                 rtr=self.rtr, rvy=self.rvy, kvy=self.kvy, psr=self.psr,
                                 name=name, ns=ns, pre=habord.sid)
            elif habord.sid and habord.mid:
                hab = SignifyGroupHab(ks=self.ks, db=self.db, cf=self.cf, mgr=self.mgr,
                                      rtr=self.rtr, rvy=self.rvy, kvy=self.kvy, psr=self.psr,
                                      name=name, pre=habord.sid)
                groups.append(habord)
            else:
                hab = Hab(ks=self.ks, db=self.db, cf=self.cf, mgr=self.mgr,
                          rtr=self.rtr, rvy=self.rvy, kvy=self.kvy, psr=self.psr,
                          name=name, ns=ns, pre=pre, temp=self.temp)

            # Rules for acceptance
            #  if its delegated its accepted into its own local KEL even if the
            #    delegator has not sealed it
            if not hab.accepted and not habord.mid:
                raise kering.ConfigurationError(f"Problem loading Hab pre="
                                                f"{pre} name={name} from db.")

            # read in config file and process any oobis or endpoints for hab
            hab.inited = True
            if ns not in self.namespaces:
                self.namespaces[ns] = dict()
            self.namespaces[ns][hab.pre] = hab

        # Populate the participant hab after loading all habs
        for habord in groups:
            self.habs[habord.hid].mhab = self.habs[habord.mid]

        self.reconfigure()  # post hab load reconfiguration

    def makeHab(self, name, ns=None, cf=None, **kwa):
        """Make new Hab with name, pre is generated from **kwa

        Parameters: (Passthrough to hab.make)
            secrecies (list): of list of secrets to preload key pairs if any
            iridx (int): initial rotation index after ingestion of secrecies
            code (str): prefix derivation code
            transferable (bool): True means pre is transferable (default)
                    False means pre is nontransferable
            isith (Union[int, str, list]): incepting signing threshold as int, str hex, or list
            icount (int): incepting key count for number of keys
            nsith (Union[int, str, list]): next signing threshold as int, str hex or list
            ncount (int): next key count for number of next keys
            toad (Union[int,str]): int or str hex of witness threshold
            wits (list): of qb64 prefixes of witnesses
            delpre (str): qb64 of delegator identifier prefix
            estOnly (str): eventing.TraitCodex.EstOnly means only establishment
                events allowed in KEL for this Hab
            data (list | None): seal dicts
        """
        if ns is not None and "." in ns:
            raise kering.ConfigurationError("Hab namespace names are not allowed to contain the '.' character")

        cf = cf if cf is not None else self.cf
        hab = Hab(ks=self.ks, db=self.db, cf=cf, mgr=self.mgr,
                  rtr=self.rtr, rvy=self.rvy, kvy=self.kvy, psr=self.psr,
                  name=name, ns=ns, temp=self.temp)

        hab.make(**kwa)

        if ns is None:
            self.habs[hab.pre] = hab
        else:
            if ns not in self.namespaces:
                self.namespaces[ns] = dict()
            self.namespaces[ns][hab.pre] = hab

        return hab

    def makeGroupHab(self, group, mhab, smids, rmids=None, ns=None, **kwa):
        """Make new Group Hab using group has group hab name, with lhab as local
        participant.

        Parameters: (non-pass-through):
            group (str): human readable alias for group identifier
            mhab (Hab): group member (local) hab
            smids (list): group member signing ids (qb64) from which to extract
                        inception event current signing keys
            rmids (list | None): group member rotation ids (qb64) from which to extract
                        inception event next key digests
                        if rmids is None then use assign smids to rmids
                        if rmids is empty then no next key digests
                        which means group identifier is no longer transferable.


        Parameters: (**kwa pass-through to hab.make)
            secrecies (list): of list of secrets to preload key pairs if any
            iridx (int): initial rotation index after ingestion of secrecies
            code (str): prefix derivation code
            transferable (bool): True means pre is transferable (default)
                    False means pre is nontransferable
            isith (Union[int, str, list]): incepting signing threshold as int, str hex, or list
            icount (int): incepting key count for number of keys
            nsith (Union[int, str, list]): next signing threshold as int, str hex or list
            ncount (int): next key count for number of next keys
            toad (Union[int,str]): int or str hex of witness threshold
            wits (list): of qb64 prefixes of witnesses
            delpre (str): qb64 of delegator identifier prefix
            estOnly (str): eventing.TraitCodex.EstOnly means only establishment
                events allowed in KEL for this Hab
            DnD (bool): eventing.TraitCodex.DnD means do allow delegated identifiers from this identifier

        ToDo: NRR
        add midxs tuples for each group member or all in group multisig.

        """

        if mhab.pre not in smids and mhab.pre not in rmids:
            raise kering.ConfigurationError(f"Local member identifier "
                                            f"{mhab.pre} must be member of "
                                            f"smids ={smids} and/or "
                                            f"rmids={rmids}.")

        for mid in smids:
            if mid not in self.kevers:
                raise kering.ConfigurationError(f"KEL missing for signing member "
                                                f"identifier {mid} from group's "
                                                f"current members ={smids}")

        if rmids is not None:
            for rmid in rmids:
                if rmid not in self.kevers:
                    raise kering.ConfigurationError(f"KEL missing for next member "
                                                    f"identifier {rmid} in group's"
                                                    f" next members ={rmids}")

        # multisig group verfers of current signing keys and digers of next key digests
        merfers, migers = self.extractMerfersMigers(smids, rmids)  # group verfers and digers
        kwa["merfers"] = merfers
        kwa["migers"] = migers

        # create group Hab in this Habery
        hab = GroupHab(ks=self.ks, db=self.db, cf=self.cf, mgr=self.mgr,
                       rtr=self.rtr, rvy=self.rvy, kvy=self.kvy, psr=self.psr,
                       name=group, ns=ns, mhab=mhab, smids=smids, rmids=rmids, temp=self.temp)

        hab.make(**kwa)  # finish making group hab with injected pass throughs
        if ns is None:
            self.habs[hab.pre] = hab
        else:
            if ns not in self.namespaces:
                self.namespaces[ns] = dict()
            self.namespaces[ns][hab.pre] = hab

        return hab

    def joinGroupHab(self, pre, group, mhab, smids, rmids=None, ns=None):
        """Make new Group Hab using group has group hab name, with lhab as local
        participant.

        Parameters: (non-pass-through):
            pre (str): qb64 identifier prefix of group
            group (str): human readable alias for group identifier
            mhab (Hab): group member (local) hab
            smids (list): group member signing ids (qb64) from which to extract
                        inception event current signing keys
            rmids (list | None): group member rotation ids (qb64) from which to extract
                        inception event next key digests
                        if rmids is None then use assign smids to rmids
                        if rmids is empty then no next key digests
                        which means group identifier is no longer transferable.


        """

        if mhab.pre not in smids and mhab.pre not in rmids:
            raise kering.ConfigurationError(f"Local member identifier "
                                            f"{mhab.pre} must be member of "
                                            f"smids ={smids} and/or "
                                            f"rmids={rmids}.")

        for mid in smids:
            if mid not in self.kevers:
                raise kering.ConfigurationError(f"KEL missing for signing member "
                                                f"identifier {mid} from group's "
                                                f"current members ={smids}")

        if rmids is not None:
            for rmid in rmids:
                if rmid not in self.kevers:
                    raise kering.ConfigurationError(f"KEL missing for next member "
                                                    f"identifier {rmid} in group's"
                                                    f" next members ={rmids}")

        # create group Hab in this Habery
        hab = GroupHab(ks=self.ks, db=self.db, cf=self.cf, mgr=self.mgr,
                       rtr=self.rtr, rvy=self.rvy, kvy=self.kvy, psr=self.psr,
                       name=group, ns=ns, mhab=mhab, smids=smids, rmids=rmids, temp=self.temp)

        hab.pre = pre
        habord = basing.HabitatRecord(hid=hab.pre,
                                      mid=mhab.pre,
                                      smids=smids,
                                      rmids=rmids)

        hab.save(habord)
        hab.prefixes.add(pre)
        hab.inited = True

        if ns is None:
            self.habs[hab.pre] = hab
        else:
            if ns not in self.namespaces:
                self.namespaces[ns] = dict()
            self.namespaces[ns][hab.pre] = hab

        return hab

    def makeSignifyHab(self, name, ns=None, **kwa):
        # create group Hab in this Habery
        hab = SignifyHab(ks=self.ks, db=self.db, cf=self.cf, mgr=self.mgr,
                         rtr=self.rtr, rvy=self.rvy, kvy=self.kvy, psr=self.psr,
                         name=name, ns=ns, temp=self.temp)

        hab.make(**kwa)  # finish making group hab with injected pass throughs
        if ns is None:
            self.habs[hab.pre] = hab
        else:
            if ns not in self.namespaces:
                self.namespaces[ns] = dict()
            self.namespaces[ns][hab.pre] = hab

        return hab

    def makeSignifyGroupHab(self, name, mhab, ns=None, **kwa):
        # create group Hab in this Habery
        hab = SignifyGroupHab(ks=self.ks, db=self.db, cf=self.cf, mgr=self.mgr,
                              rtr=self.rtr, rvy=self.rvy, kvy=self.kvy, psr=self.psr,
                              name=name, mhab=mhab, ns=ns, temp=self.temp)

        hab.make(**kwa)  # finish making group hab with injected pass throughs
        if ns is None:
            self.habs[hab.pre] = hab
        else:
            if ns not in self.namespaces:
                self.namespaces[ns] = dict()
            self.namespaces[ns][hab.pre] = hab

        return hab

    def joinSignifyGroupHab(self, pre, name, mhab, smids, rmids=None, ns=None):
        """Make new Group Hab using group has group hab name, with lhab as local
        participant.

        Parameters: (non-pass-through):
            pre (str): qb64 identifier prefix of group
            name (str): human readable alias for group identifier
            mhab (Hab): group member (local) hab
            smids (list): group member signing ids (qb64) from which to extract
                        inception event current signing keys
            rmids (list | None): group member rotation ids (qb64) from which to extract
                        inception event next key digests
                        if rmids is None then use assign smids to rmids
                        if rmids is empty then no next key digests
                        which means group identifier is no longer transferable.


        """

        if mhab.pre not in smids and mhab.pre not in rmids:
            raise kering.ConfigurationError(f"Local member identifier "
                                            f"{mhab.pre} must be member of "
                                            f"smids ={smids} and/or "
                                            f"rmids={rmids}.")

        for mid in smids:
            if mid not in self.kevers:
                raise kering.ConfigurationError(f"KEL missing for signing member "
                                                f"identifier {mid} from group's "
                                                f"current members ={smids}")

        if rmids is not None:
            for rmid in rmids:
                if rmid not in self.kevers:
                    raise kering.ConfigurationError(f"KEL missing for next member "
                                                    f"identifier {rmid} in group's"
                                                    f" next members ={rmids}")

        # create group Hab in this Habery
        hab = SignifyGroupHab(ks=self.ks, db=self.db, cf=self.cf, mgr=self.mgr,
                              rtr=self.rtr, rvy=self.rvy, kvy=self.kvy, psr=self.psr,
                              name=name, mhab=mhab, ns=ns, temp=self.temp)

        hab.pre = pre
        habord = basing.HabitatRecord(hid=hab.pre,
                                      sid=mhab.pre,
                                      smids=smids,
                                      rmids=rmids)

        hab.save(habord)
        hab.prefixes.add(pre)
        hab.inited = True

        if ns is None:
            self.habs[hab.pre] = hab
        else:
            if ns not in self.namespaces:
                self.namespaces[ns] = dict()
            self.namespaces[ns][hab.pre] = hab

        return hab

    def deleteHab(self, name):
        hab = self.habByName(name)
        if not hab:
            return False

        if not self.db.habs.rem(keys=(name,)):
            return False

        del self.habs[hab.pre]
        self.db.prefixes.remove(hab.pre)

        return True

    def extractMerfersMigers(self, smids, rmids=None):
        """
        Extract the public key verfer and next digest diger from the current
        est event of all the members of the multisig group. Assumes that the KEL
        for each member is already in .kevers

        Parameters:
            smids (list): group signing member ids qb64 in group multisig
            rmids (list): group rotating member ids qb64 in group multisig

        """
        if rmids is None:  # default the same for both lists
            rmids = list(smids)

        merfers = []  # multisig group signing key verfers
        migers = []  # multisig group next key digest digers

        for mid in smids:
            kever = self.kevers[mid]
            verfers = kever.verfers
            merfers.append(verfers[0])  # assumes always verfers
            if len(verfers) > 1:
                raise kering.ConfigurationError("Identifier must have only one key, {} has {}"
                                                .format(mid, len(verfers)))

        for mid in rmids:
            kever = self.kevers[mid]
            digers = kever.ndigers
            if digers:  # abandoned id  may have empty next digers
                migers.append(digers[0])
            if len(digers) > 1:
                raise kering.ConfigurationError("Identifier must have only one next key commitment, {} has {}"
                                                .format(mid, len(digers)))

        return merfers, migers

    def close(self, clear=False):
        """Close resources.
        Parameters:
           clear is boolean, True means clear resource directories
        """
        if self.ks:
            self.ks.close(clear=self.ks.temp or clear)

        if self.db:
            self.db.close(clear=self.db.temp or clear)

        if self.cf:
            self.cf.close(clear=self.cf.temp)

    @property
    def kevers(self):
        """
        Returns .db.kevers of all Kevers
        """
        return self.db.kevers

    @property
    def prefixes(self):
        """
        Returns .db.prefixes of local prefixes
        """
        return self.db.prefixes

    def habByPre(self, pre):
        """
        Returns the Hab from and namespace including the default namespace.

        Args:
            pre (str): qb64 aid of hab to find

        Returns:
            Hab: Hab instance for the aid pre or None
        """
        hab = None
        if pre in self.habs:
            hab = self.habs[pre]
        else:
            for nsp in self.namespaces.values():
                if pre in nsp:
                    hab = nsp[pre]

        return hab

    def habByName(self, name, ns=None):
        """
        Returns:
            hab (Hab): instance from .habs by name if any otherwise None

        Parameters:
           name (str): alias of Hab
           ns (str): optional namespace of hab

        """
        if ns is not None:
            if (habord := self.db.nmsp.get(keys=(ns, name))) is not None:
                habs = self.namespaces[ns]
                return habs[habord.hid] if habord.hid in habs else None

        elif (habord := self.db.habs.get(name)) is not None:
            return self.habs[habord.hid] if habord.hid in self.habs else None

        return None

    def reconfigure(self):
        """Apply configuration from config file managed by .cf. to this Habery
        Process any oobis or endpoints

        conf
        {
          dt: "isodatetime",
          curls: ["tcp://localhost:5620/"],
          iurls: ["tcp://localhost:5621/?name=eve"],
        }

        Config file is meant to be read only at init not changed by app at
        run time. Any dynamic app changes must go in database not config file
        that way we don't have to worry about multiple writers of cf.
        Use config file to preload database not as a database. Config file may
        have named sections for Habery or individual Habs as needed.

        """
        conf = self.cf.get()
        if "dt" in conf:  # datetime of config file
            dt = help.fromIso8601(conf["dt"])  # raises error if not convert
            if "iurls" in conf:  # process OOBI URLs
                for oobi in conf["iurls"]:
                    obr = basing.OobiRecord(date=help.toIso8601(dt))
                    self.db.oobis.put(keys=(oobi,), val=obr)
            if "durls" in conf:  # process OOBI URLs
                for oobi in conf["durls"]:
                    obr = basing.OobiRecord(date=help.toIso8601(dt))
                    self.db.oobis.put(keys=(oobi,), val=obr)
            if "wurls" in conf:  # well known OOBI URLs for MFA
                for oobi in conf["wurls"]:
                    obr = basing.OobiRecord(date=help.toIso8601(dt))
                    self.db.woobi.put(keys=(oobi,), val=obr)

    @property
    def signator(self):
        """
        signator for signing and verifying data at rest for this Habery environment
        Assumes db initialized.

        Returns:
            Signator:  signer for data at rest
        """
        return self._signator


SIGNER = "__signatory__"


class Signator:
    """
    Signator will create one non-transferable identifier when it is first initialized
    and use that identifier to sign and verify any data it is passed.  This class can be used
    to maintain BADA data ensuring that it is signed at rest.

    """

    def __init__(self, db, name=SIGNER, **kwa):
        """
        Create a Signator by checking for a signing AID in the Habery database and creating one
        if it does not exist.

        Args:
            db (Baser):  Database environment for data signing

        """
        self.db = db
        spre = self.db.hbys.get(name)
        if not spre:
            self._hab = Hab(name=name, db=db, **kwa)
            self._hab.make(transferable=False, hidden=True)
            self.pre = self._hab.pre
            self.db.hbys.pin(name, self.pre)
        else:
            self.pre = spre
            self._hab = Hab(name=name, db=db, pre=self.pre, **kwa)

    def sign(self, ser):
        """ Sign the data in ser with the Signator's private key using the Manager

        Args:
            ser (bytes): Raw byte data to sign

        Returns:
            Cigar: signature object for non-transferable key

        """
        return self._hab.sign(ser, indexed=False)[0]

    def verify(self, ser, cigar):
        """

        Args:
            ser(bytes): Raw byte data to verify against signature
            cigar (Cigar): Single non-transferable signature to verify

        Returns:
            bool: True means valid signature against data provided

        """
        return self._hab.kever.verfers[0].verify(cigar.raw, ser)


class HaberyDoer(doing.Doer):
    """
    Basic Habery Doer  to initialize habery databases and config file.
    .cf, .ks, .db

    Inherited Attributes:
        .done is Boolean completion state:
            True means completed
            Otherwise incomplete. Incompletion maybe due to close or abort.

    Attributes:
        .habery is Habery subclass

    Inherited Properties:
        .tyme is float relative cycle time of associated Tymist .tyme obtained
            via injected .tymth function wrapper closure.
        .tymth is function wrapper closure returned by Tymist .tymeth() method.
            When .tymth is called it returns associated Tymist .tyme.
            .tymth provides injected dependency on Tymist tyme base.
        .tock is float, desired time in seconds between runs or until next run,
                 non negative, zero means run asap

    Properties:

    Methods:
        .wind  injects ._tymth dependency from associated Tymist to get its .tyme
        .__call__ makes instance callable
            Appears as generator function that returns generator
        .do is generator method that returns generator
        .enter is enter context action method
        .recur is recur context action method or generator method
        .exit is exit context method
        .close is close context method
        .abort is abort context method

    Hidden:
        ._tymth is injected function wrapper closure returned by .tymen() of
            associated Tymist instance that returns Tymist .tyme. when called.
        ._tock is hidden attribute for .tock property
    """

    def __init__(self, habery, **kwa):
        """
        Parameters:
           habery (Habery): instance
        """
        super(HaberyDoer, self).__init__(**kwa)
        self.habery = habery

    def enter(self):
        """ Enter context and set up Habery """
        if not self.habery.inited:
            self.habery.setup(**self.habery._inits)

    def exit(self):
        """Exit context and close Habery """
        if self.habery.inited and self.habery.free:
            self.habery.close(clear=self.habery.temp)


class BaseHab:
    """
    Hab class provides a given idetnifier controller's local resource environment
    i.e. hab or habitat. Includes dependency injection of database, keystore,
    configuration file as well as Kevery and key store Manager..

    Attributes: (Injected)
        ks (keeping.Keeper): lmdb key store
        db (basing.Baser): lmdb data base for KEL etc
        cf (configing.Configer): config file instance
        mgr (keeping.Manager): creates and rotates keys in key store
        rtr (routing.Router): routes reply 'rpy' messages
        rvy (routing.Revery): factory that processes reply 'rpy' messages
        kvy (eventing.Kevery): factory for local processing of local event msgs
        psr (parsing.Parser):  parses local messages for .kvy .rvy


     Attributes:
        name (str): alias of controller
        pre (str): qb64 prefix of own local controller or None if new
        temp (bool): True means testing:
                     use weak level when salty algo for stretching in key creation
                     for incept and rotate of keys for this hab.pre
        inited (bool): True means fully initialized wrt databases.
                          False means not yet fully initialized
        delpre (str | None): delegator prefix if any else None

    Properties:
        kever (Kever): instance of key state of local controller
        kevers (dict): of eventing.Kever instances from KELs in local db
            keyed by qb64 prefix. Read through cache of of kevers of states for
            KELs in db.states
        iserder (coring.Serder): own inception event
        prefixes (OrderedSet): local prefixes for .db
        accepted (bool): True means accepted into local KEL.
                          False otherwise

    """

    def __init__(self, ks, db, cf, mgr, rtr, rvy, kvy, psr, *,
                 name='test', ns=None, pre=None, temp=False):
        """
        Initialize instance.

        Injected Parameters:  (injected dependencies)
            ks (keeping.Keeper): lmdb key store
            db (basing.Baser): lmdb data base for KEL etc
            cf (configing.Configer): config file instance
            mgr (keeping.Manager): creates and rotates keys in key store
            rtr (routing.Router): routes reply 'rpy' messages
            rvy (routing.Revery): factory that processes reply 'rpy' messages
            kvy (eventing.Kevery): factory for local processing of local event msgs
            psr (parsing.Parser):  parses local messages for .kvy .rvy


        Parameters:
            name (str): alias name for local controller of habitat
            pre (str | None): qb64 identifier prefix of own local controller else None
            temp (bool): True means testing:
                use weak level when salty algo for stretching in key creation
                for incept and rotate of keys for this hab.pre

        """
        self.db = db  # injected
        self.ks = ks  # injected
        self.cf = cf  # injected
        self.mgr = mgr  # injected
        self.rtr = rtr  # injected
        self.rvy = rvy  # injected
        self.kvy = kvy  # injected
        self.psr = psr  # injected

        self.name = name
        self.ns = ns
        self.pre = pre  # wait to setup until after db is known to be opened
        self.temp = True if temp else False

        self.inited = False
        self.delpre = None  # assigned laster if delegated

    def make(self, DnD, code, data, delpre, estOnly, isith, verfers, nsith, digers, toad, wits):
        """
        Creates Serder of inception event for provided parameters.
        Assumes injected dependencies were already setup.

        Parameters:
            isith (int | str | list | None): incepting signing threshold as
                    int, str hex, or list weighted if any, otherwise compute
                    default from verfers
            code (str): prefix derivation code default Blake3
            nsith (int, str, list | None ): next signing threshold as int,
                str hex or list weighted if any, otherwise compute default from
                digers
            verfers (list[Verfer]): Verfer instances for initial signing keys
            digers  (list[Diger] | None) Diger instances for next key digests
            toad (int |str| None): int or str hex of witness threshold if
                specified else compute default based on number of wits (backers)
            wits (list | None): qb64 prefixes of witnesses if any
            delpre (str | None): qb64 of delegator identifier prefix if any
            estOnly (bool | None): True means add trait eventing.TraitCodex.EstOnly
                which means only establishment events allowed in KEL for this Hab
                False (default) means allows non-est events and no trait is added.
            DnD (bool): True means add trait of eventing.TraitCodex.DnD which
                    means do not allow delegated identifiers from this identifier
                    False (default) means do allow and no trait is added.

            data (list | None): seal dicts

        """
        icount = len(verfers)
        ncount = len(digers) if digers is not None else 0
        if isith is None:  # compute default
            isith = f"{max(1, ceil(icount / 2)):x}"
        if nsith is None:  # compute default
            nsith = f"{max(0, ceil(ncount / 2)):x}"
        cst = coring.Tholder(sith=isith).sith  # current signing threshold
        nst = coring.Tholder(sith=nsith).sith  # next signing threshold
        cnfg = []
        if estOnly:
            cnfg.append(eventing.TraitCodex.EstOnly)
        if DnD:
            cnfg.append(eventing.TraitCodex.DoNotDelegate)
        self.delpre = delpre
        keys = [verfer.qb64 for verfer in verfers]
        if self.delpre:
            serder = eventing.delcept(keys=keys,
                                      delpre=self.delpre,
                                      isith=cst,
                                      nsith=nst,
                                      ndigs=[diger.qb64 for diger in digers],
                                      toad=toad,
                                      wits=wits,
                                      cnfg=cnfg,
                                      code=code)
        else:
            serder = eventing.incept(keys=keys,
                                     isith=cst,
                                     nsith=nst,
                                     ndigs=[diger.qb64 for diger in digers],
                                     toad=toad,
                                     wits=wits,
                                     cnfg=cnfg,
                                     code=code,
                                     data=data)
        return serder

    def save(self, habord):
        if self.ns is None:
            self.db.habs.pin(keys=self.name,
                             val=habord)
        else:
            self.db.nmsp.put(keys=(self.ns, self.name),
                             val=habord)

    def reconfigure(self):
        """Apply configuration from config file managed by .cf. to this Hab.
        Assumes that .pre and signing keys have been setup in order to create
        own endpoint auth when provided in .cf.

        conf
        {
          dt: "isodatetime",
          curls: ["tcp://localhost:5620/"],
          iurls: ["tcp://localhost:5621/?name=eve"]
        }

        Config file is meant to be read only at init not changed by app at
        run time. Any dynamic app changes must go in database not config file
        that way we don't have to worry about multiple writers of cf.
        Use config file to preload database not as a database. Config file may
        have named sections for Habery or individual Habs as needed.
        """

        conf = self.cf.get()
        if self.name not in conf:
            return

        conf = conf[self.name]
        if "dt" in conf:  # datetime of config file
            dt = help.fromIso8601(conf["dt"])  # raises error if not convert
            msgs = bytearray()
            msgs.extend(self.makeEndRole(eid=self.pre,
                                         role=kering.Roles.controller,
                                         stamp=help.toIso8601(dt=dt)))
            if "curls" in conf:
                curls = conf["curls"]
                for url in curls:
                    splits = urlsplit(url)
                    scheme = (splits.scheme if splits.scheme in kering.Schemes
                              else kering.Schemes.http)
                    msgs.extend(self.makeLocScheme(url=url,
                                                   scheme=scheme,
                                                   stamp=help.toIso8601(dt=dt)))
            self.psr.parse(ims=msgs)

    @property
    def iserder(self):
        """
        Return serder of inception event
        """
        if (dig := self.db.getKeLast(eventing.snKey(pre=self.pre, sn=0))) is None:
            raise kering.ConfigurationError("Missing inception event in KEL for "
                                            "Habitat pre={}.".format(self.pre))
        if (raw := self.db.getEvt(eventing.dgKey(pre=self.pre, dig=bytes(dig)))) is None:
            raise kering.ConfigurationError("Missing inception event for "
                                            "Habitat pre={}.".format(self.pre))
        return serdering.SerderKERI(raw=bytes(raw))

    @property
    def kevers(self):
        """
        Returns .db.kevers
        """
        return self.db.kevers

    @property
    def accepted(self):
        return self.pre in self.kevers

    @property
    def kever(self):
        """
        Returns kever for its .pre
        """
        return self.kevers[self.pre] if self.accepted else None

    @property
    def prefixes(self):
        """
        Returns .db.prefixes
        """
        return self.db.prefixes

    def incept(self, **kwa):
        """Alias for .make """
        self.make(**kwa)

    def rotate(self, *, verfers=None, digers=None, isith=None, nsith=None, toad=None, cuts=None, adds=None,
               data=None):
        """
        Perform rotation operation. Register rotation in database.
        Returns: bytearrayrotation message with attached signatures.

        Parameters:
            verfers (list | None): Verfer instances of public keys qb64
            digers (list | None): Diger instances of public next key digests qb64
            isith (int |str | None): current signing threshold as int or str hex
                                     or list of str weights
                                     default is prior next sith
            nsith (int |str | None): next, next signing threshold as int
                                     or str hex or list of str weights
                                     default is based on isith when None
            toad (int | str | None): hex of witness threshold after cuts and adds
            cuts (list | None): of qb64 pre of witnesses to be removed from witness list
            adds (list | None): of qb64 pre of witnesses to be added to witness list
            data (list | None): of dicts of committed data such as seals


        """
        # recall that kever.pre == self.pre
        kever = self.kever  # before rotation kever is prior next

        if isith is None:
            isith = kever.ntholder.sith  # use prior next sith as default
        if nsith is None:
            nsith = isith  # use new current as default

        if isith is None:  # compute default from newly rotated verfers above
            isith = f"{max(1, ceil(len(verfers) / 2)):x}"
        if nsith is None:  # compute default from newly rotated digers above
            nsith = f"{max(0, ceil((len(digers) if digers is not None else 0) / 2)):x}"

        cst = coring.Tholder(sith=isith).sith  # current signing threshold
        nst = coring.Tholder(sith=nsith).sith  # next signing threshold

        keys = [verfer.qb64 for verfer in verfers]

        indices = []
        for idx, diger in enumerate(kever.ndigers):
            pdigs = [coring.Diger(ser=verfer.qb64b, code=diger.code).qb64 for verfer in verfers]
            if diger.qb64 in pdigs:
                indices.append(idx)

        if not kever.ntholder.satisfy(indices):
            raise kering.ValidationError("invalid rotation, new key set unable to satisfy prior next signing threshold")

        if kever.delegator is not None:  # delegator only shows up in delcept
            serder = eventing.deltate(pre=kever.prefixer.qb64,
                                      keys=keys,
                                      dig=kever.serder.said,
                                      sn=kever.sner.num + 1,
                                      isith=cst,
                                      nsith=nst,
                                      ndigs=[diger.qb64 for diger in digers],
                                      toad=toad,
                                      wits=kever.wits,
                                      cuts=cuts,
                                      adds=adds,
                                      data=data)
        else:
            serder = eventing.rotate(pre=kever.prefixer.qb64,
                                     keys=keys,
                                     dig=kever.serder.said,
                                     sn=kever.sner.num + 1,
                                     isith=cst,
                                     nsith=nst,
                                     ndigs=[diger.qb64 for diger in digers],
                                     toad=toad,
                                     wits=kever.wits,
                                     cuts=cuts,
                                     adds=adds,
                                     data=data)

        # sign handles group hab with .mhab case
        sigers = self.sign(ser=serder.raw, verfers=verfers, rotated=True)

        # update own key event verifier state
        msg = eventing.messagize(serder, sigers=sigers)

        try:
            self.kvy.processEvent(serder=serder, sigers=sigers)
        except MissingSignatureError:
            pass
        except Exception as ex:
            raise kering.ValidationError("Improper Habitat rotation for "
                                         "pre={self.pre}.") from ex

        return msg

    def interact(self, *, data=None):
        """
        Perform interaction operation. Register interaction in database.
        Returns: bytearray interaction message with attached signatures.
        """
        kever = self.kever
        serder = eventing.interact(pre=kever.prefixer.qb64,
                                   dig=kever.serder.said,
                                   sn=kever.sner.num + 1,
                                   data=data)

        sigers = self.sign(ser=serder.raw)

        msg = eventing.messagize(serder, sigers=sigers)
        try:
            # verify event, update kever state, and escrow if group
            self.kvy.processEvent(serder=serder, sigers=sigers)
        except MissingSignatureError:
            pass
        except Exception as ex:
            raise kering.ValidationError("Improper Habitat interaction for "
                                         "pre={}.".format(self.pre)) from ex

        return msg


    def sign(self, ser, verfers=None, indexed=True, indices=None, ondices=None, **kwa):
        """Sign given serialization ser using appropriate keys.
        Use provided verfers or .kever.verfers to lookup keys to sign.

        Parameters:
            ser (bytes): serialization to sign
            verfers (list[Verfer] | None): Verfer instances to get pub verifier
                keys to lookup private siging keys.
                verfers None means use .kever.verfers. Assumes that when group
                and verfers is not None then provided verfers must be .kever.verfers
            indexed (bool): When not mhab then
                True means use use indexed signatures and return
                list of Siger instances.
                False means do not use indexed signatures and return
                list of Cigar instances
            indices (list[int] | None): indices (offsets)
                when indexed == True. See Manager.sign
            ondices (list[int | None] | None): other indices (offsets)
                when indexed is True. See Manager.sign

        """
        if verfers is None:
            verfers = self.kever.verfers  # when group these provide group signing keys

        return self.mgr.sign(ser=ser,
                             verfers=verfers,
                             indexed=indexed,
                             indices=indices,
                             ondices=ondices)

    def decrypt(self, ser, verfers=None, **kwa):
        """Sign given serialization ser using appropriate keys.
        Use provided verfers or .kever.verfers to lookup keys to sign.

        Parameters:
            ser (bytes): serialization to sign
            verfers (list[Verfer] | None): Verfer instances to get pub verifier
                keys to lookup private siging keys.
                verfers None means use .kever.verfers. Assumes that when group
                and verfers is not None then provided verfers must be .kever.verfers

        """
        if verfers is None:
            verfers = self.kever.verfers  # when group these provide group signing keys

        return self.mgr.decrypt(ser=ser,
                                verfers=verfers,
                                )

    def query(self, pre, src, query=None, **kwa):
        """ Create, sign and return a `qry` message against the attester for the prefix

        Parameters:
            pre (str): qb64 identifier prefix being queried for
            src (str): qb64 identifier prefix of attester being queried
            query (dict): addition query modifiers to include in `q`
            **kwa (dict): keyword arguments passed to eventing.query

        Returns:
            bytearray: signed query event

        """

        query = query if query is not None else dict()
        query['i'] = pre
        query["src"] = src
        serder = eventing.query(query=query, **kwa)
        return self.endorse(serder, last=True)

    def endorse(self, serder, last=False, pipelined=True):
        """
        Returns msg with own endorsement of msg from serder with attached signature
        groups based on own pre transferable or non-transferable.

        Parameters:
            serder (Serder): instance of msg
            last (bool): True means use SealLast. False means use SealEvent
                         query messages use SealLast
            pipelined (bool): True means use pipelining attachment code

        Useful for endorsing message when provided via serder such as state,
        reply, query or similar.
        """
        if self.kever.prefixer.transferable:
            # create SealEvent or SealLast for endorser's est evt whose keys are
            # used to sign
            kever = self.kever

            if last:
                seal = eventing.SealLast(i=kever.prefixer.qb64)
            else:
                seal = eventing.SealEvent(i=kever.prefixer.qb64,
                                          s="{:x}".format(kever.lastEst.s),
                                          d=kever.lastEst.d)

            sigers = self.sign(ser=serder.raw,
                               indexed=True)

            msg = eventing.messagize(serder=serder,
                                     sigers=sigers,
                                     seal=seal,
                                     pipelined=pipelined)

        else:
            cigars = self.sign(ser=serder.raw,
                               indexed=False)
            msg = eventing.messagize(serder=serder,
                                     cigars=cigars,
                                     pipelined=pipelined)

        return msg

    def exchange(self, route,
                 payload,
                 recipient,
                 date=None,
                 eid=None,
                 dig=None,
                 modifiers=None,
                 embeds=None,
                 save=False):
        """
        Returns signed exn, message of serder with count code and receipt
        couples (pre+cig)
        Builds msg and then processes it into own db to validate
        """
        # sign serder event

        serder, end = exchanging.exchange(route=route,
                                          payload=payload,
                                          sender=self.pre,
                                          recipient=recipient,
                                          date=date,
                                          dig=dig,
                                          modifiers=modifiers,
                                          embeds=embeds)

        if self.kever.prefixer.transferable:
            msg = self.endorse(serder=serder, pipelined=False)
        else:
            cigars = self.sign(ser=serder.raw,
                               indexed=False)
            msg = eventing.messagize(serder, cigars=cigars)

        msg.extend(end)

        if save:
            self.psr.parseOne(ims=bytearray(msg))  # process local copy into db

        return msg

    def receipt(self, serder):
        """
        Returns own receipt, rct, message of serder with count code and receipt
        couples (pre+cig)
        Builds msg and then processes it into own db to validate
        """
        ked = serder.ked
        reserder = eventing.receipt(pre=ked["i"],
                                    sn=int(ked["s"], 16),
                                    said=serder.said)

        # sign serder event
        if self.kever.prefixer.transferable:
            seal = eventing.SealEvent(i=self.pre,
                                      s="{:x}".format(self.kever.lastEst.s),
                                      d=self.kever.lastEst.d)
            sigers = self.sign(ser=serder.raw,
                               indexed=True)
            msg = eventing.messagize(serder=reserder, sigers=sigers, seal=seal)
        else:
            cigars = self.sign(ser=serder.raw,
                               indexed=False)
            msg = eventing.messagize(reserder, cigars=cigars)

        self.psr.parseOne(ims=bytearray(msg))  # process local copy into db
        return msg


    def witness(self, serder):
        """
        Returns own receipt, rct, message of serder with count code and witness
        indexed receipt signatures if key state of serder.pre shows that own pre
        is a current witness of event in serder

        Before calling this must check that serder being witnessed has been
        accepted as valid event into controller's KEL

        """
        if self.kever.prefixer.transferable:  # not non-transferable prefix
            raise ValueError("Attempt to create witness receipt with"
                             " transferable pre={}.".format(self.pre))
        ked = serder.ked

        if serder.pre not in self.kevers:
            raise ValueError("Attempt by {} to witness event with missing key "
                             "state.".format(self.pre))
        kever = self.kevers[serder.pre]
        if self.pre not in kever.wits:
            print("Attempt by {} to witness event of {} when not a "
                  "witness in wits={}.".format(self.pre,
                                               serder.pre,
                                               kever.wits))
        index = kever.wits.index(self.pre)

        reserder = eventing.receipt(pre=ked["i"],
                                    sn=int(ked["s"], 16),
                                    said=serder.said)

        # assumes witness id is nontrans so public key is same as pre
        wigers = self.mgr.sign(ser=serder.raw,
                               pubs=[self.pre],
                               indices=[index])

        msg = eventing.messagize(reserder, wigers=wigers, pipelined=True)
        self.psr.parseOne(ims=bytearray(msg))  # process local copy into db
        return msg


    def replay(self, pre=None, fn=0):
        """
        Returns replay of FEL first seen event log for pre starting from fn
        Default pre is own .pre

        Parameters:
            pre is qb64 str or bytes of identifier prefix.
                default is own .pre
            fn is int first seen ordering number

        """
        if not pre:
            pre = self.pre

        msgs = bytearray()
        kever = self.kevers[pre]
        for msg in self.db.cloneDelegation(kever=kever):
            msgs.extend(msg)

        for msg in self.db.clonePreIter(pre=pre, fn=fn):
            msgs.extend(msg)

        return msgs

    def replayAll(self, key=b''):
        """
        Returns replay of FEL first seen event log for all pre starting at key

        Parameters:
            key (bytes): fnKey(pre, fn)

        """
        msgs = bytearray()
        for msg in self.db.cloneAllPreIter(key=key):
            msgs.extend(msg)
        return msgs

    def makeOtherEvent(self, pre, sn):
        """
        Returns: messagized bytearray message with attached signatures of
                 own event at sequence number sn from retrieving event at sn
                 and associated signatures from database.

        Parameters:
            sn is int sequence number of event
        """
        if pre not in self.kevers:
            return None

        msg = bytearray()
        dig = self.db.getKeLast(dbing.snKey(pre, sn))
        if dig is None:
            raise kering.MissingEntryError("Missing event for pre={} at sn={}."
                                           "".format(pre, sn))
        dig = bytes(dig)
        key = dbing.dgKey(pre, dig)  # digest key
        msg.extend(self.db.getEvt(key))
        msg.extend(coring.Counter(code=coring.CtrDex.ControllerIdxSigs,
                                  count=self.db.cntSigs(key)).qb64b)  # attach cnt
        for sig in self.db.getSigsIter(key):
            msg.extend(sig)  # attach sig
        return msg

    def fetchEnd(self, cid: str, role: str, eid: str):
        """
        Returns:
            endpoint (basing.EndpointRecord): instance or None
        """
        return self.db.ends.get(keys=(cid, role, eid))

    def fetchLoc(self, eid: str, scheme: str = kering.Schemes.http):
        """
        Returns:
            location (basing.LocationRecord): instance or None
        """
        return self.db.locs.get(keys=(eid, scheme))

    def fetchEndAllowed(self, cid: str, role: str, eid: str):
        """
        Returns:
            allowed (bool): True if eid is allowed as endpoint provider for cid
                          in role. False otherwise.
        Parameters:
            cid (str): identifier prefix qb64 of controller authZ endpoint provided
                       eid in role
            role (str): endpoint role such as (controller, witness, watcher, etc)
            eid (str): identifier prefix qb64 of endpoint provider in role
        """
        end = self.db.ends.get(keys=(cid, role, eid))
        return end.allowed if end else None

    def fetchEndEnabled(self, cid: str, role: str, eid: str):
        """
        Returns:
            allowed (bool): True if eid is allowed as endpoint provider for cid
                          in role. False otherwise.
        Parameters:
            cid (str): identifier prefix qb64 of controller authZ endpoint provided
                       eid in role
            role (str): endpoint role such as (controller, witness, watcher, etc)
            eid (str): identifier prefix qb64 of endpoint provider in role
        """
        end = self.db.ends.get(keys=(cid, role, eid))
        return end.enabled if end else None

    def fetchEndAuthzed(self, cid: str, role: str, eid: str):
        """
        Returns:
            allowed (bool): True if eid is allowed as endpoint provider for cid
                          in role. False otherwise.
        Parameters:
            cid (str): identifier prefix qb64 of controller authZ endpoint provided
                       eid in role
            role (str): endpoint role such as (controller, witness, watcher, etc)
            eid (str): identifier prefix qb64 of endpoint provider in role
        """
        end = self.db.ends.get(keys=(cid, role, eid))
        return (end.enabled or end.allowed) if end else None

    def fetchUrl(self, eid: str, scheme: str = kering.Schemes.http):
        """
        Returns:
            url (str): for endpoint provider given by eid
                       empty string when url is nullified
                       None when no location record
        """
        loc = self.db.locs.get(keys=(eid, scheme))
        return loc.url if loc else loc

    def fetchUrls(self, eid: str, scheme: str = ""):
        """
        Returns:
           surls (hicting.Mict): urls keyed by scheme for given eid. Assumes that
                user independently verifies that the eid is allowed for a
                given cid and role. If url is empty then does not return

        Parameters:
            eid (str): identifier prefix qb64 of endpoint provider
            scheme (str): url scheme
        """
        return hicting.Mict([(keys[1], loc.url) for keys, loc in
                             self.db.locs.getItemIter(keys=(eid, scheme)) if loc.url])

    def fetchRoleUrls(self, cid: str, *, role: str = "", scheme: str = "",
                      eids=None, enabled: bool = True, allowed: bool = True):
        """
        Returns:
           rurls (hicting.Mict):  of nested dicts. The top level dict rurls is keyed by
                        role for a given cid. Each value in rurls is eurls dict
                        keyed by the eid of authorized endpoint provider and
                        each value in eurls is a surls dict keyed by scheme

        Parameters:
            cid (str): identifier prefix qb64 of controller authZ endpoint provided
                       eid in role
            role (str): endpoint role such as (controller, witness, watcher, etc)
            scheme (str): url scheme
            eids (list): when provided restrict returns to only eids in eids
            enabled (bool): True means fetch any allowed witnesses as well
            allowed (bool): True means fetech any enabled witnesses as well
        """
        if eids is None:
            eids = []

        rurls = hicting.Mict()

        if role == kering.Roles.witness:
            if kever := self.kevers[cid] if cid in self.kevers else None:
                # latest key state for cid
                for eid in kever.wits:
                    if not eids or eid in eids:
                        surls = self.fetchUrls(eid, scheme=scheme)
                        if surls:
                            rurls.add(kering.Roles.witness,
                                      hicting.Mict([(eid, surls)]))

        for (_, erole, eid), end in self.db.ends.getItemIter(keys=(cid, role)):
            if (enabled and end.enabled) or (allowed and end.allowed):
                if not eids or eid in eids:
                    surls = self.fetchUrls(eid, scheme=scheme)
                    if surls:
                        rurls.add(erole, hicting.Mict([(eid, surls)]))
        return rurls

    def fetchWitnessUrls(self, cid: str, scheme: str = "", eids=None,
                         enabled: bool = True, allowed: bool = True):
        """
        Fetch witness urls for witnesses of cid at latest key state or enabled or
        allowed witnesses if not a witness at latest key state.

        Returns:
           rurls (hicting.Mict):  of nested dicts. The top level dict rurls is keyed by
                        role for a given cid. Each value in rurls is eurls dict
                        dict keyed by the eid of authorized endpoint provider and
                        each value in eurls is a surls dict keyed by scheme

        Parameters:
            cid (str): identifier prefix qb64 of controller authZ endpoint provided
                       eid is witness
            scheme (str): url scheme
            eids (list): when provided restrict returns to only eids in eids
            enabled (bool): True means fetch any allowed witnesses as well
            allowed (bool): True means fetech any enabled witnesses as well
        """
        return (self.fetchRoleUrls(cid=cid,
                                   role=kering.Roles.witness,
                                   scheme=scheme,
                                   eids=eids,
                                   enabled=enabled,
                                   allowed=allowed))

    def endsFor(self, pre):
        """ Load Authroized endpoints for provided AID

        Args:
            pre (str): qb64 aid for which to load ends

        Returns:
            dict: nest dict of Roles -> eid -> Schemes -> endpoints

        """
        ends = dict()

        for (_, erole, eid), end in self.db.ends.getItemIter(keys=(pre,)):
            locs = dict()
            urls = self.fetchUrls(eid=eid, scheme="")
            for rscheme, url in urls.firsts():
                locs[rscheme] = url

            if erole not in ends:
                ends[erole] = dict()

            ends[erole][eid] = locs

        witrolls = dict()
        if kever := self.kevers[pre] if pre in self.kevers else None:
            for eid in kever.wits:
                locs = dict()
                urls = self.fetchUrls(eid=eid, scheme="")
                for rscheme, url in urls.firsts():
                    locs[rscheme] = url

                witrolls[eid] = locs

        if len(witrolls) > 0:
            ends[Roles.witness] = witrolls

        return ends

    def reply(self, **kwa):
        """
        Returns:
            msg (bytearray): reply message

        Parameters:
            route is route path string that indicates data flow handler (behavior)
                to processs the reply
            data is list of dicts of comitted data such as seals
            dts is date-time-stamp of message at time or creation
            version is Version instance
            kind is serialization kind
        """
        return self.endorse(eventing.reply(**kwa))

    def makeEndRole(self, eid, role=kering.Roles.controller, allow=True, stamp=None):
        """
        Returns:
            msg (bytearray): reply message allowing/disallowing endpoint provider
               eid in role

        Parameters:
            eid (str): qb64 of endpoint provider to be authorized
            role (str): authorized role for eid
            allow (bool): True means add eid at role as authorized
                          False means cut eid at role as unauthorized
            stamp (str): date-time-stamp RFC-3339 profile of iso8601 datetime.
                          None means use now.
        """
        data = dict(cid=self.pre, role=role, eid=eid)
        route = "/end/role/add" if allow else "/end/role/cut"
        return self.reply(route=route, data=data, stamp=stamp)

    def loadEndRole(self, cid, eid, role=kering.Roles.controller):
        msgs = bytearray()
        end = self.db.ends.get(keys=(cid, role, eid))
        if end and (end.enabled or end.allowed):
            said = self.db.eans.get(keys=(cid, role, eid))
            serder = self.db.rpys.get(keys=(said.qb64,))
            cigars = self.db.scgs.get(keys=(said.qb64,))
            tsgs = eventing.fetchTsgs(db=self.db.ssgs, saider=said)

            if len(cigars) == 1:
                (verfer, cigar) = cigars[0]
                cigar.verfer = verfer
            else:
                cigar = None

            if len(tsgs) > 0:
                (prefixer, seqner, diger, sigers) = tsgs[0]
                seal = eventing.SealEvent(i=prefixer.qb64,
                                          s=seqner.snh,
                                          d=diger.qb64)
            else:
                sigers = None
                seal = None

            msgs.extend(eventing.messagize(serder=serder,
                                           cigars=[cigar] if cigar else [],
                                           sigers=sigers,
                                           seal=seal,
                                           pipelined=True))
        return msgs

    def makeLocScheme(self, url, eid=None, scheme="http", stamp=None):
        """
        Returns:
           msg (bytearray): reply message of own url service endpoint at scheme

        Parameters:
            url (str): url of endpoint, may have scheme missing or not
                       If url is empty then nullifies location
            eid (str): qb64 of endpoint provider to be authorized
            scheme (str): url scheme must matche scheme in url if any
            stamp (str): date-time-stamp RFC-3339 profile of iso8601 datetime.
                          None means use now.

        """
        eid = eid if eid is not None else self.pre
        data = dict(eid=eid, scheme=scheme, url=url)
        return self.reply(route="/loc/scheme", data=data, stamp=stamp)

    def replyLocScheme(self, eid, scheme=""):
        """
        Returns a reply message stream composed of entries authed by the given
        eid from the appropriate reply database including associated attachments
        in order to disseminate (percolate) BADA reply data authentication proofs.

        Currently uses promiscuous model for permitting endpoint discovery.
        Future is to use identity constraint graph to constrain discovery
        of whom by whom.

        eid and not scheme then:
            loc url for all schemes at eid

        eid and scheme then:
            loc url for scheme at eid

        Parameters:
            eid (str): endpoint provider id
            scheme (str): url scheme
        """
        msgs = bytearray()

        urls = self.fetchUrls(eid=eid, scheme=scheme)
        for rscheme, url in urls.firsts():
            msgs.extend(self.makeLocScheme(eid=eid, url=url, scheme=rscheme))

        return msgs

    def loadLocScheme(self, eid, scheme=None):
        msgs = bytearray()
        keys = (eid, scheme) if scheme else (eid,)
        for (pre, _), said in self.db.lans.getItemIter(keys=keys):
            serder = self.db.rpys.get(keys=(said.qb64,))
            cigars = self.db.scgs.get(keys=(said.qb64,))
            tsgs = eventing.fetchTsgs(db=self.db.ssgs, saider=said)

            if len(cigars) == 1:
                (verfer, cigar) = cigars[0]
                cigar.verfer = verfer
            else:
                cigar = None

            if len(tsgs) > 0:
                (prefixer, seqner, diger, sigers) = tsgs[0]
                seal = eventing.SealEvent(i=prefixer.qb64,
                                          s=seqner.snh,
                                          d=diger.qb64)
            else:
                sigers = None
                seal = None

            msgs.extend(eventing.messagize(serder=serder,
                                           cigars=[cigar] if cigar else [],
                                           sigers=sigers,
                                           seal=seal,
                                           pipelined=True))
        return msgs

    def replyEndRole(self, cid, role=None, eids=None, scheme=""):

        """
        Returns a reply message stream composed of entries authed by the given
        cid from the appropriate reply database including associated attachments
        in order to disseminate (percolate) BADA reply data authentication proofs.

        Currently uses promiscuous model for permitting endpoint discovery.
        Future is to use identity constraint graph to constrain discovery
        of whom by whom.

        cid and not role and not scheme then:
            end authz for all eids in all roles and loc url for all schemes at each eid
            if eids then only eids in eids else all eids

        cid and not role and scheme then:
            end authz for all eid in all roles and loc url for scheme at each eid
            if eids then only eids in eids else all eids

        cid and role and not scheme then:
            end authz for all eid in role and loc url for all schemes at each eid
            if eids then only eids in eids else all eids

        cid and role and scheme then:
            end authz for all eid in role and loc url for scheme at each eid
            if eids then only eids in eids else all eids


        Parameters:
            cid (str): identifier prefix qb64 of controller authZ endpoint provided
                       eid is witness
            role (str): authorized role for eid
            eids (list): when provided restrict returns to only eids in eids
            scheme (str): url scheme
        """
        msgs = bytearray()

        if eids is None:
            eids = []

        if cid not in self.kevers:
            return msgs

        msgs.extend(self.replay(cid))

        kever = self.kevers[cid]
        witness = self.pre in kever.wits  # see if we are cid's witness

        if role == kering.Roles.witness:
            # latest key state for cid
            for eid in kever.wits:
                if not eids or eid in eids:
                    if eid == self.pre:
                        msgs.extend(self.replyLocScheme(eid=eid, scheme=scheme))
                    else:
                        msgs.extend(self.loadLocScheme(eid=eid, scheme=scheme))
                    if not witness:  # we are not witness, send auth records
                        msgs.extend(self.makeEndRole(eid=eid, role=role))

        for (_, erole, eid), end in self.db.ends.getItemIter(keys=(cid,)):
            if (end.enabled or end.allowed) and (not role or role == erole) and (not eids or eid in eids):
                msgs.extend(self.loadLocScheme(eid=eid, scheme=scheme))
                msgs.extend(self.loadEndRole(cid=cid, eid=eid, role=erole))

        return msgs

    def replyToOobi(self, aid, role, eids=None):
        """
        Returns a reply message stream composed of entries authed by the given
        aid from the appropriate reply database including associated attachments
        in order to disseminate (percolate) BADA reply data authentication proofs.

        Currently uses promiscuous model for permitting oobi initiated endpoint
        discovery. Future is to use identity constraint graph to constrain
        discovery of whom by whom.

        This method is entry point for initiating replies generated by
        .replyEndRole and/or .replyLocScheme

        Parameters:
            aid (str): qb64 of identifier in oobi, may be cid or eid
            role (str): authorized role for eid
            eids (list): when provided restrict returns to only eids in eids

        """
        # default logic is that if self.pre is witness of aid and has a loc url
        # for self then reply with loc scheme for all witnesses even if self
        # not permiteed in .habs.oobis
        return self.replyEndRole(cid=aid, role=role, eids=eids)

    def getOwnEvent(self, sn, allowPartiallySigned=False):
        """
        Returns: message Serder and controller signatures of
                 own event at sequence number sn from retrieving event at sn
                 and associated signatures from database.

        Parameters:
            sn (int): is int sequence number of event
            allowPartiallySigned(bool): True means attempt to load from partial signed escrow
        """
        key = dbing.snKey(self.pre, sn)
        dig = self.db.getKeLast(key)
        if dig is None and allowPartiallySigned:
            dig = self.db.getPseLast(key)

        if dig is None:
            raise kering.MissingEntryError("Missing event for pre={} at sn={}."
                                           "".format(self.pre, sn))
        dig = bytes(dig)
        key = dbing.dgKey(self.pre, dig)  # digest key
        msg = self.db.getEvt(key)
        serder = serdering.SerderKERI(raw=bytes(msg))

        sigs = []
        for sig in self.db.getSigsIter(key):
            sigs.append(coring.Siger(qb64b=bytes(sig)))

        couple = self.db.getAes(key)

        return serder, sigs, couple

    def makeOwnEvent(self, sn, allowPartiallySigned=False):
        """
        Returns: messagized bytearray message with attached signatures of
                 own event at sequence number sn from retrieving event at sn
                 and associated signatures from database.

        Parameters:
            sn(int): is int sequence number of event
            allowPartiallySigned(bool): True means attempt to load from partial signed escrow

        """
        msg = bytearray()
        serder, sigs, couple = self.getOwnEvent(sn=sn,
                                                allowPartiallySigned=allowPartiallySigned)
        msg.extend(serder.raw)
        msg.extend(coring.Counter(code=coring.CtrDex.ControllerIdxSigs,
                                  count=len(sigs)).qb64b)  # attach cnt
        for sig in sigs:
            msg.extend(sig.qb64b)  # attach sig

        if couple is not None:
            msg.extend(coring.Counter(code=coring.CtrDex.SealSourceCouples,
                                      count=1).qb64b)
            msg.extend(couple)

        return msg

    def makeOwnInception(self, allowPartiallySigned=False):
        """
        Returns: messagized bytearray message with attached signatures of
                 own inception event by retrieving event and signatures
                 from database.
        """
        return self.makeOwnEvent(sn=0, allowPartiallySigned=allowPartiallySigned)

    def processCues(self, cues):
        """
        Returns bytearray of messages as a result of processing all cues

        Parameters:
           cues is deque of cues
        """
        msgs = bytearray()  # outgoing messages
        for msg in self.processCuesIter(cues):
            msgs.extend(msg)
        return msgs

    def processCuesIter(self, cues):
        """
        Iterate through cues and yields one or more msgs for each cue.

        Parameters:
            cues is deque of cues

        """
        while cues:  # iteratively process each cue in cues
            msgs = bytearray()
            cue = cues.pull() #cues.popleft()
            cueKin = cue["kin"]  # type or kind of cue

            if cueKin in ("receipt",):  # cue to receipt a received event from other pre
                cuedSerder = cue["serder"]  # Serder of received event for other pre
                cuedKed = cuedSerder.ked
                cuedPrefixer = coring.Prefixer(qb64=cuedKed["i"])
                logger.info("%s got cue: kin=%s\n%s\n\n", self.pre, cueKin,
                            json.dumps(cuedKed, indent=1))

                if cuedKed["t"] == coring.Ilks.icp:
                    dgkey = dbing.dgKey(self.pre, self.iserder.said)
                    found = False
                    if cuedPrefixer.transferable:  # find if have rct from other pre for own icp
                        for quadruple in self.db.getVrcsIter(dgkey):
                            if bytes(quadruple).decode("utf-8").startswith(cuedKed["i"]):
                                found = True  # yes so don't send own inception
                    else:  # find if already rcts of own icp
                        for couple in self.db.getRctsIter(dgkey):
                            if bytes(couple).decode("utf-8").startswith(cuedKed["i"]):
                                found = True  # yes so don't send own inception

                    if not found:  # no receipt from remote so send own inception
                        # no vrcs or rct of own icp from remote so send own inception
                        msgs.extend(self.makeOwnInception())

                msgs.extend(self.receipt(cuedSerder))
                yield msgs

            elif cueKin in ("replay",):
                msgs = cue["msgs"]
                yield msgs

            elif cueKin in ("reply",):
                data = cue["data"]
                route = cue["route"]
                msg = self.reply(data=data, route=route)
                yield msg

            # ToDo XXXX cue for kin = "query" various types of queries
            #     (query witness, query delegation etc)
            # ToDo XXXX cue for kin = "notice" new event
            # ToDo XXXX cue for kin = "witness" to create witness receipt own is witness
            # ToDo XXXX cue for kin = "noticeBadCloneFN"
            # ToDo XXXX cue for kin = "approveDelegation" own is delegator

            # ToDo XXXX cue for kin = "keyStateSaved"
            # ToDo XXXX cue for kin = "psUnescrow"
            # ToDo XXXX cue for kin = "stream"
            # ToDo XXXX cue for kin = "invalid"


    def witnesser(self):
        return True


class Hab(BaseHab):
    """
    Hab class provides a given idetnifier controller's local resource environment
    i.e. hab or habitat. Includes dependency injection of database, keystore,
    configuration file as well as Kevery and key store Manager..

    Attributes: (Injected)
        ks (keeping.Keeper): lmdb key store
        db (basing.Baser): lmdb data base for KEL etc
        cf (configing.Configer): config file instance
        mgr (keeping.Manager): creates and rotates keys in key store
        rtr (routing.Router): routes reply 'rpy' messages
        rvy (routing.Revery): factory that processes reply 'rpy' messages
        kvy (eventing.Kevery): factory for local processing of local event msgs
        psr (parsing.Parser):  parses local messages for .kvy .rvy


     Attributes:
        name (str): alias of controller
        pre (str): qb64 prefix of own local controller or None if new
        mhab (Hab | None): group member (local) hab when this Hab is multisig group
                           else None
        smids (list | None): group signing member ids qb64 when this Hab is group
                            else None
        rmids (list | None): group rotating member ids qb64 when this Hab is group
                            else None
        temp (bool): True means testing:
                     use weak level when salty algo for stretching in key creation
                     for incept and rotate of keys for this hab.pre
        inited (bool): True means fully initialized wrt databases.
                          False means not yet fully initialized
        delpre (str | None): delegator prefix if any else None

    Properties:
        kever (Kever): instance of key state of local controller
        kevers (dict): of eventing.Kever instances from KELs in local db
            keyed by qb64 prefix. Read through cache of of kevers of states for
            KELs in db.states
        iserder (serdering.SerderKERI): own inception event
        prefixes (OrderedSet): local prefixes for .db
        accepted (bool): True means accepted into local KEL.
                          False otherwise

    """

    def __init__(self, **kwa):
        super(Hab, self).__init__(**kwa)

    def make(self, *, secrecies=None, iridx=0, code=coring.MtrDex.Blake3_256, dcode=coring.MtrDex.Blake3_256,
             icode=coring.MtrDex.Ed25519_Seed, transferable=True, isith=None, icount=1, nsith=None, ncount=None,
             toad=None, wits=None, delpre=None, estOnly=False, DnD=False, hidden=False, data=None, algo=None,
             salt=None, tier=None):
        """
        Finish setting up or making Hab from parameters includes inception.
        Assumes injected dependencies were already setup.

        Parameters:
            secrecies (list | None): list of secrets to preload key pairs if any
            iridx (int): initial rotation index after ingestion of secrecies
            code (str): prefix derivation code default Blake3
            icode (str): signing key code default Ed25519
            dcode (str): next key derivation code default Blake3
            transferable (bool): True means pre is transferable (default)
                    False means pre is nontransferable
            isith (int | str | list | None): incepting signing threshold as
                    int, str hex, or list weighted if any, otherwise compute
                    default from verfers
            icount (int): incepting key count for number of keys. default 1
            nsith (int, str, list | None ): next signing threshold as int,
                str hex or list weighted if any, otherwise compute default from
                digers
            ncount (int | None): next key count for number of next keys
            toad (int |str| None): int or str hex of witness threshold if
                specified else compute default based on number of wits (backers)
            wits (list | None): qb64 prefixes of witnesses if any
            delpre (str | None): qb64 of delegator identifier prefix if any
            estOnly (bool | None): True means add trait eventing.TraitCodex.EstOnly
                which means only establishment events allowed in KEL for this Hab
                False (default) means allows non-est events and no trait is added.
            DnD (bool): True means add trait of eventing.TraitCodex.DnD which
                    means do not allow delegated identifiers from this identifier
                    False (default) means do allow and no trait is added.

            hidden (bool): A hidden Hab is not included in the list of Habs.
            data (list | None): seal dicts
                        algo is str key creation algorithm code
            salt(str): qb64 salt for randomization when salty algorithm used
            tier(str): is str security criticality tier code when using salty algorithm


        """
        if not (self.ks.opened and self.db.opened and self.cf.opened):
            raise kering.ClosedError("Attempt to make Hab with unopened "
                                     "resources.")
        if nsith is None:
            nsith = isith
        if ncount is None:
            ncount = icount
        if not transferable:
            ncount = 0  # next count
            nsith = '0'
            code = coring.MtrDex.Ed25519N

        stem = self.name if self.ns is None else f"{self.ns}{self.name}"
        if secrecies:  # replay
            ipre, _ = self.mgr.ingest(secrecies,
                                      iridx=iridx,
                                      ncount=ncount,
                                      stem=stem,
                                      transferable=transferable,
                                      temp=self.temp)
            verfers, digers = self.mgr.replay(pre=ipre, advance=False)

        else:  # use defaults
            verfers, digers = self.mgr.incept(icount=icount,
                                              icode=icode,
                                              ncount=ncount,
                                              stem=stem,
                                              transferable=transferable,
                                              dcode=dcode,
                                              algo=algo,
                                              salt=salt,
                                              tier=tier,
                                              temp=self.temp)

        serder = super(Hab, self).make(isith=isith,
                                       verfers=verfers,
                                       nsith=nsith,
                                       digers=digers,
                                       code=code,
                                       toad=toad,
                                       wits=wits,
                                       estOnly=estOnly,
                                       DnD=DnD,
                                       delpre=delpre,
                                       data=data)

        self.pre = serder.ked["i"]  # new pre

        opre = verfers[0].qb64  # default zeroth original pre from key store
        self.mgr.move(old=opre, new=self.pre)  # move to incept event pre

        # may want db method that updates .habs. and .prefixes together
        habord = basing.HabitatRecord(hid=self.pre)

        if not hidden:
            self.save(habord)
            self.prefixes.add(self.pre)

        # sign handles group hab with .mhab case
        sigers = self.sign(ser=serder.raw, verfers=verfers)

        # during delegation initialization of a habitat we ignore the MissingDelegationError and
        # MissingSignatureError
        try:
            self.kvy.processEvent(serder=serder, sigers=sigers)
        except MissingSignatureError:
            pass
        except Exception as ex:
            raise kering.ConfigurationError("Improper Habitat inception for "
                                            "pre={} {}".format(self.pre, ex))

        # read in self.cf config file and process any oobis or endpoints
        self.reconfigure()  # should we do this for new Habs not loaded from db

        self.inited = True

    @property
    def algo(self):
        pp = self.ks.prms.get(self.pre)
        return pp.algo

    def rotate(self, *, isith=None, nsith=None, ncount=None, toad=None, cuts=None, adds=None,
               data=None, **kwargs):
        """
        Perform rotation operation. Register rotation in database.
        Returns: bytearrayrotation message with attached signatures.

        Parameters:
            isith (int |str | None): current signing threshold as int or str hex
                                     or list of str weights
                                     default is prior next sith
            nsith (int |str | None): next, next signing threshold as int
                                     or str hex or list of str weights
                                     default is based on isith when None
            ncount (int | None): next number of signing keys
                                 default is len of prior next digs
            toad (int | str | None): hex of witness threshold after cuts and adds
            cuts (list | None): of qb64 pre of witnesses to be removed from witness list
            adds (list | None): of qb64 pre of witnesses to be added to witness list
            data (list | None): of dicts of committed data such as seals


        """
        # recall that kever.pre == self.pre
        kever = self.kever  # before rotation kever is prior next

        if ncount is None:
            ncount = len(kever.ndigers)  # use len of prior next digers as default

        try:
            verfers, digers = self.mgr.replay(pre=self.pre)
        except IndexError:  # old next is new current
            verfers, digers = self.mgr.rotate(pre=self.pre,
                                              ncount=ncount,
                                              temp=self.temp)
        return super(Hab, self).rotate(verfers=verfers, digers=digers,
                                       isith=isith,
                                       nsith=nsith,
                                       toad=toad,
                                       cuts=cuts,
                                       adds=adds,
                                       data=data)


class SignifyHab(BaseHab):
    """
    Hab class provides a given idetnifier controller's local resource environment
    i.e. hab or habitat. Includes dependency injection of database, keystore,
    configuration file as well as Kevery and key store Manager..
    """

    def __init__(self, **kwa):
        super(SignifyHab, self).__init__(**kwa)

    def make(self, *, serder, sigers, **kwargs):
        self.pre = serder.ked["i"]  # new pre
        self.prefixes.add(self.pre)

        self.processEvent(serder, sigers)

        habord = basing.HabitatRecord(hid=self.pre, sid=self.pre)
        self.save(habord)

        self.inited = True

    def sign(self, ser, verfers=None, indexed=True, indices=None, ondices=None, **kwa):
        """Sign given serialization ser using appropriate keys.
        Use provided verfers or .kever.verfers to lookup keys to sign.

        Parameters:
            ser (bytes): serialization to sign
            verfers (list[Verfer] | None): Verfer instances to get pub verifier
                keys to lookup private siging keys.
                verfers None means use .kever.verfers. Assumes that when group
                and verfers is not None then provided verfers must be .kever.verfers
            indexed (bool): When not mhab then
                True means use use indexed signatures and return
                list of Siger instances.
                False means do not use indexed signatures and return
                list of Cigar instances
            indices (list[int] | None): indices (offsets)
                when indexed == True. See Manager.sign
            ondices (list[int | None] | None): other indices (offsets)
                when indexed is True. See Manager.sign

        """
        raise kering.KeriError("Signify hab does not support local signing")

    def rotate(self, *, serder=None, sigers=None, **kwargs):
        """
        Perform rotation operation. Register rotation in database.
        Returns: bytearrayrotation message with attached signatures.

        Parameters:
            serder (Serder): pre-created rotation event
            sigers (list[Siger]): Siger instances on next rotation event

        """
        msg = eventing.messagize(serder, sigers=sigers)
        self.processEvent(serder, sigers)
        return msg

    def interact(self, *, serder=None, sigers=None, **kwargs):
        """
        Perform interaction operation. Register interaction in database.
        Returns: bytearray interaction message with attached signatures.
        """
        msg = eventing.messagize(serder, sigers=sigers)
        self.processEvent(serder, sigers)
        return msg

    def exchange(self, serder, seal=None, sigers=None, save=False):
        """
        Returns signed exn, message of serder with count code and receipt
        couples (pre+cig)
        Builds msg and then processes it into own db to validate
        """
        # sign serder event
        msg = eventing.messagize(serder=serder, sigers=sigers, seal=seal)

        if save:
            self.psr.parseOne(ims=bytearray(msg))  # process local copy into db

        return msg

    def processEvent(self, serder, sigers):
        """ Process event with signatures raising any exception that occurs

        Performs event processing using local Kevery allowing raising all exceptions

        Args:
            serder (Serder): event serder to process
            sigers (list): list of Siger or Cigar instances representing signatures over serder.raw

        """

        try:
            # verify event, update kever state, and escrow if group
            self.kvy.processEvent(serder=serder, sigers=sigers)
        except Exception:
            raise kering.ConfigurationError(f"Improper Habitat event type={serder.ked['t']} for "
                                            f"pre={self.pre}.")

    def replyEndRole(self, cid, role=None, eids=None, scheme=""):

        """
        Returns a reply message stream composed of entries authed by the given
        cid from the appropriate reply database including associated attachments
        in order to disseminate (percolate) BADA reply data authentication proofs.

        Currently uses promiscuous model for permitting endpoint discovery.
        Future is to use identity constraint graph to constrain discovery
        of whom by whom.

        cid and not role and not scheme then:
            end authz for all eids in all roles and loc url for all schemes at each eid
            if eids then only eids in eids else all eids

        cid and not role and scheme then:
            end authz for all eid in all roles and loc url for scheme at each eid
            if eids then only eids in eids else all eids

        cid and role and not scheme then:
            end authz for all eid in role and loc url for all schemes at each eid
            if eids then only eids in eids else all eids

        cid and role and scheme then:
            end authz for all eid in role and loc url for scheme at each eid
            if eids then only eids in eids else all eids


        Parameters:
            cid (str): identifier prefix qb64 of controller authZ endpoint provided
                       eid is witness
            role (str): authorized role for eid
            eids (list): when provided restrict returns to only eids in eids
            scheme (str): url scheme
        """
        msgs = bytearray()

        if eids is None:
            eids = []

        # introduce yourself, please
        msgs.extend(self.replay(cid))

        if role == kering.Roles.witness:
            if kever := self.kevers[cid] if cid in self.kevers else None:
                witness = self.pre in kever.wits  # see if we are cid's witness

                # latest key state for cid
                for eid in kever.wits:
                    if not eids or eid in eids:
                        msgs.extend(self.loadLocScheme(eid=eid, scheme=scheme))
                        if not witness:  # we are not witness, send auth records
                            msgs.extend(self.makeEndRole(eid=eid, role=role))
                if witness:  # we are witness, set KEL as authz
                    msgs.extend(self.replay(cid))

        for (_, erole, eid), end in self.db.ends.getItemIter(keys=(cid,)):
            if (end.enabled or end.allowed) and (not role or role == erole) and (not eids or eid in eids):
                msgs.extend(self.replay(eid))
                msgs.extend(self.loadLocScheme(eid=eid, scheme=scheme))
                msgs.extend(self.loadEndRole(cid=cid, eid=eid, role=erole))

        return msgs


class SignifyGroupHab(SignifyHab):
    def __init__(self, mhab=None, **kwa):
        self.mhab = mhab
        super(SignifyGroupHab, self).__init__(**kwa)

    def make(self, *, serder, sigers, **kwargs):
        self.pre = serder.ked["i"]  # new pre
        self.prefixes.add(self.pre)

        self.processEvent(serder, sigers)

        habord = basing.HabitatRecord(hid=self.pre, mid=self.mhab.pre, sid=self.pre)
        self.save(habord)

        self.inited = True

    def processEvent(self, serder, sigers):
        """ Process event with signatures ignoring missing signature exceptions

        Performs event processing using local Kevery allowing missing signature exceptions to be ignored
        so multisig events can be created with a single local signature

        Args:
            serder (Serder): event serder to process
            sigers (list): list of Siger or Cigar instances representing signatures over serder.raw

        """

        try:
            # verify event, update kever state, and escrow if group
            self.kvy.processEvent(serder=serder, sigers=sigers)
        except MissingSignatureError:
            pass
        except Exception:
            raise kering.ValidationError(f"Improper Habitat event type={serder.ked['t']} for "
                                         f"pre={self.pre}.")


class GroupHab(BaseHab):
    """
    Hab class provides a given idetnifier controller's local resource environment
    i.e. hab or habitat. Includes dependency injection of database, keystore,
    configuration file as well as Kevery and key store Manager.

    Attributes: (Injected)
        ks (keeping.Keeper): lmdb key store
        db (basing.Baser): lmdb data base for KEL etc
        cf (configing.Configer): config file instance
        mgr (keeping.Manager): creates and rotates keys in key store
        rtr (routing.Router): routes reply 'rpy' messages
        rvy (routing.Revery): factory that processes reply 'rpy' messages
        kvy (eventing.Kevery): factory for local processing of local event msgs
        psr (parsing.Parser):  parses local messages for .kvy .rvy


     Attributes:
        name (str): alias of controller
        pre (str): qb64 prefix of own local controller or None if new
        mhab (Hab | None): group member (local) hab when this Hab is multisig group
                           else None
        smids (list | None): group signing member ids qb64 when this Hab is group
                            else None
        rmids (list | None): group rotating member ids qb64 when this Hab is group
                            else None
        temp (bool): True means testing:
                     use weak level when salty algo for stretching in key creation
                     for incept and rotate of keys for this hab.pre
        inited (bool): True means fully initialized wrt databases.
                          False means not yet fully initialized
        delpre (str | None): delegator prefix if any else None

    Properties:
        kever (Kever): instance of key state of local controller
        kevers (dict): of eventing.Kever instances from KELs in local db
            keyed by qb64 prefix. Read through cache of of kevers of states for
            KELs in db.states
        iserder (serdering.SerderKERI): own inception event
        prefixes (OrderedSet): local prefixes for .db
        accepted (bool): True means accepted into local KEL.
                          False otherwise

    """

    def __init__(self, smids, mhab=None, rmids=None, **kwa):
        """
        Initialize instance.

        Injected Parameters:  (injected dependencies)
            ks (keeping.Keeper): lmdb key store
            db (basing.Baser): lmdb data base for KEL etc
            cf (configing.Configer): config file instance
            mgr (keeping.Manager): creates and rotates keys in key store
            rtr (routing.Router): routes reply 'rpy' messages
            rvy (routing.Revery): factory that processes reply 'rpy' messages
            kvy (eventing.Kevery): factory for local processing of local event msgs
            psr (parsing.Parser):  parses local messages for .kvy .rvy


        Parameters:
            name (str): alias name for local controller of habitat
            pre (str | None): qb64 identifier prefix of own local controller else None
            mhab (Hab | None): group member hab (local) when this Hab is multisig group
                           else None. The mhab.pre aid could be a member of
                           .smids, or .rmids, or both.
            smids (list | None): group signing member ids (prefixes) when this Hab is
                           multisig group else None. Set holds current signing authority
                           for group multi-sig identifier.
            rmids (list | None): group rotation member ids (prefixes) when this Hab is
                           multisig group else None. Set holds next rotating authority
                           for group multi-sig identifier. When None defaults to
                           copy of smids.
            temp (bool): True means testing:
                use weak level when salty algo for stretching in key creation
                for incept and rotate of keys for this hab.pre

        """
        self.mhab = mhab  # local participant Hab of this group hab
        self.smids = smids  # group signing member aids in this group hab
        self.rmids = rmids  # group rotating member aids in this group hab

        super(GroupHab, self).__init__(**kwa)

    def make(self, *, code=coring.MtrDex.Blake3_256, transferable=True, isith=None, nsith=None,
             toad=None, wits=None, delpre=None, estOnly=False, DnD=False,
             merfers, migers=None, data=None):
        """
        Finish setting up or making GroupHab from parameters includes inception.
        Assumes injected dependencies were already setup.

        Parameters:
            code (str): prefix derivation code default Blake3
            transferable (bool): True means pre is transferable (default)
                    False means pre is nontransferable
            isith (int | str | list | None): incepting signing threshold as
                    int, str hex, or list weighted if any, otherwise compute
                    default from verfers
            nsith (int, str, list | None ): next signing threshold as int,
                str hex or list weighted if any, otherwise compute default from
                digers
            toad (int |str| None): int or str hex of witness threshold if
                specified else compute default based on number of wits (backers)
            wits (list | None): qb64 prefixes of witnesses if any
            delpre (str | None): qb64 of delegator identifier prefix if any
            estOnly (bool | None): True means add trait eventing.TraitCodex.EstOnly
                which means only establishment events allowed in KEL for this Hab
                False (default) means allows non-est events and no trait is added.
            DnD (bool): True means add trait of eventing.TraitCodex.DnD which
                    means do not allow delegated identifiers from this identifier
                    False (default) means do allow and no trait is added.
            merfers (list[Verfer] | None): group member Verfer instances of
                    public keys qb64
                    one collected from each multisig group member
            migers (list[Diger] | None): group member Diger instances of public
                    next key digests qb64
                    one collected from each multisig group member
            data (list | None): seal dicts

        """
        if not (self.ks.opened and self.db.opened and self.cf.opened):
            raise kering.ClosedError("Attempt to make Hab with unopened "
                                     "resources.")
        if nsith is None:
            nsith = isith
        if not transferable:
            nsith = '0'
            code = coring.MtrDex.Ed25519N

        verfers = merfers
        digers = migers

        serder = super(GroupHab, self).make(isith=isith,
                                            verfers=verfers,
                                            nsith=nsith,
                                            digers=digers,
                                            code=code,
                                            toad=toad,
                                            wits=wits,
                                            estOnly=estOnly,
                                            DnD=DnD,
                                            delpre=delpre,
                                            data=data)

        self.pre = serder.ked["i"]  # new pre

        # sign handles group hab with .mhab case
        sigers = self.sign(ser=serder.raw, verfers=verfers)

        # during delegation initialization of a habitat we ignore the MissingDelegationError and
        # MissingSignatureError
        try:
            self.kvy.processEvent(serder=serder, sigers=sigers)
        except MissingSignatureError:
            pass
        except Exception as ex:
            raise kering.ConfigurationError("Improper Habitat inception for "
                                            "pre={} {}".format(self.pre, ex))

        habord = basing.HabitatRecord(hid=self.pre,
                                      mid=self.mhab.pre,
                                      smids=self.smids,
                                      rmids=self.rmids)

        self.save(habord)
        self.prefixes.add(self.pre)

        self.inited = True

    def rotate(self, serder=None, smids=None, rmids=None, **kwargs):

        if (habord := self.db.habs.get(keys=(self.name,))) is None:
            raise kering.ValidationError(f"Missing HabitatRecord for name={self.name}")

        if serder is None:
            msg = super(GroupHab, self).rotate(**kwargs)
        else:

            # sign handles group hab with .mhab case
            sigers = self.sign(ser=serder.raw, verfers=serder.verfers, rotated=True)

            # update own key event verifier state
            msg = eventing.messagize(serder, sigers=sigers)

            try:
                self.kvy.processEvent(serder=serder, sigers=sigers)
            except MissingSignatureError:
                pass
            except Exception as ex:
                raise kering.ValidationError("Improper Habitat rotation for "
                                             "pre={self.pre}.") from ex

        self.smids = smids
        self.rmids = rmids
        habord.smids = smids
        habord.rmids = rmids
        self.db.habs.pin(keys=self.name, val=habord)

        return msg

    def sign(self, ser, verfers=None, indexed=True, rotated=False, indices=None, ondices=None):
        """ Sign given serialization ser using appropriate keys.

        Walk .mhab's kel to find latest contribution of signing key material to group
        in order to sign properly. Contributions to group key material always use the
        zeroth element of signing key and/or rotating key digest lists. Find index into
        provided group verfers from current group key state.

        Parameters:
            ser (bytes): serialization to sign
            verfers (list[Verfer] | None): Verfer instances to get pub verifier
                keys to lookup private siging keys.
                verfers None means use .kever.verfers. Assumes that when group
                and verfers is not None then provided verfers must be .kever.verfers
            indexed (bool): When not mhab then
                True means use use indexed signatures and return
                list of Siger instances.
                False means do not use indexed signatures and return
                list of Cigar instances
            rotated (bool): When indexed and .mhab then
                True means use use dual indexed signatures, i.e. current indices
                and prior next ondices
                False means do not use dual indexed signatures, i.e. current
                siging indices only
                Otherwise ignore
                Assumes .kever.digers represent prior next
            indices (list[int] | None): indices (offsets)
                when indexed == True. See Manager.sign
            ondices (list[int | None] | None): other indices (offsets)
                when indexed is True. See Manager.sign

        """
        if verfers is None:
            verfers = self.kever.verfers  # when group these provide group signing keys

        # contributed member verfer from .mhab KEL.
        # Convention is to walk KEL to find correct contributed key if any.
        # Contributed keys MUSt always be zeroth element of member key list
        # and or member next key digests list.
        # first dig of mhab's prior nexter.digs.

        # walk member kel to find event if event where member contributed to
        # group est event from which verfers is taken
        if (result := self.mhab.kever.fetchLatestContribTo(verfers=verfers)) is None:
            raise ValueError(f"Member hab={self.mhab.pre} not a participant in "
                             f"event for this group hab={self.pre}.")

        sn, csi, merfer = result  # unpack result

        # the rotated flag may now be obsolete since fixing the Kever validation
        # logic to correctly chack both of the dual indices
        if rotated:  # rotation so uses the other index from dual indices
            # Either the verfer key or both the verfer key and prior dig
            # might be participants in signature on group hab's rotation event.
            # Each prior dig  must also be exposed as a participant
            # from current (after rotation) key list.
            # If mhab.kever.verfer[0] key is in group's new verfers (after rot)
            # then mhab participates in group as new key at index csi.
            # If in addition mhab prior dig at nexter.digs[0] is in group's
            # kever.digers (which will be prior next for group after rotation)
            # then mhab participates as group prior next at index pni.
            # else pni is None which means mhab only participates as new key.
            # get nexter of .mhab's prior Next est event
            migers = self.mhab.kever.fetchPriorDigers(sn=sn - 1)
            if migers:  # not  None or not empty
                mig = migers[0].qb64  # always use first prior dig of mhab
                digs = [diger.qb64 for diger in self.kever.ndigers]  # group habs prior digs
                try:
                    pni = digs.index(mig)  # find mhab dig index in group hab digs
                except ValueError:  # not found
                    pni = None  # default not participant
            else:
                pni = None  # default not participant

        else:  # not a rotation so ignores other index of dual index
            # pni = csi  # backwards compatible is both same
            # in the future may want to fix Kever validation logic so that
            pni = None  # should also work

        return (self.mhab.sign(ser=ser,
                               verfers=[merfer],
                               indexed=indexed,
                               indices=[csi],
                               ondices=[pni]))

    def witness(self, serder):
        """
        Group Habs are not valid witnesses thus can't provide witness receipts

        """
        raise ValueError("Attempt to witness by group hab ={self.pre}.")

    def query(self, pre, src, query=None, **kwa):
        """ Create, sign and return a `qry` message against the attester for the prefix

        Parameters:
            pre (str): qb64 identifier prefix being queried for
            src (str): qb64 identifier prefix of attester being queried
            query (dict): addition query modifiers to include in `q`
            **kwa (dict): keyword arguments passed to eventing.query

        Returns:
            bytearray: signed query event

        """

        query = query if query is not None else dict()
        query['i'] = pre
        query["src"] = src
        serder = eventing.query(query=query, **kwa)

        return self.mhab.endorse(serder, last=True)

    def witnesser(self):
        """This method name does not match logic???
        """
        kever = self.kever
        keys = [verfer.qb64 for verfer in kever.verfers]
        sigs = self.db.getSigs(dbing.dgKey(self.pre, kever.serder.saidb))
        if not sigs:  # otherwise its a list of sigs
            return False

        sigers = [coring.Siger(qb64b=bytes(sig)) for sig in sigs]
        windex = min([siger.index for siger in sigers])

        # True if Elected to perform delegation and witnessing
        return self.mhab.kever.verfers[0].qb64 == keys[windex]
