import plugins.ldap
import plugins.repositories
import asfpy.sqlite
import typing
import datetime


class Committer:
    def save(self, dbhandle: asfpy.sqlite.DB):
        document = {
            "asfid": self.asf_id,
            "githubid": self.github_login,
            "mfa": 1 if self.github_mfa else 0,
            "updated": datetime.datetime.now(),
        }
        dbhandle.upsert("ids", document, asfid=self.asf_id)
    
    def remove(self, dbhandle: asfpy.sqlite.DB):
        """Removes a user from the db, used for lockouts"""
        if self.asf_id:
            dbhandle.delete("ids", asfid=self.asf_id)


    def __init__(
        self, asf_id: str, linkdb: asfpy.sqlite.DB,
    ):
        self.asf_id = asf_id
        self.repositories: typing.Set[plugins.repositories.Repository] = set()
        self.projects: typing.Set[Project] = set()
        if linkdb:
            row = linkdb.fetchone("ids", limit=1, asfid=asf_id)
        else:
            row = None
        if row:
            self.github_login = row["githubid"]
            self.github_mfa = bool(row["mfa"])
            self.real_name = ""
        else:
            self.github_login = None
            self.github_mfa = False
            self.real_name = ""

    def __repr__(self):
        return self.asf_id

    def __eq__(self, other):
        if isinstance(other, str):
            return other == self.asf_id
        if not isinstance(other, Committer):
            return False
        return self.asf_id == other.asf_id

    def __hash__(self):
        return hash((self.asf_id,))


class Project:
    def __init__(self, org: "Organization", name: str, committers: list, pmc: list):
        self.name: str = name
        self.committers: typing.List[Committer] = []
        for committer in committers:
            account = org.add_committer(committer)
            self.committers.append(account)
            account.projects.add(self)
        self.pmc: typing.List[Committer] = []
        for owner in pmc or []:
            account = org.add_committer(owner)
            self.pmc.append(account)
            account.projects.add(self)
        self.public_repos: typing.List[plugins.repositories.Repository] = []
        self.private_repos: typing.List[plugins.repositories.Repository] = []

    def __repr__(self):
        return f"Project<{self.name}>"

    def add_repository(self, repo: plugins.repositories.Repository, private: bool):
        """Adds a repository to a project and assigns the repo to the commmitter/PMC group as applicable"""
        if private:
            self.private_repos.append(repo)
            for account in self.pmc:
                account.repositories.add(repo)
        else:
            self.public_repos.append(repo)
            for account in self.committers:
                account.repositories.add(repo)

    def public_github_team(self, mfa = None):
        """Returns the GitHub IDs of everyone that should be on the GitHub team for this project"""
        team_ids = set()
        for committer in self.committers:
            if mfa:
                if mfa.get(committer.github_login):
                    team_ids.add(committer.github_login)
            elif committer.github_mfa:
                team_ids.add(committer.github_login)
        return list(team_ids)

    def private_github_team(self, mfa = None):
        """Returns the GitHub IDs of everyone that should be on the GitHub private team for this project"""
        team_ids = set()
        for committer in self.pmc:
            if mfa:
                if mfa.get(committer.github_login):
                    team_ids.add(committer.github_login)
            elif committer.github_mfa:
                team_ids.add(committer.github_login)
        return list(team_ids)


class Organization:

    def __init__(self, linkdb: asfpy.sqlite.DB = None):
        self.committers: typing.List[Committer] = list()
        self.projects: typing.Dict[str, Project] = dict()
        self.linkdb: typing.Optional[asfpy.sqlite.DB] = linkdb

    def add_project(self, name: str, committers: typing.List[str], pmc: typing.List[str]) -> typing.Optional[Project]:
        if name and name not in self.projects:
            project = Project(org=self, name=name, committers=committers, pmc=pmc)
            if project:
                self.projects[name] = project
            return project
        return None

    def add_committer(self, asf_id: str):
        """Adds a new committer to the organization.
        If already found in previous projects, returns the old committer object"""
        if asf_id not in self.committers:
            committer = Committer(asf_id, self.linkdb)
            self.committers.append(committer)
            return committer
        else:
            for committer in self.committers:
                if committer.asf_id == asf_id:
                    return committer


async def compile_data(
    ldap: plugins.ldap.LDAPConfig,
    repositories: typing.List[plugins.repositories.Repository],
    linkdb: typing.Optional[asfpy.sqlite.DB]
) -> Organization:
    """Compiles a comprehensive list of projects and people associated with them"""
    org = Organization(linkdb=linkdb)
    discovered = 0
    async with plugins.ldap.LDAPClient(ldap) as lc:
        # Gather LDAP records of all projects first
        for repo in repositories:
            project = repo.project
            if not project:
                continue
            if project not in org.projects:
                committers, pmc = await lc.get_members(project)
                if committers and pmc:
                    discovered += 1
                    # print(f"Discovered project: {project} - {len(committers)} committers, {len(pmc)} in pmc")
                    if discovered % 50 == 0:
                        print("Discovered %d projects so far..." % discovered)
                org.add_project(name=project, committers=committers, pmc=pmc)
        # Then look at overrides
        for repo in repositories:
            project = repo.project
            if not project:
                continue
            xproject = org.projects[project]
            xproject.add_repository(repo, repo.private)
            # LDAP additional owner override
            if lc.ldap_override:
                repo_alts = lc.ldap_override.get("repository_alternate_owners")  # Dict of repo -> list of alt owners
                if repo_alts:
                    alts_for_this_repo = repo_alts.get(repo.filename, [])    # list of alt owners (projects, e.g. httpd, tomcat, members)
                    for alt_project in alts_for_this_repo:
                        if alt_project in org.projects:                  # If alt project is defined, add the repo to their list
                            print(f"Adding alternate owner of {repo.filename}: {alt_project}")
                            org.projects[alt_project].add_repository(repo, repo.private)


    return org
