# Copyright (C) 2021-2022 Intel Corporation
# Copyright (C) 2022-2023 CVAT.ai Corporation
#
# SPDX-License-Identifier: MIT

import json
from http import HTTPStatus

import pytest
from cvat_sdk.api_client.api_client import ApiClient, Endpoint
from deepdiff import DeepDiff

from shared.utils.config import get_method, make_api_client, post_method

from .utils import CollectionSimpleFilterTestBase


class TestCreateInvitations:
    ROLES = ["worker", "supervisor", "maintainer", "owner"]

    @pytest.fixture(autouse=True)
    def setup(self, restore_db_per_function, organizations, memberships, admin_user):
        self.org_id = 2
        self.owner = self.get_member("owner", memberships, self.org_id)

    def _test_post_invitation_201(self, user, data, invitee, **kwargs):
        response = post_method(user, "invitations", data, **kwargs)

        assert response.status_code == HTTPStatus.CREATED, response.content
        assert data["role"] == response.json()["role"]
        assert invitee["id"] == response.json()["user"]["id"]
        assert kwargs["org_id"] == response.json()["organization"]

    def _test_post_invitation_403(self, user, data, **kwargs):
        response = post_method(user, "invitations", data, **kwargs)
        assert response.status_code == HTTPStatus.FORBIDDEN, response.content
        assert "You do not have permission" in str(response.content)

    @staticmethod
    def get_non_member_users(memberships, users):
        organization_users = set(m["user"]["id"] for m in memberships if m["user"] is not None)
        non_member_users = [u for u in users if u["id"] not in organization_users]

        return non_member_users

    @staticmethod
    def get_member(role, memberships, org_id):
        member = [
            m["user"]
            for m in memberships
            if m["role"] == role and m["organization"] == org_id and m["user"] is not None
        ][0]

        return member

    @pytest.mark.parametrize("org_role", ROLES)
    @pytest.mark.parametrize("invitee_role", ROLES)
    def test_create_invitation(self, organizations, memberships, users, org_role, invitee_role):
        org_id = self.org_id
        inviter_user = self.get_member(org_role, memberships, org_id)
        invitee_user = self.get_non_member_users(memberships, users)[0]

        if org_role in ["worker", "supervisor"]:
            self._test_post_invitation_403(
                inviter_user["username"],
                {"role": invitee_role, "email": invitee_user["email"]},
                org_id=org_id,
            )

        elif invitee_role in ["worker", "supervisor"]:
            self._test_post_invitation_201(
                inviter_user["username"],
                {"role": invitee_role, "email": invitee_user["email"]},
                invitee_user,
                org_id=org_id,
            )

        elif invitee_role == "maintainer":
            if org_role == "owner":
                # only the owner can invite a maintainer
                self._test_post_invitation_201(
                    inviter_user["username"],
                    {"role": invitee_role, "email": invitee_user["email"]},
                    invitee_user,
                    org_id=org_id,
                )
            else:
                self._test_post_invitation_403(
                    inviter_user["username"],
                    {"role": invitee_role, "email": invitee_user["email"]},
                    org_id=org_id,
                )

        elif invitee_role == "owner":
            # nobody can invite an owner
            self._test_post_invitation_403(
                inviter_user["username"],
                {"role": invitee_role, "email": invitee_user["email"]},
                org_id=org_id,
            )

        else:
            assert False, "Unknown role"


class TestInvitationsListFilters(CollectionSimpleFilterTestBase):
    field_lookups = {
        "owner": ["owner", "username"],
    }

    @pytest.fixture(autouse=True)
    def setup(self, restore_db_per_class, admin_user, invitations):
        self.user = admin_user
        self.samples = invitations

    def _get_endpoint(self, api_client: ApiClient) -> Endpoint:
        return api_client.invitations_api.list_endpoint

    @pytest.mark.parametrize(
        "field",
        ("owner",),
    )
    def test_can_use_simple_filter_for_object_list(self, field):
        return super().test_can_use_simple_filter_for_object_list(field)


@pytest.mark.usefixtures("restore_db_per_class")
class TestListInvitations:
    def _test_can_see_invitations(self, user, data, **kwargs):
        response = get_method(user, "invitations", **kwargs)

        assert response.status_code == HTTPStatus.OK
        assert DeepDiff(data, response.json()["results"]) == {}

    def test_admin_can_see_all_invitations(self, invitations):
        self._test_can_see_invitations("admin2", invitations.raw, page_size="all")

    @pytest.mark.parametrize("field_value, query_value", [(1, 1), (None, "")])
    def test_can_filter_by_org_id(self, field_value, query_value, invitations):
        invitations = filter(lambda i: i["organization"] == field_value, invitations)
        self._test_can_see_invitations(
            "admin2", list(invitations), page_size="all", org_id=query_value
        )


@pytest.mark.usefixtures("restore_db_per_class")
class TestGetInvitations:
    def test_can_remove_owner_and_fetch_with_sdk(self, admin_user, invitations):
        # test for API schema regressions
        source_inv = next(
            invitations
            for invitations in invitations
            if invitations.get("owner") and invitations["owner"]["username"] != admin_user
        ).copy()

        with make_api_client(admin_user) as api_client:
            api_client.users_api.destroy(source_inv["owner"]["id"])

            (_, response) = api_client.invitations_api.retrieve(source_inv["key"])
            fetched_inv = json.loads(response.data)

        source_inv["owner"] = None
        assert DeepDiff(source_inv, fetched_inv, ignore_order=True) == {}
