import django
import pytest

from django.conf import settings
from django.contrib.auth.models import Group, Permission
from django.contrib.contenttypes.models import ContentType

from django.test import Client

from rest_framework.reverse import reverse

from access.models.role import Role
from access.models.tenant import Tenant

from settings.models.app_settings import AppSettings



class MetadataAttributesFunctionalBase:
    """ Functional Tests for API, HTTP/Options Method
    
    These tests ensure that **ALL** serializers include the metaclass that adds the required
    data to the HTTP Options method.

    Metaclass adds data required for the UI to function correctly.
    """

    app_namespace: str = None

    url_name: str = None

    viewset_type: str = 'list'

    @classmethod
    def presetUpTestData(self):
        """Setup Test

        1. Create an organization for user and item
        . create an organization that is different to item
        2. Create a team
        3. create teams with each permission: view, add, change, delete
        4. create a user per team
        """

        User = django.contrib.auth.get_user_model()

        self.organization = Tenant.objects.create(name='test_org')

        # self.organization = organization

        self.different_organization = Tenant.objects.create(name='test_different_organization')

        self.global_organization = Tenant.objects.create(name='test_global_organization')

        self.no_permissions_user = User.objects.create_user(username="test_no_permissions", password="password")

        self.add_user = User.objects.create_user(username="test_user_add", password="password")

        self.change_user = User.objects.create_user(username="test_user_change", password="password")

        self.delete_user = User.objects.create_user(username="test_user_delete", password="password")

        self.different_organization_user = User.objects.create_user(username="test_different_organization_user", password="password")

        self.view_user = User.objects.create_user(username="test_user_view", password="password")

        app_settings = AppSettings.objects.get(
            owner_organization = None
        )

        app_settings.global_organization = self.global_organization

        app_settings.save()


    @classmethod
    def setUpTestData(self):
        """Setup Test

        1. Create an organization for user and item
        . create an organization that is different to item
        2. Create a team
        3. create teams with each permission: view, add, change, delete
        4. create a user per team
        """

        try:
            if hasattr(self, 'kwargs_create_item'):
                self.item = self.model.objects.create(
                    # organization = organization,
                    **self.kwargs_create_item
                )
        except TypeError:
            pass    # m2m field error, create item in model test case

        if hasattr(self, 'kwargs_create_item_diff_org'):

            self.other_org_item = self.model.objects.create(
                # organization = self.different_organization,
                **self.kwargs_create_item_diff_org
            )


        if hasattr(self, 'url_view_kwargs'):
            self.url_view_kwargs.update({ 'pk': self.item.id })


        view_permissions = Permission.objects.get(
                codename = 'view_' + self.model._meta.model_name,
                content_type = ContentType.objects.get(
                    app_label = self.model._meta.app_label,
                    model = self.model._meta.model_name,
                )
            )


        view_group = Group.objects.create(
            name = 'view_team',
        )


        view_role = Role.objects.create(
            name = 'view_team',
            organization = self.organization,
        )

        view_role.permissions.set([view_permissions])
        view_role.groups.set([view_group])

        self.view_user.groups.set([view_group])


        add_permissions = Permission.objects.get(
                codename = 'add_' + self.model._meta.model_name,
                content_type = ContentType.objects.get(
                    app_label = self.model._meta.app_label,
                    model = self.model._meta.model_name,
                )
            )


        add_group = Group.objects.create(
            name = 'add_team',
        )

        add_role = Role.objects.create(
            name = 'add_team',
            organization = self.organization,
        )

        add_role.permissions.set([add_permissions])
        add_role.groups.set([add_group])

        self.add_user.groups.set([add_group])


        change_permissions = Permission.objects.get(
                codename = 'change_' + self.model._meta.model_name,
                content_type = ContentType.objects.get(
                    app_label = self.model._meta.app_label,
                    model = self.model._meta.model_name,
                )
            )


        change_group = Group.objects.create(
            name = 'change_team',
        )

        change_role = Role.objects.create(
            name = 'change_team',
            organization = self.organization,
        )

        change_role.permissions.set([change_permissions])
        change_role.groups.set([change_group])

        self.change_user.groups.set([change_group])


        delete_permissions = Permission.objects.get(
                codename = 'delete_' + self.model._meta.model_name,
                content_type = ContentType.objects.get(
                    app_label = self.model._meta.app_label,
                    model = self.model._meta.model_name,
                )
            )


        delete_group = Group.objects.create(
            name = 'delete_team',
        )

        delete_role = Role.objects.create(
            name = 'delete_team',
            organization = self.organization,
        )

        delete_role.permissions.set([delete_permissions])
        delete_role.groups.set([delete_group])

        self.delete_user.groups.set([delete_group])


        diff_org_group = Group.objects.create(
            name = 'diff_org_team',
        )

        diff_org_role = Role.objects.create(
            name = 'diff_org_team',
            organization = self.different_organization,
        )

        diff_org_role.permissions.set([
            view_permissions,
            add_permissions,
            change_permissions,
            delete_permissions,
        ])
        diff_org_role.groups.set([diff_org_group])

        self.different_organization_user.groups.set([diff_org_group])


    def test_method_options_request_list_ok(self):
        """Test HTTP/Options Method

        Ensure the request returns `OK`.
        """

        client = Client()
        client.force_login(self.view_user)

        if getattr(self, 'url_kwargs', None):

            url = reverse(self.app_namespace + ':' + self.url_name + '-' + self.viewset_type, kwargs = self.url_kwargs)

        else:

            url = reverse(self.app_namespace + ':' + self.url_name + '-' + self.viewset_type)

        response = client.options( url, content_type='application/json' )

        assert response.status_code == 200


    def test_method_options_request_list_data_returned(self):
        """Test HTTP/Options Method

        Ensure the request returns data.
        """

        client = Client()
        client.force_login(self.view_user)

        if getattr(self, 'url_kwargs', None):

            url = reverse(self.app_namespace + ':' + self.url_name + '-' + self.viewset_type, kwargs = self.url_kwargs)

        else:

            url = reverse(self.app_namespace + ':' + self.url_name + '-' + self.viewset_type)

        response = client.options( url, content_type='application/json' )

        assert response.data is not None


    def test_method_options_request_list_data_type(self):
        """Test HTTP/Options Method

        Ensure the request data returned is of type `dict`
        """

        client = Client()
        client.force_login(self.view_user)

        if getattr(self, 'url_kwargs', None):

            url = reverse(self.app_namespace + ':' + self.url_name + '-' + self.viewset_type, kwargs = self.url_kwargs)

        else:

            url = reverse(self.app_namespace + ':' + self.url_name + '-' + self.viewset_type)

        response = client.options( url, content_type='application/json' )

        assert type(response.data) is dict


    def test_method_options_request_detail_ok(self):
        """Test HTTP/Options Method

        Ensure the request returns `OK`.
        """

        client = Client()
        client.force_login(self.view_user)

        if getattr(self, 'url_kwargs', None):

            url = reverse(self.app_namespace + ':' + self.url_name + '-' + self.viewset_type, kwargs = self.url_kwargs)

        else:

            url = reverse(self.app_namespace + ':' + self.url_name + '-' + self.viewset_type)

        response = client.options( url, content_type='application/json' )

        assert response.status_code == 200


    def test_method_options_request_detail_data_returned(self):
        """Test HTTP/Options Method

        Ensure the request returns data.
        """

        client = Client()
        client.force_login(self.view_user)

        response = client.options(
            reverse(
                self.app_namespace + ':' + self.url_name + '-detail',
                kwargs=self.url_view_kwargs
            ),
            content_type='application/json'
        )

        assert response.data is not None


    def test_method_options_request_detail_data_type(self):
        """Test HTTP/Options Method

        Ensure the request data returned is of type `dict`
        """

        client = Client()
        client.force_login(self.view_user)

        response = client.options(
            reverse(
                self.app_namespace + ':' + self.url_name + '-detail',
                kwargs=self.url_view_kwargs
            ),
            content_type='application/json'
        )

        assert type(response.data) is dict



    def test_method_options_request_detail_data_has_key_urls(self):
        """Test HTTP/Options Method

        Ensure the request data returned has key `urls`
        """

        client = Client()
        client.force_login(self.view_user)

        response = client.options(
            reverse(
                self.app_namespace + ':' + self.url_name + '-detail',
                kwargs=self.url_view_kwargs
            ),
            content_type='application/json'
        )

        assert 'urls' in response.data


    def test_method_options_request_detail_data_key_urls_is_dict(self):
        """Test HTTP/Options Method

        Ensure the request data key `urls` is dict
        """

        client = Client()
        client.force_login(self.view_user)

        response = client.options(
            reverse(
                self.app_namespace + ':' + self.url_name + '-detail',
                kwargs=self.url_view_kwargs
            ),
            content_type='application/json'
        )

        assert type(response.data['urls']) is dict



    def test_method_options_request_detail_data_has_key_urls_self(self):
        """Test HTTP/Options Method

        Ensure the request data returned has key `urls.self`
        """

        client = Client()
        client.force_login(self.view_user)

        response = client.options(
            reverse(
                self.app_namespace + ':' + self.url_name + '-detail',
                kwargs=self.url_view_kwargs
            ),
            content_type='application/json'
        )

        assert 'urls' in response.data


    def test_method_options_request_detail_data_key_urls_self_is_str(self):
        """Test HTTP/Options Method

        Ensure the request data key `urls.self` is a string
        """

        client = Client()
        client.force_login(self.view_user)

        response = client.options(
            reverse(
                self.app_namespace + ':' + self.url_name + '-detail',
                kwargs=self.url_view_kwargs
            ),
            content_type='application/json'
        )

        assert type(response.data['urls']['self']) is str


    @pytest.mark.skip(reason='to be written')
    def test_method_options_no_field_is_generic(self):
        """Test HTTP/Options Method

        Fields are used for the UI to setup inputs correctly.

        Ensure all fields at path `.actions.<METHOD>.<name>.type` do not have `GenericField` as the value.
        """

        pass













    def test_method_options_request_detail_data_has_key_documentation(self):
        """Test HTTP/Options Method

        Ensure the request data returned has key `documentation`
        """

        client = Client()
        client.force_login(self.view_user)

        response = client.options(
            reverse(
                self.app_namespace + ':' + self.url_name + '-detail',
                kwargs=self.url_view_kwargs
            ),
            content_type='application/json'
        )

        assert 'documentation' in response.data


    def test_method_options_request_detail_data_key_documentation_is_str(self):
        """Test HTTP/Options Method

        Ensure the request data key `documentation` is str
        """

        client = Client()
        client.force_login(self.view_user)

        response = client.options(
            reverse(
                self.app_namespace + ':' + self.url_name + '-detail',
                kwargs=self.url_view_kwargs
            ),
            content_type='application/json'
        )

        assert type(response.data['documentation']) is str


    def test_method_options_request_detail_data_key_documentation_is_url(self):
        """Test HTTP/Options Method

        Ensure the request data key `documentation` is prefixed with settings.DOC_ROOT
        """

        client = Client()
        client.force_login(self.view_user)

        response = client.options(
            reverse(
                self.app_namespace + ':' + self.url_name + '-detail',
                kwargs=self.url_view_kwargs
            ),
            content_type='application/json'
        )

        assert str(response.data['documentation']).startswith( str(settings.DOCS_ROOT) )



class MetadataAttributesFunctionalTable:
    """Test cases for Metadata
    
    These test cases are for models that are expected to
    be rendered in a table.
    """


    def test_method_options_request_list_data_has_key_table_fields(self):
        """Test HTTP/Options Method

        Ensure the request data returned has key `table_fields`
        """

        client = Client()
        client.force_login(self.view_user)

        if getattr(self, 'url_kwargs', None):

            url = reverse(self.app_namespace + ':' + self.url_name + '-' + self.viewset_type, kwargs = self.url_kwargs)

        else:

            url = reverse(self.app_namespace + ':' + self.url_name + '-' + self.viewset_type)

        response = client.options( url, content_type='application/json' )

        assert 'table_fields' in response.data


    def test_method_options_request_list_data_key_table_fields_is_list(self):
        """Test HTTP/Options Method

        Ensure the request data['table_fields'] is of type `list`
        """

        client = Client()
        client.force_login(self.view_user)

        if getattr(self, 'url_kwargs', None):

            url = reverse(self.app_namespace + ':' + self.url_name + '-' + self.viewset_type, kwargs = self.url_kwargs)

        else:

            url = reverse(self.app_namespace + ':' + self.url_name + '-' + self.viewset_type)

        response = client.options( url, content_type='application/json' )

        assert type(response.data['table_fields']) is list


    def test_method_options_request_list_data_key_table_fields_is_list_of_str(self):
        """Test HTTP/Options Method

        Ensure the request data['table_fields'] list is of `str`
        """

        client = Client()
        client.force_login(self.view_user)

        if getattr(self, 'url_kwargs', None):

            url = reverse(self.app_namespace + ':' + self.url_name + '-' + self.viewset_type, kwargs = self.url_kwargs)

        else:

            url = reverse(self.app_namespace + ':' + self.url_name + '-' + self.viewset_type)

        response = client.options( url, content_type='application/json' )

        all_string = True

        for item in response.data['table_fields']:

            if( 
                type(item) is not str
                and not (
                    type(item) is dict
                    and 'field' in item
                    and 'type' in item
                    and item['type'] == 'link'
                    and 'key' in item
                )
            ):

                all_string = False


        assert all_string



class MetadataAttributesFunctionalEndpoint:
    """Test cases for Metadata
    
    These test cases are for models that will have an
    endpoint. i.e. A Detail view
    """


    def test_method_options_request_detail_data_has_key_page_layout(self):
        """Test HTTP/Options Method

        Ensure the request data returned has key `layout`
        """

        client = Client()
        client.force_login(self.view_user)

        response = client.options(
            reverse(
                self.app_namespace + ':' + self.url_name + '-detail',
                kwargs=self.url_view_kwargs
            ),
            content_type='application/json'
        )

        assert 'layout' in response.data


    def test_method_options_request_detail_data_key_page_layout_is_list(self):
        """Test HTTP/Options Method

        Ensure the request data['layout'] is of type `list`
        """

        client = Client()
        client.force_login(self.view_user)

        response = client.options(
            reverse(
                self.app_namespace + ':' + self.url_name + '-detail',
                kwargs=self.url_view_kwargs
            ),
            content_type='application/json'
        )

        assert type(response.data['layout']) is list


    def test_method_options_request_detail_data_key_page_layout_is_list_of_dict(self):
        """Test HTTP/Options Method

        Ensure the request data['layout'] list is of `dict`
        """

        client = Client()
        client.force_login(self.view_user)

        response = client.options(
            reverse(
                self.app_namespace + ':' + self.url_name + '-detail',
                kwargs=self.url_view_kwargs
            ),
            content_type='application/json'
        )

        all_dict = True

        for item in response.data['layout']:

            if type(item) is not dict:

                all_dict = False


        assert all_dict


    def test_method_options_request_detail_data_key_page_layout_dicts_key_exists_name(self):
        """Test HTTP/Options Method

        Ensure the request data['layout'].x has key `name`
        """

        client = Client()
        client.force_login(self.view_user)

        response = client.options(
            reverse(
                self.app_namespace + ':' + self.url_name + '-detail',
                kwargs=self.url_view_kwargs
            ),
            content_type='application/json'
        )

        has_key = True

        for item in response.data['layout']:

            if 'name' not in item:

                has_key = False


        assert has_key


    def test_method_options_request_detail_data_key_page_layout_dicts_key_type_name(self):
        """Test HTTP/Options Method

        Ensure the request data['layout'].x.[name] is of type `str`
        """

        client = Client()
        client.force_login(self.view_user)

        response = client.options(
            reverse(
                self.app_namespace + ':' + self.url_name + '-detail',
                kwargs=self.url_view_kwargs
            ),
            content_type='application/json'
        )

        all_are_str = True

        for item in response.data['layout']:

            if type(item['name']) is not str:

                all_are_str = False


        assert all_are_str


    def test_method_options_request_detail_data_key_page_layout_dicts_key_exists_sections(self):
        """Test HTTP/Options Method

        Ensure the request data['layout'].x has key `sections`
        """

        client = Client()
        client.force_login(self.view_user)

        response = client.options(
            reverse(
                self.app_namespace + ':' + self.url_name + '-detail',
                kwargs=self.url_view_kwargs
            ),
            content_type='application/json'
        )

        has_key = True

        for item in response.data['layout']:

            if 'sections' not in item:

                has_key = False


        assert has_key


    def test_method_options_request_detail_data_key_page_layout_dicts_key_type_sections(self):
        """Test HTTP/Options Method

        Ensure the request data['layout'].x.[sections] is of type `list`
        """

        client = Client()
        client.force_login(self.view_user)

        response = client.options(
            reverse(
                self.app_namespace + ':' + self.url_name + '-detail',
                kwargs=self.url_view_kwargs
            ),
            content_type='application/json'
        )

        all_are_str = True

        for item in response.data['layout']:

            if type(item['sections']) is not list:

                all_are_str = False


        assert all_are_str



@pytest.mark.api
@pytest.mark.functional
@pytest.mark.metadata
class MetadataAttributesFunctional(
    MetadataAttributesFunctionalEndpoint,
    MetadataAttributesFunctionalTable,
    MetadataAttributesFunctionalBase,
):
    pass



class MetaDataNavigationEntriesFunctional:
    """ Test cases for the Navigation menu

    Navigation menu is rendered as part of the API when a HTTP/OPTIONS
    request has been made. Each menu entry requires that a user has View
    permissions for that entry to be visible.

    **No** menu entry is to be returned for **any** user whom does not 
    have the corresponding view permission.

    These test cases are for any model that has a navigation menu entry.

    ## Tests

    - Ensure add user does not have menu entry
    - Ensure change user does not have menu entry
    - Ensure delete user does not have menu entry
    - Ensure the view user has menu entry
    - No menu to return without pages for add user
    - No menu to return without pages for change user
    - No menu to return without pages for delete user
    - No menu to return without pages for view user
    """

    menu_id: str = None
    """ Name of the Menu entry

    Match for .navigation[i][name]
    """

    menu_entry_id: str = None
    """Name of the menu entry

    Match for .navigation[i][pages][i][name]
    """

    app_namespace:str = None
    """application namespace"""

    url_name: str = None
    """url name"""

    url_kwargs: dict = None
    """View URL kwargs"""

    add_user = None
    """ User with add permission"""

    change_user = None
    """ User with change permission"""

    delete_user = None
    """ User with delete permission"""

    view_user = None
    """ User with view permission"""



    def test_navigation_entry_add_user(self):
        """Test HTTP/Options Method Navigation Entry

        Ensure that a user with add permission, does not
        have the menu entry within navigation
        """

        client = Client()
        client.force_login(self.add_user)


        if getattr(self, 'url_kwargs', None):

            url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs = self.url_kwargs)

        else:

            url = reverse(self.app_namespace + ':' + self.url_name + '-list')

        response = client.options(
            url,
            content_type='application/json'
        )

        assert response.status_code == 403



    def test_navigation_entry_change_user(self):
        """Test HTTP/Options Method Navigation Entry

        Ensure that a user with change permission, does not
        have the menu entry within navigation
        """

        client = Client()
        client.force_login(self.change_user)

        if getattr(self, 'url_kwargs', None):

            url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs = self.url_kwargs)

        else:

            url = reverse(self.app_namespace + ':' + self.url_name + '-list')

        response = client.options(
            url,
            content_type='application/json'
        )


        assert response.status_code == 403



    def test_navigation_entry_delete_user(self):
        """Test HTTP/Options Method Navigation Entry

        Ensure that a user with delete permission, does not
        have the menu entry within navigation
        """

        client = Client()
        client.force_login(self.delete_user)

        if getattr(self, 'url_kwargs', None):

            url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs = self.url_kwargs)

        else:

            url = reverse(self.app_namespace + ':' + self.url_name + '-list')

        response = client.options(
            url,
            content_type='application/json'
        )


        assert response.status_code == 403



    def test_navigation_entry_view_user(self):
        """Test HTTP/Options Method Navigation Entry

        Ensure that a user with view permission,
        has the menu entry within navigation
        """

        client = Client()
        client.force_login(self.view_user)

        if getattr(self, 'url_kwargs', None):

            url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs = self.url_kwargs)

        else:

            url = reverse(self.app_namespace + ':' + self.url_name + '-list')

        response = client.options(
            url,
            content_type='application/json'
        )

        menu_entry_found: bool = False

        for nav_menu in response.data['navigation']:

            if nav_menu['name'] == self.menu_id:

                for menu_entry in nav_menu['pages']:

                    if menu_entry['name'] == self.menu_entry_id:

                        menu_entry_found = True

        assert menu_entry_found



    def test_navigation_no_empty_menu_view_user(self):
        """Test HTTP/Options Method Navigation Entry

        Ensure that a user with view permission, does not
        have any nave menu without pages
        """

        client = Client()
        client.force_login(self.view_user)

        if getattr(self, 'url_kwargs', None):

            url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs = self.url_kwargs)

        else:

            url = reverse(self.app_namespace + ':' + self.url_name + '-list')

        response = client.options(
            url,
            content_type='application/json'
        )

        no_empty_menu_found: bool = True

        for nav_menu in response.data['navigation']:

            if len(nav_menu['pages']) == 0:

                no_empty_menu_found = False

        assert no_empty_menu_found
