import pytest

from peewee import JOIN
from playhouse.test_utils import assert_query_count

from data.database import Repository, RepositoryPermission, TeamMember, Namespace
from data.model._basequery import filter_to_repos_for_user
from data.model.organization import get_admin_users
from data.model.user import get_namespace_user
from util.names import parse_robot_username

from test.fixtures import *


def _is_team_member(team, user):
    return user.id in [
        member.user_id for member in TeamMember.select().where(TeamMember.team == team)
    ]


def _get_visible_repositories_for_user(
    user, repo_kind="image", include_public=False, namespace=None
):
    """
    Returns all repositories directly visible to the given user, by either repo permission, or the
    user being the admin of a namespace.
    """
    for repo in Repository.select():
        if repo_kind is not None and repo.kind.name != repo_kind:
            continue

        if namespace is not None and repo.namespace_user.username != namespace:
            continue

        if include_public and repo.visibility.name == "public":
            yield repo
            continue

        # Direct repo permission.
        try:
            RepositoryPermission.get(repository=repo, user=user).get()
            yield repo
            continue
        except RepositoryPermission.DoesNotExist:
            pass

        # Team permission.
        found_in_team = False
        for perm in RepositoryPermission.select().where(RepositoryPermission.repository == repo):
            if perm.team and _is_team_member(perm.team, user):
                found_in_team = True
                break

        if found_in_team:
            yield repo
            continue

        # Org namespace admin permission.
        if user in get_admin_users(repo.namespace_user):
            yield repo
            continue


@pytest.mark.parametrize(
    "username",
    [
        "devtable",
        "devtable+dtrobot",
        "public",
        "reader",
    ],
)
@pytest.mark.parametrize("include_public", [True, False])
@pytest.mark.parametrize("filter_to_namespace", [True, False])
@pytest.mark.parametrize(
    "repo_kind",
    [
        None,
        "image",
        "application",
    ],
)
def test_filter_repositories(
    username, include_public, filter_to_namespace, repo_kind, initialized_db
):
    namespace = username if filter_to_namespace else None
    if "+" in username and filter_to_namespace:
        namespace, _ = parse_robot_username(username)

    user = get_namespace_user(username)
    query = (
        Repository.select()
        .distinct()
        .join(Namespace, on=(Repository.namespace_user == Namespace.id))
        .switch(Repository)
        .join(RepositoryPermission, JOIN.LEFT_OUTER)
    )

    # Prime the cache.
    Repository.kind.get_id("image")

    with assert_query_count(1):
        found = list(
            filter_to_repos_for_user(
                query,
                user.id,
                namespace=namespace,
                include_public=include_public,
                repo_kind=repo_kind,
            )
        )

    expected = list(
        _get_visible_repositories_for_user(
            user, repo_kind=repo_kind, namespace=namespace, include_public=include_public
        )
    )

    assert len(found) == len(expected)
    assert {r.id for r in found} == {r.id for r in expected}
